首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >"fork()“之后的printf异常

"fork()“之后的printf异常
EN

Stack Overflow用户
提问于 2010-03-28 03:37:15
回答 3查看 31.6K关注 0票数 88

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

我正在继续学习一般的C编程,在特殊情况下学习UNIX下的C编程。

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

代码

代码语言:javascript
复制
#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;
}

输出

代码语言:javascript
复制
Hello, my pid is 1111
I was forked! :DHello, my pid is 1111
2222 was forked!

为什么第二个"Hello“字符串出现在孩子的输出中?

是的,这正是父进程启动时打印的内容,带有父进程的pid

但!如果我们在每个字符串的末尾放置一个\n字符,我们将得到预期的输出:

代码语言:javascript
复制
#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;
}

输出

代码语言:javascript
复制
Hello, my pid is 1111
I was forked! :D
2222 was forked!

为什么会发生这种情况?这是正确的行为,还是错误?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2010-03-28 04:45:37

我注意到<system.h>是一个非标准的头文件;我用<unistd.h>替换了它,代码被干净地编译了。

当你的程序输出到终端(屏幕)时,它是行缓冲的。当您的程序输出到管道时,它是完全缓冲的。您可以通过标准C函数setvbuf()_IOFBF (全缓冲)、_IOLBF (行缓冲)和_IONBF (无缓冲)模式来控制缓冲模式。

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

这个故事的寓意是,在派生之前要小心调用fflush(0);来清空所有的I/O缓冲区。

根据要求逐行分析(删除花括号等-并通过标记编辑器删除前导空格):

  1. printf( "Hello, my pid is %d", getpid() );
  2. pid = fork();
  3. if( pid == 0 )
  4. printf( "\nI was forked! :D" );
  5. sleep( 3 );
  6. else
  7. waitpid( pid, NULL, 0 );
  8. printf( "\n%d was forked!", pid );

分析:

  1. 将"Hello,my pid is 1234“复制到缓冲区中以进行标准输出。因为末尾没有换行符,并且输出是以行缓冲模式(或全缓冲模式)运行的,所以在标准输出缓冲区中具有完全相同材料的两个独立进程上不会出现任何东西。
  2. 子进程具有pid == 0并执行第4行和第5行;父进程的pid值为非零值(这是两个进程之间的少数差异之一-来自getpid()getppid()的返回值是另外两个)。
  3. 向子进程的输出缓冲区添加了换行符和"I
  4. !:D“。输出的第一行显示在终端上;由于输出是行buffered.
  5. Everything暂停3秒,因此其余行保存在缓冲区中。在此之后,子对象通过main结尾处的返回正常退出。此时,将刷新stdout缓冲区中的剩余数据。由于没有换行符,这将把输出位置留在一行的末尾。
  6. 父代出现在这里。
  7. 父代等待子代结束死亡。
  8. 父代添加了一个换行符,并说"1345 is forked!“复制到输出缓冲区。在子代生成的不完整行之后,新行将“Hello”消息刷新到输出。

父级现在通过main结尾处的返回正常退出,并刷新剩余数据;由于结尾处仍然没有换行符,因此光标位置在感叹号之后,shell提示出现在同一行上。

我看到的是:

代码语言:javascript
复制
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()语句的末尾添加换行符(这很快就成为标准做法)会极大地改变输出:

代码语言:javascript
复制
#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;
}

我现在得到了:

代码语言:javascript
复制
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”行。

票数 100
EN

Stack Overflow用户

发布于 2010-03-28 03:40:40

原因是在格式字符串的末尾没有\n,值不会立即打印到屏幕上。相反,它是在进程中缓冲的。这意味着直到执行完fork操作之后,它才被实际打印出来,因此您会打印它两次。

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

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

代码语言:javascript
复制
printf( "Hello, my pid is %d", getpid() );
fflush(stdout);
票数 27
EN

Stack Overflow用户

发布于 2010-03-28 19:59:24

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

通常,子对象仅用于调用exec*函数。因为它替换了完整的子进程映像(包括任何缓冲区),所以从技术上讲,如果这是您在子进程中要做的所有事情,那么就没有必要使用fflush。但是,如果可能存在缓冲数据,则应小心处理exec故障。特别是,避免使用任何stdio函数(write可以)将错误输出到stdout或stderr,然后调用_exit (或_Exit),而不是调用exit或仅返回(这将刷新任何缓冲输出)。或者通过在分叉之前刷新来完全避免这个问题。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/2530663

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档