c - 为什么 fread 循环需要额外的 Ctrl+D 来用 glibc 发出 EOF 信号?

标签 c glibc eof fread libc

通常,要在 Linux 终端上向附加到标准输入的程序指示 EOF,如果我只是按 Enter,我需要按一次 Ctrl+D,否则需要按两次。不过,我注意到 patch 命令有所不同。有了它,如果我只是按 Enter,我需要按 Ctrl+D 两次,否则需要按三次。 (使用 cat | patch 就不会出现这种奇怪的情况。另外,如果我在输入任何实际输入之前按 Ctrl+D,它就不会出现这种奇怪的情况。)深入研究 patch 的源代码,我追溯到 the way it loops on fread .这是一个做同样事情的最小程序:

#include <stdio.h>

int main(void) {
    char buf[4096];
    size_t charsread;
    while((charsread = fread(buf, 1, sizeof(buf), stdin)) != 0) {
        printf("Read %zu bytes. EOF: %d. Error: %d.\n", charsread, feof(stdin), ferror(stdin));
    }
    printf("Read zero bytes. EOF: %d. Error: %d. Exiting.\n", feof(stdin), ferror(stdin));
    return 0;
}

当完全按原样编译和运行上述程序时,这里是事件的时间表:

  1. 我的程序调用 fread
  2. fread 调用 read 系统调用。
  3. 我输入“asdf”。
  4. 我按 Enter。
  5. read 系统调用返回 5。
  6. fread 再次调用read 系统调用。
  7. 我按 Ctrl+D。
  8. read 系统调用返回 0。
  9. fread 返回 5。
  10. 我的程序打印 Read 5 bytes。 EOF:1。错误:0。
  11. 我的程序再次调用 fread
  12. fread 调用 read 系统调用。
  13. 我再次按下 Ctrl+D。
  14. read 系统调用返回 0。
  15. fread 返回 0。
  16. 我的程序打印 Read zero bytes。 EOF:1。错误:0。正在退出。

为什么这种读取 stdin 的方式有这种行为,不像其他所有程序似乎读取它的方式?这是补丁中的错误吗?这种循环应该怎么写才能避免这种行为?

更新:这似乎与 libc 有关。我最初是在 Ubuntu 16.04 的 glibc 2.23-0ubuntu3 上体验的。 @Barmar 在评论中指出它不会发生在 macOS 上。听到这个消息后,我尝试针对同样来自 Ubuntu 16.04 的 musl 1.1.9-1 编译相同的程序,但没有出现这个问题。在 musl 上,事件序列删除了第 12 步到第 14 步,这就是为什么它没有问题,但在其他方面是相同的(除了 readv 的不相关细节代替 阅读).

现在,问题变成了:glibc 的行为是错误的,还是 patch 假设它的 libc 不会有这种行为是错误的?

最佳答案

我已经设法确认这是由于 2.28 之前的 glibc 版本中存在一个明确的错误(提交 2cc7bad)。相关引述自the C standard :

The byte input/output functions — those functions described in this subclause that perform input/output: [...], fread

The byte input functions read characters from the stream as if by successive calls to the fgetc function.

If the end-of-file indicator for the stream is set, or if the stream is at end-of-file, the end-of-file indicator for the stream is set and the fgetc function returns EOF. Otherwise, the fgetc function returns the next character from the input stream pointed to by stream.

(强调“或”我的)

以下程序演示了 fgetc 的错误:

#include <stdio.h>

int main(void) {
    while(fgetc(stdin) != EOF) {
        puts("Read and discarded a character from stdin");
    }
    puts("fgetc(stdin) returned EOF");
    if(!feof(stdin)) {
        /* Included only for completeness. Doesn't occur in my testing. */
        puts("Standard violation! After fgetc returned EOF, the end-of-file indicator wasn't set");
        return 1;
    }
    if(fgetc(stdin) != EOF) {
        /* This happens with glibc in my testing. */
        puts("Standard violation! When fgetc was called with the end-of-file indicator set, it didn't return EOF");
        return 1;
    }
    /* This happens with musl in my testing. */
    puts("No standard violation detected");
    return 0;
}

演示错误:

  1. 编译程序并执行
  2. 按Ctrl+D
  3. 回车

确切的错误是,如果设置了文件结束流指示器,但流不在文件结束处,glibc 的 fgetc 将返回流中的下一个字符,而不是标准要求的 EOF .

因为 fread 是根据 fgetc 定义的,所以这是我最初看到的原因。它之前被报告为 glibc bug #1190自提交以来已修复 2cc7bad 2018 年 2 月,2018 年 8 月登陆 glibc 2.28。

关于c - 为什么 fread 循环需要额外的 Ctrl+D 来用 glibc 发出 EOF 信号?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52674057/

相关文章:

c - 为什么函数不能与指针一起使用?

c - 初始化常量结构

c - glibc 从哪里获得它的 unicode 属性数据库?

c - C++ STL 的 C 实现在技术上不可行吗?

c - 在线程中使用参数

c - 使用 scanf %m 的程序不可移植

程序退出时出现 C++ 内存错误

c - Scanf while循环和EOF处理

file - 如何在 Go 中的 io.Reader 上测试 EOF?

c++ - 查找文件末尾的问题?