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

4

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

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

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

    return 0;
}

根据我的计算,应该会产生7颗星:初始进程打印了一个星号(1颗星),然后分叉,现在我们有了两个进程,每个进程都打印了一个星号(1 + 2 = 3),然后它们再次分叉,所以我们有了四个进程,每个进程都打印了一个星号,随着程序的结束而死亡。

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

然而,在某些运行中,我只得到了6颗星,就像下面的截图一样:

enter image description here

有时候当我运行程序时,一切都很好,我得到了预期的7颗星。

到目前为止,我在互联网上搜索了很多,但没有找到类似的情况。调试也没有帮助。

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


尝试使用printf("%d\n", i)代替printf("*")。这样可能会给你更多的信息。 - Jabberwocky
尝试在常规独立终端中运行,而不是在CLion中运行。顺便说一下,libc.h是非标准的东西,请使用unistd.h以实现POSIX兼容性。 - n. m.
2个回答

6
当您从终端运行此代码时,您会注意到有些运行中缺少的*实际上并未缺失,而是在程序完成后打印出来。
为了更好地理解,您可以打印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

为了解决这个问题,您可以让父进程在返回之前等待其子进程 - 这将导致您的初始进程最后完成并防止终端在仍有子进程在后台运行时返回:
while (wait(NULL) > 0);
printf ("pid %d terminates\n", getpid());

4

然而,有时我只得到了 6 颗星...
其他时候当我运行程序时一切都很好,按预期得到了 7 颗星。

你无法控制实例打印星号的顺序。一个 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复制到屏幕上。
每个实例都打印一个星号,然后随着程序的结束而终止。
首先,所有4个实例都分叉,然后8个实例结束。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接