通常,要向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;
}
按原样编译并运行上述程序时,这是事件的时间表:
fread
调用fread
系统调用。read
系统调用返回5。read
再次调用fread
系统调用。read
系统调用返回0。read
返回5。fread
Read 5 bytes. EOF: 1. Error: 0.
。fread
调用fread
系统调用。read
系统调用返回0。read
返回0。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没有这种行为的补丁是否错误?
答案 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;
}
演示该错误:
确切的错误是,如果设置了文件结束流指示符,但是该流不在文件末尾,则glibc的fgetc将返回流中的下一个字符,而不是标准要求的EOF
由于fread
是根据fgetc
定义的,因此这是我最初看到的原因。以前据报道为glibc bug #1190,自2018年2月提交2cc7bad
以来已得到修复,该提交于2018年8月进入glibc 2.28。