如果这篇博客帮助到你,可以请我喝一杯咖啡~
CC BY 4.0 (除特别声明或转载文章外)
我们知道 printf
默认是 行缓冲 。C stdio
函数的缓冲方式有三种:
_IOFBF
全缓冲:当缓冲区为空时,从流读入数据。或者当缓冲区满时,向流写入数据。对磁盘文件通常使用全缓存。_IOLBF
行缓冲:每次从流中读入一行数据或向流中写入一行数据。涉及终端时(如标准输出和标准输入)_IONBF
无缓冲:直接从流中读入数据或直接向流中写入数据,缓冲设置无效。stderr 尽快让用户看到错误。
下面的例子中,我们先调用 printf
再调用 sleep
但是 printf
的数据等到进程结束才会输出
#include<stdio.h>
#include<unistd.h>
int main(void){
printf("maomaochong");
sleep(3);
return 0;
}
这是因为想让 printf
输出的字符串先缓存到了 C 维护的缓冲区中,满足下列一项条件后,才会将缓存写入对应的文件或流中(缓冲刷新):
- 缓冲区填满
- 写入的字符中有
\n
- 调用
fflush
- 调用
scanf
,getchar
等输入函数要从缓冲区中读取数据时,也会将缓冲区内的数据刷新 - 使用
setvbuf
将缓冲方式改为无缓冲 fclose
会刷新所有写缓冲- 程序结束时
注意:网上有文章说 \r
也会导致缓冲区的刷新
Ubuntu 20.04 下测试并不会刷新,测试代码:
printf("123");
fflush(stdout);
printf("\r");
sleep(2);
可以看到 123 先输出,2 秒后输出的 123 才被 \r
清除。
\r
可以理解为回退行首的操作,会覆盖掉这一行之前的输出。
例 1
使用 setvbuf
调整缓冲方式
setvbuf(stdout, NULL, _IONBF, 0);
printf("123");
sleep(2)
了解了这些概念,接下来讲一下我遇到的问题。
这是一个有关重定向原理的实现
下面的程序从标准输入中读取 4 个字节的字符串,然后写入的标准输出:
int main()
{
char buf[1024];
read(STDIN_FILENO, buf, 4);
write(STDOUT_FILENO, buf, 4);
}
❯ ./main
hello
hell#
将 1 号描述符关闭,然后打开一个文件,文件描述符 1 会分配给这个文件。无需修改后面的代码(需要加上 close),程序会变为标准输入读取,写入到从打开的文件中
int main()
{
close(STDOUT_FILENO);
int fd = open("new_file", O_RDWR|O_CREAT, 0644);
if(fd == -1) PRINT_ERR("open");
char buf[1024];
read(STDIN_FILENO, buf, 4);
write(STDOUT_FILENO, buf, 4);
}
❯ ./main
hell
❯ cat new_file
hell#
接下来,我想尝试如果使用 printf
函数会不会也将字符串写入到了文件中:
int main()
{
close(STDOUT_FILENO);
int fd = open("new_file", O_RDWR|O_CREAT, 0644);
if(fd == -1) PRINT_ERR("open");
printf("123\n");
sleep(5);
return 0;
}
接下来就出现了问题,printf
并没有立刻输出 123 ,而是等到进程结束时才输出。
这里我就疑惑了,明明字符串有 \n
,为什么没有立刻刷新缓存?
注意 这里我们没有写 close
,如果我们加上 close
:
int main()
{
close(STDOUT_FILENO);
int fd = open("new_file", O_RDWR|O_CREAT, 0644);
if(fd == -1) PRINT_ERR("open");
printf("123\n");
sleep(5);
close(STDOUT_FILENO);
return 0;
}
则缓冲区内容在程序结束后也不会写入文件,这是因为程序结束前已经把文件描述符关闭了,自然无法写入。
如果我们手动使用 fflush
刷新缓存,则可以立刻看到字符串写入了文件
int main()
{
close(1);
int fd = open("new_file", O_RDWR|O_CREAT, 0644);
if(fd == -1) PRINT_ERR("open");
printf("123\n");
fflush(stdout);
sleep(5);
return 0;
}
文件中没有输出的字符串一定是 C stdio
的缓存没有刷新到操作系统内核中,也就是说 write
函数没有被调用。
我认为可能是
close(1)
操作让stdout
的_IOLBF
的属性失效了,所以我尝试为stdout
重新设置行缓存:
int main()
{
close(1);
int fd = open("new_file", O_RDWR|O_CREAT, 0644);
if(fd == -1) PRINT_ERR("open");
setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
printf("123\n");
sleep(5);
close(fd);
return 0;
}
可以看到 printf 输出再 sleep 前写入了文件中。
这里有必要提一句,在文件上看到的内容不一定写到了磁盘中,也可能还在内核的页缓存中。