Jos*_*ica 12 c glibc libc fread eof
通常,为了向连接到Linux终端上的标准输入的程序指示EOF,如果我只按Enter键,则需要按Ctrl + D一次,否则按两次.但我注意到patch命令不同.有了它,如果我按下Enter键,我需要按两次Ctrl + D,否则按三次.(cat | patch相反,如果我在输入任何实际输入之前按下Ctrl + D,它就没有这种奇怪之处.)深入研究patch源代码,我追溯到它的方式循环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;
}
Run Code Online (Sandbox Code Playgroud)
在完全按原样编译和运行上述程序时,这是事件的时间表:
fread.fread调用read系统调用.read系统调用返回5.freadread再次呼叫系统呼叫.read系统调用返回0.fread 返回5.Read 5 bytes. EOF: 1. Error: 0.fread再次调用.fread调用read系统调用.read系统调用返回0.fread 返回0.Read zero bytes. EOF: 1. Error: 0. Exiting.为什么这种读取stdin的方法有这种行为,不像其他程序似乎读取它的方式?这是一个错误patch吗?如何编写这种循环以避免这种行为?
更新:这似乎与libc有关.我最初在Ubuntu 16.04上的glibc 2.23-0ubuntu3上体验过它.@Barmar在评论中指出,它不会发生在macOS上.听到这个之后,我尝试编译同样的程序对抗musl 1.1.9-1,也来自Ubuntu 16.04,它没有这个问题.在MUSL,事件的顺序有步骤12至14取出,这就是为什么它没有问题,但在其他方面一样(除了的无关紧要的细节readv代替read).
现在,问题变成:glibc的行为是错误的,还是假设它的libc不会有这种行为的错误?
我已经设法确认这是由于2.28(提交2cc7bad)之前的glibc版本中的明确错误.C标准的相关引用:
该字节的输入/输出功能 -其执行的输入/输出在本节中所描述的那些功能:[...],
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;
}
Run Code Online (Sandbox Code Playgroud)
为了证明这个bug:
确切的错误是,如果设置了文件结束流指示符,但流不在文件末尾,则glibc的fgetc将返回流中的下一个字符,而不是标准要求的EOF.
由于fread是根据定义fgetc,这是我最初看到的原因.它之前被报道为glibc bug#1190并且自2cc7bad2018年2月提交以来已经修复,2018年8月降至glibc 2.28.