在C代码中如何表示EOF?

67

在C代码中,换行符由"\n"表示。是否存在与文件结尾(EOF)字符相当的等效字符?


21
这个问题的假设是错误的,认为“EOF”是一个字符,但实际上它是一种条件。如果它确实是一个字符,那么它也就不再是结束了,对吧? - Kerrek SB
2
没有EOF字符。EOF是一个越界值,用于指示EOF条件。它不等于任何字符值(如由getc()等读取)。 - wildplasser
5
@Kerrek SB: 你说得对,但请注意过去有些操作系统实际上确实在文件中嵌入了EOF字符,例如CP/M使用Control-Z进行标记。 - Paul R
3
回答问题时,像“这个问题太明显了”这样的回答并不像那些表现出善意并给予指导的回答那么有帮助。这个关于EOF和SOF的问题困扰了我很久,直到我深入研究它。这是一篇好文章,讨论了这个确切的问题,并用代码示例更详细地回答了它... https://ruslanspivak.com/eofnotchar/ - Rich Lysakowski PhD
11个回答

108

在大多数现代操作系统中,EOF不是一个字符。它只是适用于文件流的一种条件,当流的结尾被达到时就会发生。混淆之处在于用户可能通过输入特殊字符(例如Unix、Linux和其他操作系统中的Control-D)来"信号化"控制台输入的EOF,但是该字符并不会被正在运行的程序看到,而是被操作系统捕获,进而向进程发出EOF信号。

注意:在一些非常旧的操作系统中,EOF确实是一个字符,例如CP/M中的Control-Z,但这是一种粗糙的hack,以避免在文件系统目录中维护实际文件长度的开销。


3
C标准并不保证EOF不是一个字符。 - Eric Postpischil
3
C标准确保getchar()等函数的返回值要么是一个有效的字符,要么是一种不同于有效字符代码的特殊代码EOF。EOF是一个整型常量表达式,类型为int且具有负值,由多个函数返回以指示文件末尾,即没有更多的输入流。fgetc函数将[下一个]字符作为无符号字符转换为int获取。因此,在任何sizeof(char) != sizeof(int)的系统上,EOF都不同于任何字符。 - Jonathan Leffler
11
请注意,即使在 Windows 中,如果以文本模式打开文件,则Ctrl-Z会触发EOF条件。微软非常重视与CP/M的向后兼容性。 - Michael Burr
2
@MichaelBurr:你确定那是Windows而不是特定于编译器的stdio实现吗?据我所知,Windows甚至没有“以文本模式打开”的条件。 - Ben Voigt
2
@vercellop:是的,命令解释器具有许多DOS向后兼容性。但是虽然它与Windows捆绑在一起,但它只是一个用户模式工具,不是操作系统的一部分。 - Ben Voigt
显示剩余3条评论

16

EOF 不是一个字符。因为(二进制)文件可以包含任何字符。假设你有一个文件,其中的字节逐渐增加,变成 0 1 2 3 ... 255 然后再次变成 0 1 ... 255,总共 512 个字节。无论你认为哪一个那 256 个可能的字节是 EOF,该文件将被截断。

这就是为什么 getchar() 等返回一个 int。可能的返回值范围是一个 char 可以拥有的,再加上一个真正的 intEOF(在 stdio.h 中定义)。这也是为什么在检查 EOF 之前将返回值转换为 char 将不起作用。

注意,一些协议有 "EOF" "字符"。ASCII 有 "End of Text"、"End of Transmission"、"End of Transmission Block" 和 "End of Medium"。其他答案已经提到了旧操作系统。我自己在 Linux 上输入 ^D,在 Windows 控制台上输入 ^Z 来停止给程序输入。(但通过管道读取的文件可以在任何地方拥有 ^D 和 ^Z 字符,并且仅在它们耗尽字节时才信号 EOF。)C 字符串以字符 '\0' 结尾,但这也意味着它们不能包含字符 '\0'。这就是为什么所有 C 非字符串数据函数都使用一个 char 数组(用于包含数据)和一个 size_t(用于知道数据在哪里结束)。

编辑:C99 标准 §7.19.1.3 规定:

宏是 [...]
EOF
它扩展为一个整数常量表达式,带有类型 int 和负值,由多个函数返回,以表示文件结束,即来自流的没有更多输入;


1
您的编辑没有显示EOF不等于字符值。EOF表示文件结尾并不排除它等于char值的可能性。EOF是负数也不排除它等于char值的可能性。(允许EOF成为字符值有点烦人,但正如我所链接的答案所述,这并不妨碍C实现符合C标准。) - Eric Postpischil
1
这并没有解决问题。那些使用 ((charVar = getchar()) == EOF) 的人将会看到错误的行为。你所说的是,当他们读取那个被提升为 int 类型的 char 值等于 EOF 时,他们可能会得到一个过早的、虚假的 EOF,而不是因为没有任何 char 等于 EOF 而永远循环下去。解决方案仍然是相同的:((intVar = getchar()) == EOF) - aib
你应该说:“C标准不能保证EOF不等于char值。”实际上,即使一个实现使用相同类型的charint,它们对于标准和符合标准的实现仍然是不同的类型。 - aib
1
@Santropedro:是的,答案是错误的。各种标准库函数都将字符作为“unsigned char”转换为“int”返回,因此必须具有非负值,这个值不能等于“EOF”,因为“EOF”是负数。然而,在C标准的定义中,“字符”的一种定义是“适合一个字节的位表示”。许多人使用“char”类型来处理字符,该类型可以是带符号的。(实际上,“fgets”采用“char *”)。那么可能存在一个“char x”的值等于“EOF”,但它可以通过“fputc”和其他函数有效地打印。 - Eric Postpischil
1
@Santropedro:正确回答这个问题的意思是,应该通过使用诸如fgetc之类的函数的返回值来检测EOF,该函数返回一个字符作为unsigned char转换为intEOF。这将适用于除我提供的链接中讨论的奇特假设C实现之外的所有情况。(要编写即使对于那些实现也能工作的代码,请使用feof函数。)但是,不应假定char值不等于EOF - Eric Postpischil
显示剩余2条评论

11

不,EOF不是一个字符,而是文件句柄的一种状态。

虽然ASCII字符集中有表示数据结束的控制字符,但这些字符通常不用来标识文件的结尾。例如EOT(^D),在某些情况下几乎具有相同的信号意义。

当标准C库使用带符号整数返回字符,并将-1用于文件结束时,实际上只是表示发生了错误的信号。我没有可用的C标准,但引用SUSv3:

如果流的文件结束指示器被设置或者流处于文件末尾,则流的文件结束指示器应该被设置,并且fgetc()应该返回EOF。如果读取错误发生,则流的错误指示器应该被设置,fgetc()应该返回EOF,并设置errno以指示错误。


6

我已经阅读了所有评论。有趣的是,当您打印出以下内容时会发生什么:

printf("\nInteger =    %d\n", EOF);             //OUTPUT = -1
printf("Decimal =    %d\n", EOF);               //OUTPUT = -1
printf("Octal =  %o\n", EOF);                   //OUTPUT = 37777777777
printf("Hexadecimal =  %x\n", EOF);             //OUTPUT = ffffffff
printf("Double and float =  %f\n", EOF);        //OUTPUT = 0.000000
printf("Long double =  %Lf\n", EOF);            //OUTPUT = 0.000000
printf("Character =  %c\n", EOF);               //OUTPUT = nothing

正如我们在这里看到的,EOF并不是一个字符(无论如何)。


你遇到UB是因为你使用了错误的格式说明符。EOF既不是浮点型、双精度浮点型也不是长双精度浮点型,因此以浮点类型打印它显然行不通。 - phuclv
@phuclv,你能告诉我UB是什么吗? - carloswm85
1
未定义行为 [当我使用错误的格式说明符时会发生什么?] (https://dev59.com/h3LYa4cB1Zd3GeqPbsqN) - phuclv
1
未定义行为意味着它在C标准中没有定义,但并不意味着从未有理由进行该行为。当您打印double值时,库函数从堆栈读取8个字节,其中最后4个字节是您可以从%x看到的0xFFFFFFFF,并将这8个字节解释为double。它很可能看到一个非常小的非零非规格化值,打印为0.0000000,因为只有6位小数。其他4个字节可能是0x00,但它们可能是任何东西;因此是"未定义行为",您可能会看到其他随机无意义的内容。 - szmoore

3
在Windows(以及MSDOS和CP/M)上,命令解释器识别的EOF字符是0x1a(十进制26,也称为Ctrl+Z或SUB)。
它今天仍然可以被用来标记二进制文件中可读标题的结尾:如果文件以"Some description\x1a"开头,则用户可以使用TYPE命令将文件内容转储到控制台,并且在EOF字符处停止转储,即打印 Some description 并停止,而不是继续打印后面的垃圾。

1

我认为这可能因系统而异,但一种检查方法是只需使用printf

#include <stdio.h>
int main(void)
{
    printf("%d", EOF);
    return 0;
}

我在Windows上执行了这个操作,控制台输出了-1。希望这可以帮到你。


如果eof是一个字符,为什么你要用%d打印呢? - Koray Tugay

1
答案是否定的,但是...
你可能会因为fgets()的行为感到困惑。
来自http://www.cplusplus.com/reference/cstdio/fgets/
从流中读取字符并将它们作为C字符串存储在str中,直到已经读取(num-1)个字符或者遇到换行符或文件结束符,以先发生的为准。

1

EOF的值不能与任何实际字符混淆。

如果 a= getchar(),那么我们必须声明 a 足够大,以容纳 getchar() 返回的任何值。我们不能使用 char,因为 a 必须足够大,能够除了字符外还能容纳EOF。


这个答案有歧义。虽然第一部分是正确的,但描述a大小的第二部分很难理解。我编辑了你的帖子以增加一些清晰度。 - Luke Taylor

1

这取决于系统,但通常为-1。请参见此处


1
我一直在研究EOF信号。在Dennis Ritchie的C语言编程书中,它首次出现在介绍putchar()和getchar()命令时。它基本上标志着字符输入的结束。
例如,让我们编写一个程序,获取两个数字输入并打印它们的总和。您会注意到,在每个数字输入后,您按Enter键来标记已完成输入操作的信号。但是,在使用字符字符串时,Enter被读取为另一个字符['\n':换行符]。要标记输入的终止,您需要在一个全新的行中输入^Z(在键盘上按Ctrl + Z),然后再按Enter。这将信号下一行命令执行。
#include <stdio.h>

int main()
{
char c;
int i = 0;
printf("INPUT:\t");
c = getchar();

while (c != EOF)
{
   ++i;
   c = getchar();
   
};

printf("NUMBER OF CHARACTERS %d.", i);

return 0;}

上面的代码用于计算包括换行符和空格符在内的字符数。如果您不想计算换行符,请使用以下代码:
#include <stdio.h>

int main()
{
char c;
int i = 0;
printf("INPUT:\t");
c = getchar();

while (c != EOF)
{
    if (c != '\n')
    {
        ++i;
    }

    c = getchar();
    };

printf("NUMBER OF CHARACTERS %d.", i);

return 0;}. 

现在的主要问题是如何输入内容。很简单: 先写下你想要的所有故事,然后换行并输入^Z,再按一次回车。


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