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】:

据我所知,在fgetsfscanf 的情况下,保证这三个调用不会修改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 标准库输入函数使缓冲区的后半部分保持不变?的主要内容,如果未能解决你的问题,请参考以下文章

C++开源大全

吐血整理:C++编程语言资源汇总

收藏 | C语言编程规范大全

按值或 C++11 通用参考传递函子? [复制]

C语言中常用的几个头文件及库函数

C 标准库基础 IO 操作总结