printf() 和 fork() 产生的输出少于预期

printf() and fork() produce less output than expected

我正在学习分叉进程和内存管理,我遇到了这段代码:

#include <stdio.h>
#include <libc.h>

int main() {
    for (int i = 0; i < 3; ++i) {
        printf("*");
        fflush(stdout);
        fork();
    }

    return 0;
}

根据我的计算,它应该产生 7 颗星:初始进程打印一颗星(1 颗星),分叉,现在我们有 2 个进程,每个进程打印一颗星(1 + 2 = 3),然后他们分叉,所以我们有 4 个进程,每个进程打印一个星号,然后在程序结束时死亡。

因此,我们应该得到 1 + 2 + 4 = 7 星。

然而,在某些 运行 期间我只得到 6 颗星,如下面的屏幕截图所示:

其他时候,当我 运行 程序一切正常时,我得到了预期的 7 星。

到目前为止,我在互联网上搜索并没有找到类似的东西。调试也没有帮助。

那么,是什么导致了这种奇怪的行为,我该如何解决?

当 运行 从终端中输入此代码时,您会注意到您在某些 运行 上丢失的 * 根本没有丢失,而是在程序完成后打印出来。

为了更好地理解,您可以在过程即将完成时打印 PID 而不是星号和额外的一行:

int main() {
    for (int i = 0; i < 3; ++i) {
        printf("pid %d\n", getpid());
        fflush(stdout);
        fork();        
     }

    printf ("pid %d terminates\n", getpid());
    return 0;
}

缺少打印的 运行 上的输出看起来像这样,表明在所有进程完成之前终端已经返回:

pid 31241
pid 31241
pid 31241
pid 31242
pid 31241 terminates
pid 31243
pid 31244 terminates
pid 31242
pid 31245
pid 31242 terminates
pid 31246 terminates
pid 31247 terminates
pid 31245 terminates
user@machine:~/bla$ pid 31243 terminates
pid 31248 terminates

要解决此问题,您可以在返回之前让父进程 wait 为其子进程返回 - 这将导致您的初始进程最后完成并防止终端在仍有子进程时返回 运行在后台宁:

while (wait(NULL) > 0);
printf ("pid %d terminates\n", getpid());

However, during some runs I get only 6 stars, ...
Other times when I run the program everything is good and I get 7 stars as expected.

您无法控制实例打印星星的顺序。 write()(由 fflush() 隐式调用)、fork() 和程序末尾的隐式 exit() 的一个示例可以是:

instance 1: write()  -> First star
instance 1: fork()   -> New instance #2
instance 1: write()  -> Second star
instance 2: write    -> Third star
instance 2: fork()   -> New instance #3
instance 1: fork()   -> New instance #4
instance 1: write()  -> Fourth star
instance 2: write()  -> Fifth star
instance 2: exit()
instance 1: exit()   -> Process #1 has finished
---- Process #1 does no longer exist ---
instance 3: write()  -> Sixth star
instance 3: exit()
instance 4: write()  -> Seventh star
instance 4: exit()

在示例中,打印了第六和第七颗星,但是在第一个过程完成之后。

但是,程序的输出会写入某个“管道”或“虚拟终端”,您的文本编辑器或IDE会将该输出复制到屏幕上。

“初始”进程(实例 #1)一完成,您的 IDE 就认为您的程序已经完成并停止将数据复制到屏幕 - 所以第六颗星和第七颗星该示例是由您的程序编写的,但它们不会被 IDE.

复制到屏幕上

each of them prints a star and then dies as the program ends.

...首先,所有4个实例分叉,然后8个实例结束。