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”字符串?

是的,这正是父级启动时打印的内容,带有父级的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>是非标准标题;我将其替换为 <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. 在子级的输出缓冲区中添加一个换行符和“我被 fork 了!:D”。第一行输出出现在终端上;其余部分保存在缓冲区中,因为输出是行缓冲的。
  5. 一切停止 3 秒。在此之后, child 通过main结束时的return正常退出。此时,标准输出缓冲区中的剩余数据被刷新。由于没有换行符,这会将输出位置留在行尾。
  6. 家长来了。
  7. parent 等待 child 完成死亡。
  8. 父级添加了一个换行符,“1345 被 fork 了!”到输出缓冲区。在子级生成的不完整行之后,换行符将“Hello”消息刷新到输出。

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/2530663/

相关文章:

c - 在 C 中为各种事物重复使用相同的缓冲区名称是一种常见的做法吗?

编译器 -march 标志基准测试?

linux - 在 Linux 上安装 GWT 开发者插件

c - 如何使用 execvp——查找文件参数

将 PCM 16 位 LE 转换为 WAV

c - `sysconf()` 和 `getrlimit()` 有什么关系和区别?

linux - 如何使用 Shell 脚本从 Linux 中的 Excel 工作表中读取数据?

无法读取 bash 行生成的文件

linux - 从文本文件中删除注释行

linux - sudo 不工作 - 无法 stat/etc/sudoers : Permission denied