为什么fread循环需要额外的Ctrl + D才能通过glibc发出EOF信号?

时间:2018-10-05 22:36:36

标签: c glibc eof fread libc

通常,要向Linux终端上标准输入所附带的程序指示EOF,如果我只是按Enter键,则需要按Ctrl + D一次,否则按两次。我注意到patch命令是不同的。有了它,如果我只是按Enter键,则需要按Ctrl + D两次,否则按3次。 (相反,执行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. 我的程序调用{​​{1}}。
  2. fread调用fread系统调用。
  3. 我键入“ asdf”。
  4. 我按Enter。
  5. read系统调用返回5。
  6. read再次调用fread系统调用。
  7. 我按Ctrl + D。
  8. read系统调用返回0。
  9. read返回5。
  10. 我的程序打印fread
  11. 我的程序再次调用Read 5 bytes. EOF: 1. Error: 0.
  12. fread调用fread系统调用。
  13. 我再次按Ctrl + D。
  14. read系统调用返回0。
  15. read返回0。
  16. 我的程序打印fread

为什么这种读取stdin的方式具有这种行为,而不同于其他所有程序似乎都读取它的方式?这是Read zero bytes. EOF: 1. Error: 0. Exiting.中的错误吗?应该如何编写这种循环以避免这种行为?

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

现在,问题变成了:glibc的行为是否错误,或者假设其libc没有这种行为的补丁是否错误?

1 个答案:

答案 0 :(得分:6)

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

  

字节输入/输出功能-本节中描述的执行功能   输入/输出:[...],fread

     

字节输入函数从流中读取字符,就像连续读取一样   调用fgetc函数。

     

如果设置了流的文件结束指示符,,如果流在文件末尾,则设置流的文件结束指示符,并且fgetc函数返回EOF。否则,fgetc函数将从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. 按Enter

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

由于fread是根据fgetc定义的,因此这是我最初看到的原因。以前据报道为glibc bug #1190,自2018年2月提交2cc7bad以来已得到修复,该提交于2018年8月进入glibc 2.28。