c - "fork()"后printf异常

标签 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 == 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”字符串出现在 child 的输出中?

是的,这正是父级启动时打印的内容,带有父级的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!

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

最佳答案

我注意到 <system.h>是非标准 header ;我将其替换为 <unistd.h>并且代码编译得很干净。

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

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

这个故事的寓意是要小心调用fflush(0);在 fork 之前清空所有 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. 将“你好,我的 pid 是 1234”复制到标准输出的缓冲区中。因为末尾没有换行符并且输出以行缓冲模式(或全缓冲模式)运行,所以终端上没有任何显示。
  2. 为我们提供两个独立的进程,在标准输出缓冲区中使用完全相同的 Material 。
  3. child 有pid == 0并执行第 4 行和第 5 行;父级的 pid 具有非零值(这两个进程之间的少数差异之一 - 来自 getpid()getppid() 的返回值还有两个)。
  4. 添加一个换行符和“I was forked! :D”到 child 的输出缓冲区。第一行输出出现在终端上;其余部分保存在缓冲区中,因为输出是行缓冲的。
  5. 一切都停止了 3 秒。这之后子进程通过main末尾的return正常退出。那时,标准输出缓冲区中的残留数据被刷新。由于没有换行符,因此将输出位置留在行尾。
  6. 家长来了。
  7. parent 等待 child 结束死亡。
  8. parent 添加了一个换行符和“1345 被 fork 了!”到输出缓冲区。换行符将“Hello”消息刷新到输出,在 child 生成的不完整行之后。

parent现在通过main末尾的return正常退出,残留数据被flush;由于最后还是没有换行符,光标位置在感叹号之后,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”行。

关于c - "fork()"后printf异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49813401/

相关文章:

linux - 使用 Linux 删除短于 4 个字符的单词

linux - BASH getopts 可选参数

bash - UNIX:使用 egrep 或 sed 查找字符串第一次出现的行?

c - 为什么 C 代码会出现段错误?

python - 如何在本地复制干净的 Google Colab 环境?

linux - 使用 awk 进行多重搜索 - 模式 + 算术条件

c++ - 从 Qt Linux 应用程序连接到 MS SQLServer

c - 编写 GTK 应用程序的首选方法是什么?

c - Ruby-FFI 在包装以大写字母开头的函数时生成常量?

c - 从结构队列中删除所有节点