C 规范的哪一部分(如果有)指定 C 标准库输入函数使缓冲区的后半部分保持不变?
Posted
技术标签:
【中文标题】C 规范的哪一部分(如果有)指定 C 标准库输入函数使缓冲区的后半部分保持不变?【英文标题】:What part of the C spec, if any, specifies that C standard library input functions leave the latter part of a buffer unchanged? 【发布时间】:2017-01-09 00:03:05 【问题描述】:示例:假设文件在文本模式下成功打开,行结束翻译不成问题,并且文件包含"12345\n"
,并且使用 3 个函数之一成功读取,
int main(void)
char *Filename = "c:\\tmp\\text.txt";
FILE *stream = fopen(Filename, "w");
assert(stream);
fputs("12345\n", stream);
fclose(stream);
stream = fopen(Filename, "r");
assert(stream);
char buf[8];
rewind(stream);
memset(buf, 'x', sizeof buf);
size_t sz = fread(buf, 1, sizeof buf, stream);
// Is buf[6], buf[7] specified to remain unchanged as `x'?
printf("<%.6s> %c %c\n", buf, buf[6], buf[7]);
assert(sz == 6);
rewind(stream);
memset(buf, 'y', sizeof buf);
fgets(buf, sizeof buf, stream);
// Is buf[7] specified to remain unchanged as `y'?
printf("<%s> %d %c\n", buf, buf[6], buf[7]);
assert(strlen(buf) == 6);
rewind(stream);
memset(buf, 'z', sizeof buf);
fscanf(stream, "%7[^\n]", buf);
// Is buf[6], buf[7] specified remain unchanged as `z'?
printf("<%s> %c %c\n", buf, buf[6], buf[7]);
assert(strlen(buf) == 5);
fclose(stream);
return 0;
样本输出
<12345
> x x
<12345
> 0 y
<12345> z z
什么直接或间接指定
buf
的最后部分保持不变? 也许这只是常见的未指定做法。
辅助数据:在读取错误时,缓冲区内容未定义 - 但这是关于无错误读取。
我很少看到需要知道缓冲区剩余部分的稳定性,并且通过实验,我没有看到任何变化。我在fgets()
之前阅读了使用memset(buf, '\n', sizeof buf);
来帮助检测可能的空字符的想法,这首先让我想到了它的特殊性。
【问题讨论】:
您使用的是 M$ 系统,它会默默地删除 '\r'。也许尝试以“rb”模式打开? ... 7.21.3 第 11 段“字节输入函数从流中读取字符,就像通过连续调用 fgetc 函数一样。” 这个案例对我来说似乎还没有结束:“......好像通过连续调用 fgetc 函数。”fgetc()
以int
的形式返回一个字符,而fgets()
以char
的形式将一个字符存储在缓冲区中,并返回一个指针。注意fgetc()
可以返回EOF
,但fgets()
不一定能将EOF
存入缓冲区。该标准似乎指定了如何从文件中检索字符,但没有指定如何将它们存储在缓冲区中。这个问题似乎是在询问这种存储可以合法实施的方式。
@DavidBowling:EOF
不是字符。这是fgetc
返回的值,表示没有可用的字符。并非巧合的是,它不能与fgetc
返回的字符混淆。 (参见第 7.21.7.1 节第 3 段:“……如果流位于文件末尾……fgetc
函数返回 EOF
。否则,fgetc
函数返回下一个字符.”和 §7.21.1 第 3 段:EOF
是一个负整数,“由多个函数返回以指示文件结束,即不再有来自流的输入”。)
@rici-- 是的,也许我的语言在这里有点松散。关键是我们甚至不能说调用fgets()
的结果必须看起来像连续调用fgetc()
的结果而不需要一些额外的解释。我同意 NUL
之外的缓冲区应该不被修改似乎是常识,但我仍然看不到标准在哪里说 fgets()
在存储字符之前不能修改缓冲区。
【参考方案1】:
据我所知,在fgets
和fscanf
的情况下,保证这三个调用不会修改buf
之外的最后一个字符加上一个NUL 字符。
标准中最相关的要求在 §7.21.3 第 11 段:
字节输入函数从流中读取字符,就像通过连续调用
fgetc
函数一样。
(“字节输入函数”在 §7.21.1 第 5 段中定义,包括 fgetc、fgets、fprintf、fputc、fputs、fread、fscanf、fwrite、getc、getchar、printf、putc、putchar、puts、scanf , ungetc、vfprintf、vfscanf、vprintf 和 vscanf。)
这解释了数据是如何被读取的,但它并不是 100% 准确地说明读取后如何处理数据。为此,有必要查看各个函数定义(并可能应用一些常识)。
fgets
对于fgets
,我们读到(§7.21.7.2 第 2 段):
在换行符(保留)之后或文件结尾之后不会读取其他字符。
这对我来说似乎很清楚。
fscanf
对于fscanf
,我们需要参考s
转换说明符的描述(第 7.21.6.2 节第 12 段),它简洁地声明它匹配:
一系列非空白字符
但是,我们还有 §7.21.6.2 第 9 段,它描述了导致应用转换说明符的工作:
从流中读取输入项... 输入项定义为最长的输入字符序列,不超过任何指定的字段宽度,并且是匹配输入序列的前缀。输入项之后的第一个字符(如果有)仍然未读。
因此读取必须在第一个空白字符或输入结尾处终止。读取终止后,将附加一个空字符,但不再进行读取(对于此转换说明符)。
fread
fread
函数在 §7.21.8.1 中指定,其中规定(在第 2 段中):
fread
函数从 stream 指向的流中读取ptr
指向的数组,直到nmemb
元素的大小由size
指定。对于每个对象,对fgetc
函数进行size
调用,并按读取顺序存储结果……
这并没有完全说明如果fgetc
返回错误或文件结尾,则读取会提前停止,但第 3 段指出:
fread
函数返回成功读取的元素个数,如果遇到读取错误或文件结束,可能小于nmemb
。
这里的英文有点含糊:自然的解释是返回值可能小于nmemb
,如果提前终止读取就会发生这种情况。对该句子的恶意阅读可能是读取错误或文件结尾可能导致提前终止(与将导致...相反),但是(以英语为母语),我不认为这是对句子意图的准确理解。无论如何,常识表明自然解释是预期的。
在这种情况下值得注意的是,如果size
不是 1,则在对象中间终止的部分读取可能会覆盖整个对象。原始问题中的代码将size
指定为1,因此此异常没有直接关系,但函数调用已
size_t sz = fread(buf, sizeof buf, 1, stream); /* DON'T COPY THIS CODE */
(您有时会看到,尽管我个人不鼓励使用它),那么即使只有一个字节可用,fread
也可以修改整个缓冲区(尽管恕我直言不太可能)。
【讨论】:
谁强烈反对以1
作为对象数量的代码,他们为什么反对?这是在我的书中调用fread()
的一种非常有用的方式。它告诉您读取了多少完整的对象。如果您担心 EOF 或错误,那么使用feof()
或ferror()
来检测问题是合法的。替代方法(使用size_t sz = fread(buf, 1, sizeof(buf), stream);
对大多数目的没有帮助。您必须检查sz != sizeof(buf)
而不是sz != 1
来检测问题。没什么大不了的,但是在您拥有多达20 个对象的情况下,您必须更加努力。
我同意你的大部分回答。我不认为“敌对阅读”有任何有效性。唯一小于nmemb
的情况是发生读取错误或 EOF ——“成功读取的元素数量”排除了您尝试的“恶意读取”IMO。
@JonathanLeffler:由于我无法立即找到不鼓励这种用法的杰出人物,因此我编辑以表明 我 不鼓励使用,因为你无法分辨读取了多少字节,标准不要求 any 缓冲区的内容与输入相对应。除了打代码,我不认为测试sz != sizeof buf
有问题。显然,我同意您对 7.21.8.1/3 含义的评估,但非母语人士不止一次向我询问过这个问题,所以我知道其他解释存在于野外。
我认为我们的一致意见多于分歧,尽管有任何吹毛求疵的事情即将发生。如果发生错误,知道读取了多少字节也无济于事;读取失败的对象的状态是不确定的,读取指针的位置也是不确定的。如果您实际上正在读入一个字符(字节)数组,那么您就有一些知识,但是如果您正在读取结构,您就会知道有多少被成功读取,而对它失败的结构的状态一无所知。由于fread()
经常用于读取 POD 结构,因此这一点很重要。
使用 not-1
作为第二个参数通常在您想要读取一定数量的固定大小记录时使用,并丢弃任何部分记录以及有关它的部分程度的信息.我敢猜测,健壮的代码很少会这样做(如果发现部分记录,至少希望能够报告出了什么问题)以上是关于C 规范的哪一部分(如果有)指定 C 标准库输入函数使缓冲区的后半部分保持不变?的主要内容,如果未能解决你的问题,请参考以下文章