【问题标题】:What are all the reasons `fgetc()` might return `EOF`?`fgetc()` 可能返回 `EOF` 的所有原因是什么?
【发布时间】:2022-01-06 23:56:32
【问题描述】:

文件结束输入错误发生时,fgetc()肯定会返回EOF
仅此而已,这是否意味着没有更多数据可用?

FILE *inf = ...;
int ch;
while ((ch = fgetc(inf)) != EOF) {
  ;
}
if (feof(inf)) puts("End-of-file");
else if (ferror(inf)) puts("Error");
else puts("???");

使用feof(), ferror() 进行测试是否足够?

注意:EOF 这里是一个宏,它的结果是一些负的int,通常是-1。它不是文件结尾的同义词。

我发现 some questions and more 与这个问题很接近,但没有一个能列举所有可能性。

【问题讨论】:

  • 我确实期待其他答案(在这个“回答我自己的问题”中不仅仅是我自己的答案)。
  • 另外,我的解释是 feof() 在阅读 eof 时是 ferror() 的补充,但这可能是一个有缺陷的假设。
  • @Neil John Bollinger 回答相关问题可能会有用。

标签: c fgetc


【解决方案1】:

仅此而已,这是否意味着没有更多可用数据?

不,EOF 有更多方法。
EOF 并不一定意味着没有更多数据 - 这取决于。

C 库列出了fgetc() 返回EOF 的三种情况。

如果设置了流的文件结束指示符,或者如果流处于文件结束位置,则设置流的文件结束指示符并且fgetc 函数返回@987654329 @。否则,fgetc 函数从流指向的输入流中返回下一个字符。如果发生读取错误,则设置流的错误指示符并且fgetc 函数返回EOF。 C17dr § 7.21.7.1 3

回想一下每个,例如stdin,都有一个文件结束指示符错误指示符

  • 流刚刚遇到文件结尾

    (最常见)已尝试获取更多数据,但没有。

  • 设置了流的文件结束指示器

    流首先检查其文件结束指示符。如果它看到指标已设置,则返回EOF。不尝试查看是否存在更多数据。某些类型的流将报告EOF,但数据将在之前的EOF 报告之后到达。在 文件结束指示符clearerr() 一样被清除之前,返回仍然是 EOFExample 1Example 2.

  • 输入错误

    错误指示器检查。然而,由于某种原因,该函数未能读取文件结尾以外的数据。一个常见的例子是fputc(stdin)。通常输入错误是持久的。有些不是。可能有更多数据可用。常见的策略是结束输入。

      // Example where ferror() is true, yet fgetc() does not return EOF
      FILE *inf = stdin;
      printf("end-of-file:%d error:%d\n", feof(inf), ferror(inf));
      printf("fputc():%d\n", fputc('?', inf));  // EOF reported
      printf("end-of-file:%d error:%d\n", feof(inf), ferror(inf));
      printf("fgetc():%d\n", fgetc(inf));  // User typed in `A`, 'A' reported
      printf("end-of-file:%d error:%d\n", feof(inf), ferror(inf));
    

    输出

    end-of-file:0 error:0
    fputc():-1
    end-of-file:0 error:1
    fgetc():65
    end-of-file:0 error:1
    

    ferror() 为真时,这并不意味着错误刚刚发生,只是在过去的某个时间发生。

其他情况

  • 明显是EOF,因为不正确地保存为char

    fgetc() 返回一个int,其值在unsigned char 范围内,EOF - 一个负值。
    fgetc() 读取字符代码255,但在char签名 的系统上将其保存为char,这通常会导致char 具有与EOF 相同的值,但没有出现文件结尾。

        FILE *f = fopen("t", "w");
        fputc(EOF & 255, f);
        fclose(f);
        f = fopen("t", "r");
        char ch = fgetc(f); // Should be int ch
        printf ("%d %d\n", ch == EOF, ch);
        printf("end-of-file:%d error:%d\n", feof(f), ferror(f));
        fclose(f);
    

    输出

    1 -1  // ch == EOF !
    end-of-file:0 error:0
    
  • UCHAR_MAX == UINT_MAX 的系统。 稀有

    (我只在一些较旧的图形处理器中遇到过这种情况,C 仍然允许。)在这种情况下,fgetc() 可能会在int 范围之外读取unsigned char,因此将其转换为EOF on函数返回。因此fgetc() 返回一个恰好等于EOF 的字符代码。这在 C 历史上几乎是一个奇怪的现象。主要处理方式是:

      while ((ch = fgetc(inf)) != EOF && !feof(inf) && !ferror(inf)) {
        ;
      }
    

    很少需要这种迂腐的代码。

  • 未定义的行为

    当然,当UB 出现时,一切皆有可能。

          FILE * f = fopen("Some_non_existent_file", "r");
          // Should have tested f == NULL here
          printf("%d\n", fgetc(f) == EOF); // Result may be 1
    

一种处理来自fgetc() 的返回的可靠方法。

FILE *inf = ...;
if (inf) {  // Add test
  int ch; // USE int !

  // Pedantic considerations, usually can be ignored
  #if UCHAR_MAX > INT_MAX
    clearerr(inf); // Clear history of prior flags
    while ((ch = fgetc(inf)) != EOF && !feof(inf) && !ferror(inf)) {
      ;
    }
  #else
    while ((ch = fgetc(inf)) != EOF) {
      ;
    }
  #endif

  if (feof(inf)) puts("End-of-file");
  else puts("Error");

如果代码需要在文件结束错误之后查找数据,请调用clearerr()并重复if()块。

【讨论】:

  • 注意:[f]printf() 可能会重置 errno
  • @wildplasser 确实,“无论是否存在错误,库函数调用都可以将 errno 的值设置为非零,前提是 errno 的使用未记录在功能....”,但这对这里的事情有何影响? errno 不是文件错误指示器
  • 我建议添加这个(我最喜欢的):在 Windows 上以文本模式读取字节 0x1a 设置 EOF 标志。
  • @HolyBlackCat 有用的是添加“在文本模式下在 Windows 上读取字节 0x1a 设置 EOF 标志”一个答案,但我怀疑这是编译器问题,而不是操作系统问题。
  • 我认为 MinGW 和 MSVC 都是这种情况,所以“在 windows 上”是一种简写。 :)
【解决方案2】:

EOF 并不一定意味着“没有更多数据”的另一种情况是(而不是“是”)读取磁带。一个磁带上可以有多个文件,每个文件的结尾都标有 EOF。当您遇到 EOF 时,您使用 clearerr(fp) 重置文件流上的 EOF 和错误状态,然后您可以继续读取磁带上的下一个文件。然而,磁带(在大多数情况下)已经走上了渡渡鸟的道路,所以这几乎不再重要了。

【讨论】:

  • 这也发生在终端上——如果你指示一个 EOF(通过点击系统特定的组合键,通常是 ctrl-D 或 ctrl-Z),从终端读取的程序将得到一个 EOF .如果该程序然后使用 clearerr,它可以从终端读取更多内容。
  • @ChrisDodd — 这取决于您使用的平台和/或您使用的 glibc 版本。没错,在某些(可能很多)Linux 系统上,键入 EOF 指示符(通常是 control-D)不会永久设置 EOF 指示符。在大多数其他 Unix 系统上,一旦您在终端上指示 EOF,您将继续获得 EOF,直到您使用 clearerr() 清除错误和 EOF 指示符。 RHEL 7.4 遭受了我认为的“这个错误”; macOS(我用过的所有版本),没有。
  • 对,但如果你打电话给clearerr,你可以继续从终端阅读
  • 是的,如果您拨打clearerr(),您可以在我知道的所有系统上继续从终端阅读。
  • 当需要归档大量数据并且不经常访问时,磁带仍然在商业环境中大量使用:就每 GB 存储介质的价格而言,磁带胜过硬盘独自的。也就是说,这些天您可能会使用一些供应商提供的软件来访问磁带,而不是直接从 C 访问设备,因此您可能不会再遇到这个问题了。见:en.wikipedia.org/wiki/Linear_Tape-Open
【解决方案3】:

这是一个不为人知的原因:

在 Windows 上,以文本模式读取字节 0x1A 会导致 EOF。

“Windows”是指 MSVC 和 MinGW(所以它可能是微软 CRT 的一个怪癖)。这在 Cygwin 上不会发生。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-11
    • 2013-02-16
    相关资源
    最近更新 更多