fgets()是否在短缓冲区兼容的情况下返回NULL?

chu*_*ica 11 c fgets language-lawyer

在单元测试中包含一个函数fgets(),当缓冲区大小时遇到​​意外结果n < 2.显然这样的缓冲区大小是愚蠢的,但测试正在探索极端情况.

简化代码:

#include <error.h>
#include <stdio.h>

void test_fgets(char * restrict s, int n) {
  FILE *stream = stdin;
  s[0] = 42;
  printf("< s:%p n:%d stream:%p\n", s, n, stream);
  char *retval = fgets(s, n, stream);
  printf("> errno:%d feof:%d ferror:%d retval:%p s[0]:%d\n\n",
    errno, feof(stream), ferror(stream), retval, s[0]);
}

int main(void) {
  char s[100];
  test_fgets(s, sizeof s);  // Entered "123\n" and works as expected
  test_fgets(s, 1);         // fgets() --> NULL, feof() --> 0, ferror() --> 0
  test_fgets(s, 0);         // Same as above
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

令人惊讶的是,fgets()收益NULL既不 feof()也不ferror()1.

下面的C规范似乎对这种罕见的情况保持沉默.

问题:

  • 正在恢复NULL,而不设置feof(),也不ferror()合规行为?
  • 可能有不同的结果是合规行为吗?
  • 如果n是1或小于1,它会有所不同吗?

平台:gcc版本4.5.3目标:i686-pc-cygwin

这是C11标准的摘要,重点是我的:

7.21.7.2 fgets功能

与fgets函数读取比指定的字符数少至多一个Ñ [...]

如果成功,fgets函数返回s.如果遇到文件结尾没有字符读入数组,则数组的内容保持不变,并返回空指针.如果在操作期间发生读取错误,则数组内容是不确定的,并返回空指针.

相关文章
如何在fgets中使用feof和ferror(C中的minishell)
在C(Seg-Fault和ferror)中创建shell的问题
fputs(),fgets(),ferror()问题和C++等价物
返回fgets()的值


[编辑]评论答案

@Shafik Yaghmour很好地介绍了整个问题:因为C规范没有提到当它不读取任何数据时也不做什么,也没有写任何数据到swhen(n <= 0)时,它是Undefined Behavior.所以任何合理的响应都应该是可以接受的,比如返回NULL,设置无标志,单独留下缓冲区.

至于应该发生什么时n==1,@ Oliver Matthews的回答和@Matt McNabb的评论表明C规格缺乏清晰度,考虑到缓冲区n == 1.C规范似乎倾向于缓冲区n == 1应该返回缓冲区指针s[0] == '\0',但是不够明确.

Sha*_*our 8

在新版本的 中,行为有所不同glibc, for n == 1,它返回s表示成功,这不是对7.19.7.2 fgets 函数第2段的无理阅读,其中说(在 C99 和 C11 中都是相同的,强调我的):

\n\n
\n

char *fgets(char * 限制 s, int n , FILE * 限制流);

\n\n

fgets 函数从stream 指向的流中最多读取比n \n 指定的字符数少1 的字符到s 指向的数组中。在换行符(保留)之后或文件结束符之后不会读取其他\n 字符。在读入数组的最后一个字符之后立即写入一个空字符。

\n
\n\n

不是很有用,但不违反标准中所说的任何内容,它最多会读取0字符并以空终止。因此,您看到的结果看起来像是在后续版本中修复的错误glibc。它显然也不是文件结尾,也不是第3段中所述的读取错误:

\n\n
\n

[...]如果遇到文件结尾并且没有字符读入数组,则数组的内容保持不变并返回空指针。如果操作过程中发生读取错误,则数组内容不确定并返回空指针。

\n
\n\n

就最终情况而言,n == 0这看起来只是未定义的行为。C99标准草案4. Conformance第2段说(重点是我的):

\n\n
\n

如果 \xe2\x80\x98\xe2\x80\x98shall\xe2\x80\x99\xe2\x80\x99 或 \xe2\x80\x98\xe2\x80\x98shall not\xe2\x80\x99\xe2\x80 \x99 违反了约束之外出现的要求,行为未定义。未定义的行为在本国际标准中另外通过文字 \xe2\x80\x98\xe2\x80\x98undefinedbehavior\xe2\x80\x99\xe2\x80\x99或通过省略任何明确的行为定义来表示。这三者的侧重点没有区别;它们都描述了未定义的\xe2\x80\x98\xe2\x80\x98行为\xe2\x80\x99\xe2\x80\x99。

\n
\n\n

C11 中的措辞相同。最多无法读取-1个字符,并且它既不是文件结束也不是读取错误。所以我们对这种情况下的行为没有明确的定义。看起来像是一个缺陷,但我找不到任何涵盖此问题的缺陷报告。

\n

  • 虽然我同意你的结论,即“test_fgets(s, 0);”是未定义的行为,但我不认为*不可能读取最多-1个字符*。不读取任何字符是读取最多任意负数字符的唯一方法。微妙的语义问题是:负数可以描述*字符数*吗?如果否,则负值的行为未定义,如果是,则存在另一个有趣的极端情况:“test_fgets(s, INT_MIN);”。 (2认同)