问题
碰到了这样一段代码(经过简化的):
#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
查看结构, 确实与预期相同.
原文链接: https://hujingnb.com/archives/780