我正在探索用 C 语言实现 IO 缓冲区。我使用的是 Ubuntu 12.04 / GCC。 对行缓冲的行为方式和触发底层系统调用 read() 的方式有疑问。我对行缓冲区实现的理解是,对 read() 的系统调用是在流中遇到新行时完成的。 但是,如果您查看第一个 read(),它会读取 200 个字节。第一次写入在另一个 read() 完成之前打印 114 个字符,因为第二次写入在流中产生了新行。 但是,如果您查看后续的reads(),它看起来并不是由换行符发生触发的。 无论流中是否出现新行,它始终读取 200 个字节。顺便说一句,在第二次读取完成之前,流中已经有大约 90 个字符,当缓冲区大小为 200 时,如何读取额外的 200 个字符。 注意:如果在 setvbuf 中将缓冲从行缓冲更改为完全缓冲,则输出完全相同。有人可以解释一下为什么在行缓冲如何触发底层系统调用时会出现这种行为吗?
这是代码:
#define KBUFFER 200
#define LBUFFER 100
int main(int argc, char *argv[])
{
FILE *file;
char lbuf[LBUFFER], kbuf[KBUFFER];
if ((file = fopen("testing", "r")) == NULL){
printf("Failed to open testing\n");
exit(1);
}
// Set Line Buffer mode
setvbuf(file, kbuf, _IOLBF, KBUFFER);
while ( (fgets(lbuf, LBUFFER, file) != NULL ) ){
printf("[%s]", lbuf);
}
return 0;
}
测试时每行110个字符;
strace 的输出是:
open("testing", O_RDONLY) = 3
read(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 200) = 200
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7064a98000
write(1, "[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"...,114[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa][aaaaaaaaaaa
) = 114
write(1, "]", 1])
= 1
read(3, "bbbbbbbbbbbbbbbbbbbbb\ncccccccccc"..., 200) = 200
write(1, "[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"..., 114[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb][bbbbbbbbbbb
) = 114
write(1, "][cccccccccccccccccccccccccccccc"..., 115][ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc][ccccccccccc
) = 115
write(1, "]", 1]) = 1
read(3, "dddddddddddddddddddddddddddddddd"..., 200) = 200
write(1, "[ddddddddddddddddddddddddddddddd"..., 114[ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd][ddddddddddd
) = 114
write(1, "][eeeeeeeeeeeeeeeeeeeeeeeeeeeeee"..., 115][eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee][eeeeeeeeeee
) = 115
write(1, "]", 1])
= 1
read(3, "ffffffffffffffffffffffffffffffff"..., 200) = 200
write(1, "[fffffffffffffffffffffffffffffff"..., 114[fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff][fffffffffff
) = 114
write(1, "][gggggggggggggggggggggggggggggg"..., 115][ggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg][ggggggggggg
) = 115
write(1, "]", 1]) = 1
read(3, "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh"..., 200) = 88
write(1, "[hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh"..., 114[hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh][hhhhhhhhhhh
) = 114
write(1, "]", 1]) = 1
read(3, "", 200) = 0
exit_group(0)
对行缓冲的行为方式和触发底层系统调用 read() 的方式有疑问
C 标准库中没有读缓冲模式的概念。
当缓冲区中的数据无法满足FILE
调用时,C 库会使用
read
系统调用读入内部
fread/fgets/...
缓冲区。该读取缓冲区的存在只是为了最大限度地减少
read
系统调用的数量。
ungetc
的存在可能是为了从
FILE
缓冲区中获得更多里程。行缓冲和其他缓冲模式仅适用于输出流。
fgets
正在使用自己的内部缓冲区来存储已读取但尚未返回的数据(因为它在另一行上)。作者可以随意将该缓冲区设置为任意大小。唯一的限制是它不能将超过 200 个字节复制到your 缓冲区中。 我认为 200 字节读取确实与您的缓冲区大小相匹配,这是一种有意的优化,但是,同样,实现可以自由使用任何大小;这对你的代码没有影响。
您不应该
使用通过这种方式获得的任何数据来“优化”您的程序,因为它不可移植,并且可能会在您下次更新 C 库时发生变化。 如果确切的读/写大小对您很重要,那么您应该完全绕过 stdio 并自己调用系统调用。
最后,您提到的行缓冲仅适用于
output流,而不适用于输入。
参数模式决定 如何缓冲流,如下所示:_IOFBF 导致
input/output充分缓冲; _IOLBF 导致 output 进行行缓冲; _IONBF 导致输入/输出不被缓冲。
一些库实现将输入流上的 _IOLBF 规范视为与实践中的 _IOFBF 等效,但标准中未指定此行为。