C “之后的printf异常”;fork();

C “之后的printf异常”;fork();,c,linux,unix,printf,fork,C,Linux,Unix,Printf,Fork,操作系统:Linux,语言:纯C 我将继续学习一般的C编程,以及在UNIX下的特殊情况下的C编程 在使用fork()调用后,我检测到printf()函数的奇怪行为(对我来说) 代码 #include <stdio.h> #include <system.h> int main() { int pid; printf( "Hello, my pid is %d", getpid() ); pid = fork(); if( pid ==

操作系统:Linux,语言:纯C

我将继续学习一般的C编程,以及在UNIX下的特殊情况下的C编程

在使用
fork()
调用后,我检测到
printf()
函数的奇怪行为(对我来说)

代码

#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”字符串出现在孩子的输出中

是的,它正是父级开始时打印的内容,带有父级的
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;
}

为什么会这样?这是正确的行为还是错误?

原因是格式字符串末尾没有
\n
,值不会立即打印到屏幕上。相反,它是在进程内缓冲的。这意味着直到fork操作之后才真正打印,因此您可以将其打印两次

但是添加
\n
会强制刷新缓冲区并将其输出到屏幕。这发生在fork之前,因此只打印一次

您可以使用
fflush
方法强制执行此操作。比如说

printf( "Hello, my pid is %d", getpid() );
fflush(stdout);
我注意到
是一个非标准标题;我用
替换了它,代码编译得很干净

当程序输出到终端(屏幕)时,它是行缓冲的。当程序的输出进入管道时,它将被完全缓冲。您可以通过标准C函数
setvbuf()
\u IOFBF
(完全缓冲)、
\u IOLBF
(行缓冲)和
\u iobf
(无缓冲)模式来控制缓冲模式

您可以在修改后的程序中演示这一点,方法是将程序的输出传输到,例如,
cat
。即使在
printf()
字符串末尾有换行符,您也会看到双重信息。如果你直接发送到终端,那么你只会看到一大堆信息

这个故事的寓意是小心地调用
fflush(0)在分叉之前清空所有I/O缓冲区


按要求逐行分析(大括号等已删除,前导空格已由标记编辑器删除):

  • printf(“您好,我的pid是%d”,getpid())
  • pid=fork()
  • if(pid==0)
  • printf(“\nI被分叉了!:D”)
  • sleep(3)
  • else
  • waitpid(pid,NULL,0)
  • printf(“\n%d被分叉!”,pid)
  • 分析:

  • 将“你好,我的pid是1234”复制到标准输出的缓冲区中。由于末尾没有换行符,且输出以行缓冲模式(或完全缓冲模式)运行,因此终端上不会显示任何内容
  • 给我们两个独立的过程,在标准输出缓冲区中使用完全相同的材料
  • 子项具有
    pid==0
    ,并执行第4行和第5行;父进程的
    pid
    值为非零值(这两个进程之间为数不多的差异之一-从
    getpid()
    getppid()
    返回的值是另外两个)
  • 在子对象的输出缓冲区中添加换行符和“I was forked!:D”。输出的第一行出现在终端上;其余部分保留在缓冲区中,因为输出是行缓冲的
  • 一切暂停3秒钟。在此之后,子进程通过主进程末尾的返回正常退出。此时,将刷新标准输出缓冲区中的剩余数据。由于没有换行符,因此将输出位置保留在行尾
  • 父母来了
  • 父母等待孩子死去
  • 父级向输出缓冲区添加一个换行符和“1345已分叉!”。换行符将“Hello”消息刷新到输出中,位于子项生成的不完整行之后
  • 父级现在通过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;
    }
    
    注意,当输出到达终端时,它是行缓冲的,因此“Hello”行出现在
    fork()
    之前,只有一个副本。当输出通过管道传输到
    cat
    时,它会被完全缓冲,因此
    fork()
    之前不会显示任何内容,并且两个进程在缓冲区中都有要刷新的“Hello”行。

    fork()
    有效地创建了进程的副本。如果在调用
    fork()
    之前,它有缓冲的数据,那么父级和子级都将有相同的缓冲数据。下一次,当它们中的每一个执行刷新其缓冲区的操作(例如在终端输出的情况下打印换行符)时,除了该进程生成的任何新输出之外,您还将看到该缓冲输出。因此,如果要在父级和子级中使用stdio,那么在分叉之前应该
    fflush
    ,以确保没有缓冲数据

    通常,子函数仅用于调用
    exec*
    函数。因为它替换了完整的子进程映像(包括任何缓冲区),所以从技术上讲,如果您真的要在子进程中执行所有操作,就不需要
    fflush
    。但是,如果可能存在缓冲数据,那么在处理exec故障时应小心。特别是,避免使用任何stdio函数将错误打印到stdout或stderr(
    write
    正常),然后调用
    \u exit
    (或
    \u exit
    )而不是调用
    exit
    或jus
    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: 
    
    #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: