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

Posted

技术标签:

【中文标题】为啥我需要多个 EOF (CTRL+Z) 字符?【英文标题】:Why do I require multiple EOF (CTRL+Z) characters?为什么我需要多个 EOF (CTRL+Z) 字符? 【发布时间】:2011-08-05 00:51:19 【问题描述】:

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

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

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

#include <stdio.h>

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 字符后程序不会立即打印当前计数并退出?

【问题讨论】:

作为更新,我确定 Tiny C 编译器确实可以识别 EOF 字符 ^Z。 ^Z(输入)和 EOF(符号常数)都被识别为值 -1。 另一个更新,我将另一个符号常量 EOT(传输结束)定义为 4。现在,当我针对这个常量进行测试时,通过发出 ^D(识别为值 4),一切都按计划进行。但是,如果将 EOT 定义为 -1,作为 EOF,程序会再次中断。这看起来确实很奇怪,因为 ^Z 被程序识别为值 -1。 您误将^Z 视为一个角色。在终端/控制台级别指定生成 EOF 的任何字符都会在到达您的应用程序之前由另一层解释。在 Windows 上,^D 默认情况下并不特殊,因此您只需获取字节 ^D (4)。 @R.. 是的,我相信你是对的。在进一步思考了这个问题之后,我得出的结论是 ^Z 在单独读取时被识别为-1(可能是因为 cmd 截断了进一步的输入,导致读取长度为零)。我只是在这里猜测,但是在考虑场景 2 时这是有道理的,因为程序在等待进一步输入时只读取“abcd”。似乎 ^Z(不像 ^D 实际代表 ASCII 字符 4)只是虚无/虚无的符号表示。再次感谢您的洞察力。你肯定帮助我巩固了这个概念。 【参考方案1】:

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

【讨论】:

【参考方案2】:

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

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

【讨论】:

【参考方案3】:

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

如果您可以访问 Linux(或其他 *nix)系统,请编写一个类似的测试程序并在 strace 下运行它。观察发生的底层read 调用,这种不直观的行为的原因是有道理的。

【讨论】:

非常感谢您提供的信息。我目前没有运行 *nix 机器,但必须在接下来的几天内设置一台,唯一目的是测试这个假设。但是,如果按照建议使用 EOF 信号,我想知道为什么程序不能继续愉快地读取行中剩下的任何内容(除非 cmd 在程序到达那么远之前在 ^Z 之后截断。) 这可能是我不熟悉的 Windows 的一个特性。 :-)【参考方案4】:

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

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

【讨论】:

是的,^C/CTRL+C 发送一个 SIGINT 信号或导致当前正在运行的进程退出的东西(这就是它无论如何都可以工作的原因)。但是,我仍然不明白为什么 abcd^Zefgh 仅在未处理 EOF 字符时才计算 abcd 。如果是的话,为什么程序会继续循环并收集输入。 @bfisher,我认为现在接受这个答案还为时过早 - 我没有完全回答这个问题,是吗?【参考方案5】:

我不确定 TCC,但在很多(大多数?)情况下,您需要或多或少地单独输入 ^Z 才能将其识别为 EOF(即,您需要一个序列的 [enter]^z[enter])。

【讨论】:

以上是关于为啥我需要多个 EOF (CTRL+Z) 字符?的主要内容,如果未能解决你的问题,请参考以下文章

为啥只有在 Windows 控制台程序中按 Ctrl+Z 时才会终止输入? [复制]

C语言getchar()和EOF

Linux后台进程管理以及ctrl+z(挂起)ctrl+c(中断)ctrl+(退出)和ctrl+d(EOF)的区别(转)

c语言高手请进,关于浮点型变量储存EOF字符的问题

如何在 Windows 10 上的 Git bash 上为标准输入注入 EOF?

linux中Ctrl+D,Ctrl+Z,Ctrl+C