使用 fgetc 时是否可能将 EOF 与正常字节值混淆?

Is it possible to confuse EOF with a normal byte value when using fgetc?

我们经常这样使用fgetc

int c;
while ((c = fgetc(file)) != EOF)
{
    // do stuff
}

理论上,如果文件中的一个字节的值为 EOF,则此代码有问题 - 它会提前中断循环并且无法处理整个文件。这种情况可能吗?

据我了解,fgetc 将从文件读取的字节内部转换为 unsigned char,然后转换为 int,然后 returns。如果 int 的范围大于 unsigned char.

的范围,这将起作用

如果不是(可能 sizeof(int)=1)会怎样?

我可以通过额外检查使我的代码万无一失:

int c;
for (;;)
{
    c = fgetc(file);
    if (feof(file))
        break;
    // do stuff
}

如果我想要最大的便携性,有必要吗?

C 规范规定 int 必须至少能够保存从 -32767 到 32767 的值。 int 较小的任何平台都是非标准的。

C 规范还指出 EOF 是一个负数 int 常数,并且 fgetc returns "an unsigned char converted to an int" 在成功读取的情况下。由于 unsigned char 不能有负值,因此 EOF 的值可以与从流中读取的任何内容区分开来。*

*见下面的一个漏洞案例,其中这个失败了。


相关标准文本(来自C99):

  • §5.2.4.2.1 整数类型的大小 <limits.h>:

    [The] implementation-defined values shall be equal or greater in magnitude (absolute value) to those shown, with the same sign.

    [...]

    • minimum value for an object of type int

      INT_MIN -32767

    • int 类型对象的最大值

      INT_MAX +32767

  • §7.19.1 <stdio.h> - 简介

    EOF ... expands to an integer constant expression, with type int and a negative value, that is returned by several functions to indicate end-of-file, that is, no more input from a stream

  • §7.19.7.1 fgets 函数

    If the end-of-file indicator for the input stream pointed to by stream is not set and a next character is present, the fgetc function obtains that character as an unsigned char converted to an int and advances the associated file position indicator for the stream (if defined)

如果UCHAR_MAXINT_MAX,没有问题:所有unsigned char值将被转换为非负整数,因此它们将与EOF不同。

现在, 这里有一个有趣的漏洞:如果一个系统有 UCHAR_MAX > INT_MAX,那么该系统在法律上允许转换大于 INT_MAX 的值转换为负整数(根据 §6.3.1.3,将值转换为无法表示该值的有符号类型的结果是 实现定义的 ),使其成为从流中读取的字符可能会转换为 EOF。

具有 CHAR_BIT > 8 的系统确实存在(例如 TI C4x DSP,显然使用 32 位字节),但我不确定它们是否在 EOF 和流函数方面被破坏。

注意:在大多数情况下,chux 的回答是正确的。我留下这个答案是因为我相信评论中的答案和讨论对于理解需要 chux 方法的(罕见)情况很有价值。

EOF 保证具有负值 (C99 7.19.1),正如您提到的,fgetc 在转换为 int 之前将其输入读取为 unsigned char。所以那些人自己保证不能从文件中读取 EOF。

关于您的具体问题:

  • fgetc 无法读取等于 EOF 的合法数据。在文件中,没有签名或未签名之类的东西;它只是位序列。 C 对 1000 1111 的解释不同,这取决于它是被视为有符号的还是无符号的。 fgetc 需要将其视为无符号,因此不能 returned.

    负数(EOF 除外)

    补充:对于unsigned char部分无法读取EOF,但是当它把unsigned char转成int时,如果int不能表示unsigned char的所有值,那么行为是实现-定义 (6.3.1.3).

  • 托管实现的标准要求 fgetc,但允许独立实现省略大部分标准库函数(有些显然是必需的,但我找不到列表。)

  • EOF 不需要 long,因为 fgetc 需要能够 return 而 fgetc returns 是一个整数。

  • 就更改数据而言,它无法准确更改 ,但由于指定 fgetc 从中读取 "characters"该文件而不是字符,即使系统以其他方式将 CHAR_BIT 定义为 16(如果 sizeof(int) == 1,它可能具有的最小值),它也可能一次读取 8 位,因为 INT_MIN <= -32767 和 INT_MAX >= 32767 是 5.2.4.2 所要求的)。在那种情况下,输入 character 将被转换为 unsigned char,它的高位总是为 0。然后它可以转换为int 而不会丢失精度。 (实际上,这不会出现,因为机器通常没有 16 位字节)

是的,c = fgetc(file); if (feof(file)) 确实可以实现最大的可移植性。它通常有效,当 unsigned charint 具有相同数量的唯一值时也是如此。这发生在 charsigned charunsigned charshortunsigned shortintunsigned 都使用相同的位宽和范围宽度。

请注意 feof(file)) 是不够的。代码还应检查 ferror(file).

int c;
for (;;)
{
    c = fgetc(file);
    if (c == EOF) {
      if (feof(file)) break;
      if (ferror(file)) break;
    }
    // do stuff
}