通常,要在 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;
}
当完全按原样编译和运行上述程序时,这里是事件的时间表:
- 我的程序调用
fread
。 fread
调用read
系统调用。- 我输入“asdf”。
- 我按 Enter。
read
系统调用返回 5。fread
再次调用read
系统调用。- 我按 Ctrl+D。
read
系统调用返回 0。fread
返回 5。- 我的程序打印
Read 5 bytes。 EOF:1。错误:0。
- 我的程序再次调用
fread
。 fread
调用read
系统调用。- 我再次按下 Ctrl+D。
read
系统调用返回 0。fread
返回 0。- 我的程序打印
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 returnsEOF
. Otherwise, thefgetc
function returns the next character from the input stream pointed to bystream
.
(强调“或”我的)
以下程序演示了 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;
}
演示错误:
- 编译程序并执行
- 按Ctrl+D
- 回车
确切的错误是,如果设置了文件结束流指示器,但流不在文件结束处,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/