printf缓冲区踩坑
问题
碰到了这样一段代码(经过简化的):
#include "stdio.h"
#include "unistd.h"
#include "sys/wait.h"
int main(){
fork();
printf("1\n");
fork();
printf("1\n");
wait(NULL);
return 0;
}
这里我们简单算一下, 结果会打印几个1
嘞?
进程数: 2, line: 6
进程数: 2, line: 7
打印数: 2进程数: 4, line: 8
进程数: 4, line: 9
打印数: 4- 共计打印6次
看一下结果gcc main.c && ./a.out
, 确实是6个.
但是, 到这并没有完, 若将printf
中的\n
去掉, 将结果在一行显示, 就会看到一些不同的内容了:
结果竟然有8个? 这这这, 多出来的两个是哪来的嘞?
揭秘
我们将printf
的数字差异化, 可能就有眉目了.
int main(){
fork();
printf("1");
fork();
printf("2");
wait(NULL);
return 0;
}
其中数字1
, 在我们分析时应该是只打印2次, 但是却打印了4次. 二者的唯一差异就是\n
.
经过查证, 发现是printf
的缓冲区捣的鬼. 简单来说, printf
在调用的时候, 为了提高效率, 并不会立刻将内容输出, 而是先放到缓冲区, 那么什么时候输出呢?
- 标准输出时为
line buffer
. 既行缓冲, 当碰到\n
时输出 - 重定向时为
full buffer
. 当缓冲区满了输出, 一般为1kb
有没有发现什么是与我们这个问题相关的? line buffer
啊, 这不就是是否添加\n
的差别么.
现在应该可以回答, 为什么去掉\n
时, 输出了8个数字了, 当时的状态如下:
输出了8个的原因, 就是printf
将内容写入到了缓冲区中, 而在fork
的时候带着缓冲区一起复制了. 真相大白
扩展
刷新缓冲器
既然知道是缓冲区搞的鬼, 有没有办法在fork
之前清掉缓冲区呢? 有的:
#include "stdio.h"
#include "unistd.h"
#include "sys/wait.h"
int main(){
fork();
printf("1");
// 刷新缓冲区
fflush(stdout);
fork();
printf("2");
fflush(stdout);
wait(NULL);
return 0;
}
这样做的时候, 再fork
之前将缓冲区内容输出并清空, fork
时缓冲区中没有数据, 就没问题啦.
修改缓冲区大小
既然前面发生问题是因为缓冲区, 那么能不能将缓冲区关掉呢? 在输出的时候不进行缓冲不就没问题了么? 确实可以
#include "stdio.h"
#include "unistd.h"
#include "sys/wait.h"
int main(){
// 将标准输出的缓冲区关闭
setbuf(stdout, NULL);
fork();
printf("1");
fork();
printf("2");
wait(NULL);
return 0;
}
full buffer
还记得在查资料的时候, 不光有line buffer
, 还碰到了一个full buffer
. 是在重定向的时候使用的.
之前说, 这段代码直接运行时没有问题的, 正常输出了6个. 是因为缓冲区使用了line buffer
, 每次碰到换行都会刷新缓冲区.
#include "stdio.h"
#include "unistd.h"
#include "sys/wait.h"
int main(){
fork();
printf("1\n");
fork();
printf("2\n");
wait(NULL);
return 0;
}
那么, 如果说不刷新缓冲区, 也就是换成所谓的full buffer
, 不是也会有问题么? 既然重定向结果时为full buffer
, 那重定向一下试试咯:
./a.out > a.log
查看结构, 确实与预期相同.