printf函数缓冲区问题

今天本想测试一下僵尸进程,却无意中发现了printf函数的一些问题。我的测试代码如下:

#include 
#include 
#include 
int main(int argc,char *argv[])
{
    pid_t son;
    printf("parent pid:%d",getpid());
    if((son=fork())>0)
    {
        printf("son‘s pid:%d",son);
        sleep(2);
    }
    else if(son==0)
    {
        exit(0);
    }
    else
    {
        perror("fork error");
    }
}

打印出来的结果如下:
parent pid:16019parent pid:16019son pid:16020
发现很奇怪的问题:parent pid:16019重复打印了一次。为什么会出现这种问题呢?对此的解释是printf函数的缓冲区刷新机制。Linux下缓冲区分为三类:
1、全缓冲区:linux下默认为8192字节,在缓冲区满或者显示调用刷新函数后进行IO系统调用操作,普通磁盘文件通常使用全缓冲区访问。
2、行缓冲区:默认大小为128字节,当在遇到换行符或者缓冲区满时,标准IO库执行IO系统调用操作,终端使用行缓冲区。
3、非缓冲区:标准IO库不对字符进行缓存,标准出错流stderr通常是不带缓冲区的。
Linux下缓冲区刷新机制如下:
1、使用fflush(stdout)强制刷新标准输出缓冲区。
2、缓冲区已满。
3、scanf()要在缓冲区里取数据时会先将缓冲区刷新。
4、换行符\n进入缓冲区时。(注意:用换行符刷新缓冲区可能会出现问题,下面会解释)
5.、程序结束时。
综上我们可以解释本程序的异常输出,由于我们在终端打印结果,所以printf使用行缓冲区,接着对比前面所说的缓冲区刷新机制,没有用fflush(stdout),缓冲区没满,没有调用scanf(),没有使用换行符\n,只满足第5条,所以调用两个printf时都没有刷新缓冲区,调用第二个printf时,缓冲区还存着第一个printf的parent pid:16019,所以第二个printf重复打印了parent pid:16019一次,出现最终异常的结果。
需要注意的是换行符\n有时并不能刷新缓冲区,测试方法如下:

#include 
#include 
#include 
int main(int argc,char *argv[])
{
    pid_t son;
    printf("parent pid:%d\n",getpid());
    if((son=fork())>0)
    {
        printf("son‘s pid:%d\n",son);
        sleep(2);
    }
    else if(son==0)
    {
        exit(0);
    }
    else
    {
        perror("fork error");
    }
}

编译生成可执行文件test_zombie,如果直接执行./test_zombie,那么结果正常:
parent pid:16057
son’s pid:16058
而如果按如下方式执行就会出现问题。
./test_zombie > test.txt
查看test.txt中的内容如下:
parent pid:16092
parent pid:16092
son’s pid:16093
可见如果将标准输出重定向到文件后,换行符\n就无法刷新缓冲区了。因为重定向到文件之后printf使用的不是行缓冲区,而是全缓冲区。换行符可以刷新行缓冲区,但不会刷新全缓冲区。因此又出现重复打印的情况。那么三种缓冲区都是在什么情况下使用的呢?
1、标准出错cerr使用非缓冲区,即没有缓冲,立即打印。
2、如果标准输出指向交互式设备,比如终端,那么使用行缓冲区。否则使用全缓冲区,比如将标准输出重定向到了文件时。
注意:行缓冲区和全缓冲区都可以使用fflush(stdout)强制刷新缓冲区。
参考资料:
http://blog.csdn.net/topgunliu/article/details/50532782

你可能感兴趣的:(Linux系统编程)