pec*_*nie 66 c unix linux printf fork
操作系统:Linux,语言:纯C
我正在学习一般的C编程,以及在特殊情况下在UNIX下进行C编程.
printf()在使用fork()呼叫后,我发现了一个奇怪的(对我来说)函数的行为.
码
#include <stdio.h>
#include <system.h>
int main()
{
    int pid;
    printf( "Hello, my pid is %d", getpid() );
    pid = fork();
    if( pid == 0 )
    {
            printf( "\nI was forked! :D" );
            sleep( 3 );
    }
    else
    {
            waitpid( pid, NULL, 0 );
            printf( "\n%d was forked!", pid );
    }
    return 0;
}
产量
Hello, my pid is 1111
I was forked! :DHello, my pid is 1111
2222 was forked!
为什么第二个"Hello"字符串出现在子输出中?
是的,这正是父母在开始时与父母一起打印的内容pid.
但!如果我们\n在每个字符串的末尾放置一个字符,我们得到预期的输出:
#include <stdio.h>
#include <system.h>
int main()
{
    int pid;
    printf( "Hello, my pid is %d\n", getpid() ); // SIC!!
    pid = fork();
    if( pid == 0 )
    {
            printf( "I was forked! :D" ); // removed the '\n', no matter
            sleep( 3 );
    }
    else
    {
            waitpid( pid, NULL, 0 );
            printf( "\n%d was forked!", pid );
    }
    return 0;
}
输出:
Hello, my pid is 1111
I was forked! :D
2222 was forked!
为什么会这样?这是正确的行为,还是一个错误?
Jon*_*ler 84
我注意到这<system.h>是一个非标准的标题; 我用它代替了,<unistd.h>代码编译干净.
当程序的输出进入终端(屏幕)时,它是行缓冲的.当程序的输出进入管道时,它是完全缓冲的.您可以通过标准C功能setvbuf()和_IOFBF(完全缓冲),_IOLBF(线路缓冲)和_IONBF(无缓冲)模式来控制缓冲模式.
您可以在修改后的程序中通过管道程序的输出来证明这一点,比如说cat.即使printf()字符串末尾有换行符,您也会看到双重信息.如果你直接发送到终端,那么你只会看到一大堆信息.
故事的寓意是fflush(0);在分叉之前要小心调用清空所有I/O缓冲区.
按要求逐行分析(删除括号等 - 以及标记编辑器删除的前导空格):
printf( "Hello, my pid is %d", getpid() );pid = fork();if( pid == 0 )printf( "\nI was forked! :D" );sleep( 3 );elsewaitpid( pid, NULL, 0 );printf( "\n%d was forked!", pid );分析:
pid == 0并执行第4和第5行; 父项具有非零值pid(两个进程之间的少数差异之一 - 从getpid()和返回两个值getppid()).父节点现在通过main末尾的返回正常退出,剩余数据被刷新; 由于末尾仍然没有换行符,因此光标位置位于感叹号之后,并且shell提示符出现在同一行上.
我看到的是:
Osiris-2 JL: ./xx
Hello, my pid is 37290
I was forked! :DHello, my pid is 37290
37291 was forked!Osiris-2 JL: 
Osiris-2 JL: 
PID编号不同 - 但整体外观清晰.在printf()语句末尾添加换行符(这很快就成为标准做法)会大大改变输出:
#include <stdio.h>
#include <unistd.h>
int main()
{
    int pid;
    printf( "Hello, my pid is %d\n", getpid() );
    pid = fork();
    if( pid == 0 )
        printf( "I was forked! :D %d\n", getpid() );
    else
    {
        waitpid( pid, NULL, 0 );
        printf( "%d was forked!\n", pid );
    }
    return 0;
}
我现在得到:
Osiris-2 JL: ./xx
Hello, my pid is 37589
I was forked! :D 37590
37590 was forked!
Osiris-2 JL: ./xx | cat
Hello, my pid is 37594
I was forked! :D 37596
Hello, my pid is 37594
37596 was forked!
Osiris-2 JL:
请注意,当输出到达终端时,它是行缓冲的,因此"Hello"行出现在之前,fork()并且只有一个副本.当输出通过管道输出时cat,它是完全缓冲的,因此在此之前没有任何内容出现,fork()并且两个进程都要在刷新缓冲区中使用"Hello"行.
Jar*_*Par 25
原因是没有\n格式字符串的末尾,该值不会立即打印到屏幕上.相反,它在过程中缓冲.这意味着它直到fork操作之后才会实际打印,因此您打印两次.
添加\n虽然强制缓冲区被刷新并输出到屏幕.这发生在前叉之前,因此只打印一次.
您可以使用该fflush方法强制执行此操作.例如
printf( "Hello, my pid is %d", getpid() );
fflush(stdout);
fork()有效地创建了一个过程的副本.如果在调用之前fork(),它具有缓冲的数据,则父级和子级都将具有相同的缓冲数据.下次每个人都做了一些事情来刷新缓冲区(例如在终端输出的情况下打印换行符),除了该进程产生的任何新输出之外,你还会看到缓冲输出.因此,如果您要在父级和子级中使用stdio,那么您应该fflush在分叉之前确保没有缓冲数据.
通常,孩子仅用于呼叫exec*功能.由于这取代了完整的子进程映像(包括任何缓冲区),因此技术上没有必要,fflush如果这真的是你要在孩子身上做的所有事情.但是,如果可能存在缓冲数据,那么您应该注意如何处理exec失败.特别是,避免使用任何stdio函数将错误打印到stdout或stderr(write正常),然后调用_exit(或_Exit)而不是调用exit或只返回(这将刷新任何缓冲的输出).或者在分叉之前通过冲洗完全避免这个问题.