如何更好地检查fgetc()的EOF和错误?

8
我通常采用这种方法。
int c;
while ((c = fgetc(fp))!=EOF)
{
    printf("%c", c);
}

在我看来,使用if (feof(fp))更易读且更健壮。但是在我的回答链接中,chux评论道:

如果 (feof(fp)) 比 int c; while ((c = fgetc(fp))!=EOF) 更健壮。

如此。

    while(1)
    {
        c = fgetc(fp);
        if ( feof(fp) )
        {
            break ;
        }
        printf("%c", c);
    }

第二个版本比第一个版本更加健壮。那么我应该使用哪个版本?请解释一下为什么那个版本更好。

编辑

在问题“为什么 'while ( !feof (file) )' 总是错误的?”中,询问了为什么在控制循环中使用 feof() 总是错误的。但是在if语句中以正确的方式检查 feof() 总是错误的吗?解释是值得赞赏的。


4
请使用第一个版本。第二个版本并不“更健壮”,但需要编写更多代码。它还没有检查错误条件。 - fuz
@user - 不,正确的C++方式是while (cin >> c),而绝对不是while (!cin.eof())。请参见为什么在循环条件中使用iostream::eof被认为是错误的 - Bo Persson
4
@Kninnug 这并不是那个问题的重复。 - ashiquzzaman33
1
请参见 https://dev59.com/tG865IYBdhLWcg3wWtKz#3861506 - user539810
评论未完全复制,因此影响了本次讨论。 - chux - Reinstate Monica
3个回答

5

我通常会像这样编写输入循环:

int c;

while (c = fgetc(fp), c != EOF) {
    /* do something with c here */
}

/* check if EOF came from an end-of-file or an error */
if (ferror(fp)) {
    /* error handling here */
}

通常不应该使用这样的循环条件:

while (!feof(fp)) {
    /* do stuff */
}

或者

for (;;) {
    c = fgetc(fp);
    if (feof(fp))
        break;
}

因为遇到IO错误时会中断程序。在这种情况下,fgetc返回EOF,但是文件结束标志未被设置。你的代码可能会进入无限循环,因为错误条件通常会一直存在,直到采取外部操作。
正确的方法是检查fgetc()的结果:如果等于EOF,那么通常可以停止读取更多数据,因为在出现IO错误和文件结束情况时,通常无法再读取更多数据。然后应该检查是否发生了错误,并采取适当的措施。

8
楼主要求一份解释 - Karoly Horvath
1
这不是一个无用的答案(应该被踩),但并没有真正回答 OP 所寻找的内容。 - edmz

5

2个有趣的问题

ferror()

ferror() 反映了流中错误指示器的状态。此标志在发生罕见的输入错误时被设置,并保持设置直到被清除 - 请参阅 clearerr()。如果读取输入时发生错误,并且稍后的代码再次读取,而不进行清除ferror()仍然报告为真,即使以下读取没有错误。

fgetc() 返回 EOF 可能是由于文件结束(常见情况)或罕见的输入错误。最好使用 feof() 而不是 ferror() 进行区分。 ferror() 可能是由于先前的错误而不是当前的情况 - 当然是因为文件结束。

int c;
c = fgetc(file);
if (c == EOF) {
  if (feof(file)) puts("end-of-file");
  else puts("input error");
}

宽字符: 因为C语言中有一个边界情况,所以测试错误条件的问题就出现了。

fgetc()返回一个int。其值在unsigned charEOF(一些负数)的范围内。

int ch;
while ((ch = fgetc(fp)) != EOF) {
  // do something with ch
}
if (ferror(fp)) Handle_InputError();
if (feof(fp)) Handle_EndOffFile();  // Usually nothing special

然而,C语言允许unsigned char拥有比int正数范围更广的范围。将unsigned char转换为int具有实现定义的行为,可能会导致unsigned char值被转换为负的int,并且与EOF匹配。

这样的平台很少见,不在2015年的主流。大多数平台将具有UCHAR_MAX <= INT_MAX,通常使用上述样式。这些平台很难成为常见的平台,因为像上面那样依赖于EOF与转换为intunsigned char不同的代码量。

如果代码需要处理UCHAR_MAX > INT_MAX的罕见情况,则

int c;
for (;;)
{
    c = fgetc(file);
    if (c == EOF) {
      if (feof(file)) break;
      if (ferror(file)) break;
      // fall through if both if's fail.
    }
    // do stuff with c
}

while ( !feof (file) ) always wrong?中的普遍参考提醒了我们在检查错误之前使用fgetc(in)结果时,代码通常会犯的错误。其他答案中的两个代码都在使用fgetc()结果之前检查错误条件。


第二个代码处理了所有情况,包括可能只适用于某些被遗忘的电脑堆积垃圾的计算机。而第一个则更加常见。


2
建议的改进不是更好,甚至不够稳健。正如这里所解释的那样,如果出现读取错误(没有eof),它会进入无限循环。在这种情况下,feof将返回0,而fgetc将返回EOF。
您的版本没有此问题。此外,您的版本较短、复杂度较低,很标准。

@Ashiquzzaman:这不是重复。 - undur_gongor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接