getchar() 即使在后续调用后仍会继续返回 EOF,但 read() 系统调用似乎“清除”了 stdin。这背后的原因是什么?

getchar() keeps returning EOF even after subsequent calls but read() system calls seem to "clear" the stdin. What are the reasons behind this?

提问人:Kode1000 提问时间:4/29/2022 最后编辑:chqrlieKode1000 更新时间:4/30/2022 访问量:195

问:

char buff[1];

int main() {
    int c;
    c = getchar();
    printf("%d\n", c); //output -1

    c = getchar();
    printf("%d\n", c); // output -1

    int res;

    //here I get a prompt for input. What happened to EOF ?
 
    while ((res = read(0, buff, 1)) > 0) { 
        printf("Hello\n");
    }

    while ((res = read(0, buff, 1)) > 0) {
        printf("Hello\n");
    }

    return 0;
}

代码中带有注释行的最终输出是简单地键入(在 macOS 上)的结果。Ctrl-DEOF

我对 的行为有点困惑,尤其是与 相比。getchar()read

  1. 循环内的系统调用不应该也返回吗?他们为什么提示用户?有没有发生某种清除?readwhileEOFstdin

  2. 考虑到在后台使用系统调用,为什么它们的行为会有所不同?难道不应该是“唯一”的,条件是共享的吗?getchar()readstdinEOF

  3. 为什么在下面的代码中,当给定输入时,两个系统调用都返回?readEOFCtrl-D

    int res;

    while ((res = read(0, buff, 1)) > 0) {
        printf("Hello\n");
    }

    while ((res = read(0, buff, 1)) > 0) {
        printf("Hello\n");
    }

我试图找到这一切背后的逻辑。希望有人能弄清楚它到底是什么,它的真实行为方式。EOF

P.S 我正在使用 Mac OS 机器

C UNIX EOF 文件描述符

评论

5赞 Barmar 4/29/2022
stdio 将状态保留在对象中,因此它会记住它何时获得 EOF,并且在您调用 之前不允许再次读取。 没有任何状态,因此如果有新数据可用,您可以读取过去的 EOF。FILEclearerr(stdin)read()
1赞 Jonathan Leffler 4/29/2022
你可以/应该通过在通话后打电话来展示@Barmar所说的话。您也可以尝试一下。getchar()read()clearerr()

答:

3赞 chux - Reinstate Monica 4/29/2022 #1

一旦文件结束指示器设置为 ,就不尝试读取。stdingetchar()

清除文件结束指示符(例如 或其他)重新尝试阅读。clearerr()

该函数等价于 with 参数 。getchargetcstdin

该函数等价于...getcfgetc

如果未设置流指向的输入流的文件结束指示符,并且存在下一个字符,则该函数将获取该字符作为转换为 an 的字符,并推进流的关联文件位置指示器(如果已定义)。fgetcunsigned charint

read()每次仍然尝试阅读。


注意:如果设置了文件结束指示器,则通过 、like 、 读取不会尝试读取。然而,即使设置了错误指示器,仍会发生读取尝试。FILE *stdin

评论

0赞 chqrlie 4/29/2022
为了避免类型范围和 返回的值之间潜在的混淆,我建议使用 byte 而不是 char 来描述 的正返回值。chargetchar()getchar()
0赞 chux - Reinstate Monica 4/29/2022
@chqrlie 您的建议适用于 C 规范 - 报价的来源。在 C 中,byte 和 都可以表示 8 位或更多。然而,在普通语言中,字节意味着 8 位,而 . 这里是更好的短语。unsigned charunsigned charunsigned char
0赞 chqrlie 4/30/2022
fgetc 函数获取该字符的短语仍然有些模棱两可,因为字符不能正确描述控制字节,也不会返回多字节字符编码的单个字节。在这方面,你的措辞比路易斯的回答更精确,问题更少,他确实指定了一个范围 0..255,这是 C 标准规定的最小值,但不是唯一的可能性,他应该在许多地方使用而不是。getchar()unsigned charchar
0赞 Luis Colorado 4/29/2022 #2

MacOs 是 BSD unix 系统的衍生产品。它的 stdio 实现不是来自 GNU 软件,因此它是一个不同的实现。在 EOF 上,当发出系统调用并接收 0 作为读取返回的字符数时,文件描述符被标记为错误,因此,在重置错误条件之前,它不会再次显示错误,这将产生您观察到的行为。在发出下一个调用之前在描述符上使用,一切都会好起来的。你也可以用 glib 来做到这一点,然后,你的程序将在 stdio 的任一实现中运行相同的内容(glib 与 bsd)read(2)read(2)clearerr(stream);FILE *getchar(3)

我试图找到这一切背后的逻辑。希望有人能弄清楚EOF到底是什么,以及它的真实行为方式。

EOF只是一个常量(通常它的值为 -1),它与返回的任何可能值不同 ( 返回 0..255 区间的 an,而不是用于此目的的 char,以扩展 os 可能的字符范围,再增加一个来表示 EOF 条件,但 EOF 不是字符) 文件结束条件由 getchar 函数系列 (getchar, fgetc 等),因为文件条件的结束由返回值 0(返回的字符数为零)发出信号,该值不会映射为某个字符。因此,可能的字符数将扩展到整数,并定义一个新值,以便在达到文件结束条件时返回。这与具有 Ctrl-D 字符(ASCII EOT 或 Cntrl-D,十进制值 4)且不表示 END OF FILE 条件的文件兼容(当您从文件中读取 ASCII EOT 时,它显示为十进制值 4 的普通字符)chargetchar(3)getchar()intread(2)EOF

另一方面,unix tty 实现允许在线输入模式使用特殊字符(Ctrl-D、ASCII EOT/END OF TRANSMISSION、十进制值 4)来指示和结束流到驱动程序。这是一个特殊字符,如 ASCII CR 或 ASCII DEL(在将其提供给程序之前在输入中生成行编辑),在这种情况下,终端只是准备所有输入字符并允许应用程序读取它们(如果没有,则不读取,并且您得到了文件的末尾) 所以认为 Cntrl-D 仅在 unix tty 驱动程序中是特殊的,并且仅在它在规范中工作时才有效模式(线路输入模式)。因此,最后,只有两种方法可以在行模式下将数据输入到程序中:

  • 按下 RETURN 键(这由终端映射到 ASCII CR,终端将其转换为著名字符 ASCII LF)并将 ASCII LF 字符输入到程序中'\n'
  • 按 Ctrl-D 键。这使得终端可以抓取到此刻之前输入的所有内容并将其发送到程序(不添加 Ctrl-D 本身),并且不会向输入缓冲区添加任何字符,这意味着,如果输入缓冲区为空,则不会向程序发送任何内容,并且调用有效地从缓冲区中读取零个字符。read(2)

为了统一,在每种情况下,系统调用通常会阻塞到内核中,直到一个或多个字符可用。只有在文件末尾,它才会取消阻止并向程序返回零个字符。这应该是文件结束指示。许多程序在发出真正的 END OF FILE 信号之前会读取不完整的缓冲区(少于作为参数传递的字符数),因此,几乎每个程序都会进行另一次读取以检查这是否是不完整的读取,或者确实是文件结束指示。read(2)

最后,如果我想将 Cntrl-D 字符作为其本身输入到文件中怎么办?TTY 实现中还有另一个特殊字符,允许您对前面的特殊字符进行转义。在今天的系统中,该字符默认为 Ctrl-V,因此如果您想输入一个特殊字符(甚至 ?Ctrl-V) 您必须在它前面加上 Ctrl-V,因此在文件中输入 Ctrl-D 必须输入 Ctrl-V + Ctrl-D。

评论

0赞 chux - Reinstate Monica 4/29/2022
旁白:。。。“与 getchar(3) 返回的任何可能的 char 值不同”更像是“与 getchar(3) 返回的任何可能的无符号字符值不同,并且是负数EOF
0赞 Luis Colorado 5/21/2022
正如 spacification 所说,getchar() 返回一个 0..255 或 EOF 范围内的 int 值,但它不会强制它为负数......在大多数实现中,它与任何系统调用的错误返回值兼容。但恕我直言,ANSI C 并没有指定为否定。这确实是一个小问题,并且定义为正值(例如 256)有点奇怪,并且可能会破坏传统软件。EOFEOF
0赞 chux - Reinstate Monica 5/23/2022
“但恕我直言,ANSI C 没有指定 EOF 为负数。” --> “ 它扩展为具有类型和负值的整数常量表达式” C17dr § 7.21.1 3EOFint
0赞 Luis Colorado 6/28/2022
@chux-ReinstateMonica,getchar 不返回无符号值。它返回 0..255 或 EOF 范围内的值。EOF 常量的标准中没有定义特殊值,但几乎所有实现都使用 -1 作为 EOF 的值。这里没有人谈论无符号值,因为返回类型是 int