题目:请问下面的程序一共输出多少个“A”?
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
int i;
for(i=0; i<2; i++)
{
fork();
printf("A");
}
return 0;
}
经过分析我们知道是6个,自己画了一张图
如下:
下面的代码呢?
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
int i;
for(i=0; i<2; i++)
{
fork();
printf("A");
}
return 0;
}
最后会输出6个A,结果答案不是6,而是8。
究其原因,主要有两个因素决定最后结果,一个是fork调用的作用,一个是printf标准I/O调用的输出缓冲区在作怪。
一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。
注意,子进程持有的是上述地址空间的“副本”,这意味着父子进程间不共享这些存储空间。
Linux中对文件系统的读写可以通过系统调用read和write来完成,用户写数据时内核首先会把用户数据拷贝到内核高速缓冲中,然后在采取延迟写策略写到文件中去。这两个系统调用是通过文件描述符和输入输出关联,但是这就涉及到需要用户来管理缓冲区,因为不同的缓存区的长度会影响读写的效率。为了克服这个缺陷,后来开发出了标准I/O库,不再通过文件描述符和输入输出关联,而是通过FILE指针和流关联,FILE结构中包括了文件描述符,缓冲区的大小,当调用fopen的时候系统会动态分配一块内存作为缓存区。而printf作为标准I/O中的函数,也有自己的缓冲区。
- 标准I/O对待缓存的数据采用3种不同的策略,全缓冲、行缓冲、无缓冲。
- 对于没有交互的终端,例如块设备文件,系统采用全缓冲;
- 对于标准输入,标准输出这样交互设备采用行缓冲;
- 对于需要立即响应的设备,例如标准错误采用无缓冲;
所以printf调用输出到标准输出将采用行缓冲策略。当i=0时,fork调用后,父子进程同时输出‘A’,此时‘A’并不会立即输出到终端显示屏,而是会暂存在各自的缓冲区内,而当i=1时,再次fork时,由于子进程是父进程的副本,这样这两个进程的孩子进程将得到他们父进程的缓冲区,这就是最后答案会由6到8的原因,
可用下图表示为:
所以,为了防止这种不是我们希望的结果出现,就需要采取相应的措施,对于行缓冲,程序在遇到“\n”,或是EOF,或是缓中区满,或是文件描述符关闭,或是主动flush,或是程序退出,都会把数据刷出缓冲区。
这样如果我们这样输出:
printf(“A\n”);
或者主动洗刷缓冲区
fflush(stdout);
都会把数据刷出缓冲区。