为什么我需要多个EOF(CTRL + Z)字符?

作为一个小背景,我对C编程语言很陌生,因此一直试图通过第二版Kernighan&Ritchie手册中的一些练习。 我确实意识到我可以通过更多地使用标准库来更简洁地处理某些问题,但是我试图尽可能地保持我的有用命令库与书本同步。

如果它有所不同,我正在使用Tiny C编译器(TCC)在Windows XP环境中编译我的源代码并在XP控制台(cmd.exe)中执行二进制文件。

问题 :处理End-of-File (EOF) characters 。 我已经整理了一个小测试案例来说明问题。 该程序似乎处理EOF字符(部分)。 我将尝试用样本输入/输出来演示问题。

 #include  int main() { int character, count; character = 0; character = getchar(); for (count = 0; character != EOF; ++count) { character = getchar(); } printf("Count: %d", count); return 0; } 

示例输入1: abcd^Z[enter] (其中^ Z / CTRL + Z表示EOF字符,[enter]表示Enter键。)

样本输出1: Count: 4 (等待更多输入或在^ C / ^ Z [enter]上正确结束)

样本输入2: abcd^Zefgh

样本输出2: Count: 4 (等待更多输入或在^ C / ^ Z [enter]上正确结束)

如两个示例中所述,在启动^ C / ^ Z [enter]序列之前不输出字符计数。 在启动之前,程序会等待(并确实处理)更多输入。 但是,如示例2中所述,当程序遇到初始^ Z时,它会停止处理该输入行,等待更多输入或在启动^ C / ^ Z [enter]序列时返回正确的计数。

我无法弄清楚为什么程序只是部分处理EOF字符。 在我看来,如果它截断样本2的末尾,它也应该完全脱离循环。 任何想法为什么在识别EOF字符时程序不会立即打印当前计数并退出?

这个答案是unix-ish,但我认为在Windows上发生了类似的现象。 EOF的基本forms是零长度read 。 在交互式输入设备(终端)上,有一种在输入流中具有EOF的特殊机制,但是如果已经有要读取的输入,则它将与该输入一起被消耗(导致非零长度read ),因此从未注意到应用程序。 只有当EOF发生且没有先前输入缓冲时,才能注意到应用程序并对其起作用。

如果您可以访问Linux(或其他* nix)系统,请编写类似的测试程序并在strace下运行它。 观察发生的基础read调用,并且这种否则不直观的行为的原因将是有意义的。

这可以追溯到计算的石器时代。 至少CP / M,可能在早期的DEC操作系统中更长。 CP / M没有存储文件的大小,它只跟踪磁盘扇区的数量,每个128字节。 二进制文件不是问题,程序只有在足够的时候停止读取。 但肯定是文本文件的问题。

因此按照惯例,文本文件的结尾标记有代码0x1a,Control + Z. 由于文本文件的遗留量大于其中的文本数量,因此必须在每一代CRT实现中继续使用。 Windows没有对此发表评论,这纯粹是一个CRT实现细节。 这就是为什么在控制台上键入Ctrl + Z不会做任何特别的事情。 按Enter键后,cmd.exe中的CRT将再次调用旧行为并声明EOF。

我不确定TCC,但在很多(大多数?)情况下,你需要自己或多或少地输入^ Z,因为它被识别为EOF(即,你需要一个序列[输入] ] ^ Z [输入])。

键入^ Z时,Windows不会自动生成EOF; 这只是DOS上的一项约定。 C编译器的运行时必须识别它并设置EOF标志,我猜Tiny C不这样做。

另一方面,^ C被Windows命令环境识别。 它并不一定意味着EOF,我认为它更像是一个中止信号。

我猜标准输入是行缓冲的(它在Unix上)。 DOS有一些比stdio更低级别的getch()getche()函数,所以它们绕过了stdio缓冲。 我不知道如何在Windows上禁用输入缓冲,在Unix上通过将终端设置为非规范模式来完成。