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

现在父进程通过main末尾的return正常退出,并且残留数据被刷新;由于末尾仍然没有换行符,因此光标位置位于感叹号之后,并且 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/47065785/

相关文章:

php - 将控制台输出重定向到浏览器

c - 在 C 中实现一个 shell

c - C中/+和+的区别?

php - 一般错误 : 1 Can't create/write to file errcode:2

linux - 重置局部变量的值

linux - 无法安装android studio包

c - WinAPI 管理画笔

c++ - 如何在文本文件中添加自定义行结尾(例如,我想添加一个在所有句点之后结束的行)

c++ - 使用 iptables 共享来自两个不同连接的 Internet 连接

c - C 编程中如何调用变量指定 printf 的格式