为啥这个程序打印“fork”! 4次?

Posted

技术标签:

【中文标题】为啥这个程序打印“fork”! 4次?【英文标题】:Why does this program print "forked!" 4 times?为什么这个程序打印“fork”! 4次? 【发布时间】:2014-12-30 05:28:28 【问题描述】:

为什么这个程序打印“forked!” 4次?

#include <stdio.h>
#include <unistd.h>

int main(void) 

  fork() && (fork() || fork());

  printf("forked!\n");
  return 0;

【问题讨论】:

【参考方案1】:

一个来自main(),另外三个来自每个fork()

请注意,所有三个forks() 都将被执行。你可能想看看ref:

返回值

成功完成后,fork() 将返回 0 给子进程,并将子进程的进程 ID 返回给父进程。两个进程都应继续从 fork() 函数执行。否则返回-1给父进程,不创建子进程,设置errno表示错误。

请注意,进程 ID 不能为零,如 here 所述。


那么到底发生了什么?

我们有:

fork() && (fork() || fork());

所以第一个fork() 将返回其非零进程ID 给父进程,而它会返回0 给子进程。这意味着逻辑表达式的第一个 fork 将在父进程中被评估为 true,而在子进程中它将被评估为 false,并且由于 Short circuit evaluation,它不会调用剩余的两个 fork()s。

所以,现在我们知道这将至少得到两张打印(一张来自主要的,一张来自第一张fork())。

现在,父进程中的第二个fork() 将被执行,它执行并向父进程返回一个非零值,在子进程中返回一个零值。

所以现在,父进程不会继续执行到最后一个fork()(由于短路),而子进程将执行最后一个fork,因为||的第一个操作数是0。

这意味着我们将获得另外两个打印件。

因此,我们总共得到了四张照片。


短路

这里,短路基本上意味着如果 && 的第一个操作数为零,则其他操作数不被评估。在相同的逻辑中,如果 || 的操作数为 1,则其余操作数不需要计算。这是因为剩下的操作数不能改变逻辑表达式的结果,所以不需要执行,这样可以节省时间。

请参阅下面的示例。


流程

请记住,父进程会创建子进程,而子进程又会创建其他进程,依此类推。这导致了进程的层次结构(或者可以说是一棵树)。

考虑到这一点,值得看看这个similar problem 和this 的答案。


描述性图片

我猜我也制作了这个可以提供帮助的图。我假设每次调用返回的 pid 的 fork() 是 3、4 和 5。

请注意,一些fork()s 上面有一个红色的 X,这意味着它们因为逻辑表达式的短路评估而没有被执行。

顶部的fork()s 不会被执行,因为&amp;&amp; 的第一个操作数是0,所以整个表达式的结果是0,所以执行剩下的操作数没有意义(s) &amp;&amp;

底部的fork()不会被执行,因为它是||的第二个操作数,它的第一个操作数是一个非零数,因此表达式的结果已经被计算为真,没有不管第二个操作数是什么。

在下一张图片中,您可以看到流程的层次结构: 根据上图。


短路示例

#include <stdio.h>

int main(void) 

  if(printf("A printf() results in logic true\n"))
    ;//empty body

  if(0 && printf("Short circuiting will not let me execute\n"))
    ;
  else if(0 || printf("I have to be executed\n"))
    ;
  else if(1 || printf("No need for me to get executed\n"))
    ;
  else
  printf("The answer wasn't nonsense after all!\n");

  return 0;

输出:

A printf() results in logic true
I have to be executed

【讨论】:

【参考方案2】:

第一个fork()在调用进程中返回一个非零值(称为p0),在子进程中返回0(称为p1)。

在 p1 中,&amp;&amp; 发生短路,进程调用 printf 并终止。在 p0 中,该过程必须计算表达式的其余部分。然后它再次调用fork(),从而创建一个新的子进程(p2)。

在p0中fork()返回一个非零值,对||进行短路,所以进程调用printf并终止。

在 p2 中,fork() 返回 0,所以 || 的余数必须进行评估,也就是最后一个fork();这导致为 p2 创建一个子项(称为 p3)。

P2 然后执行printf 并终止。

P3 然后执行printf 并终止。

然后执行 4 个printfs。

【讨论】:

你能解释一下“&&短路”吗? @rona-altico,在我的回答中查看有关短路的链接。这基本上意味着如果&amp;&amp; 的第一个操作数为零,则不计算其他操作数。同样的逻辑,如果 || 的一个操作数是 1,那么其余的操作数不需要计算。这是因为剩下的操作数不能改变逻辑表达式的结果,所以它们不需要被执行,这样我们就节省了时间。现在更好罗娜?顺便说一句,这是个好问题,我不明白为什么会投反对票。你得到了我的 +1。 @rona-altico:我很难理解你为什么要使用fork(),但我什至不知道什么是短路。这是学校的问题吗? @G.Samaras:我猜它被否决了,因为它是许多重复项之一,并且在问这里之前甚至没有努力谷歌它。 @G.Samaras:你看不出为什么投反对票?这是一个可怕的问题。有一个完全相同的副本,他没有费心解释他期望的不同输出。为什么这有 41 个赞成票完全超出了我的理解;通常这种事情很快就会达到-3或-4。【参考方案3】:

对于所有反对者,这是来自一个合并但不同的问题。责怪SO。谢谢。

您可以将问题分解为三行,第一行和最后一行都只是将进程数加倍。

fork() && fork() || fork();

运算符短路了,所以这就是你得到的:

       fork()
      /      \
    0/        \>0
 || fork()     && fork()
     /\            /   \
    /  \         0/     \>0
   *    *     || fork()  *
                /   \
               *     *

所以这总共是 4 * 5 = 20 个进程,每个进程打印一行。

注意:如果由于某种原因 fork() 失败(例如,您对进程数有一些限制),它会返回 -1,然后您可以获得不同的结果。

【讨论】:

感谢@yi_H 的回复和一件事,如果我们的代码中只有这一行,那么输出将分叉 5 次?? 还有fork1() || fork2() && fork3();此语句产生 16 个进程。 嗨@yi_H 我只是混淆了 "fork1() || fork2() && fork3()" 总共有 16 个结果。我的意思是我可以有一个像你上面提到的这样的图表 如果没有任何括号,您可能会错误地解析逻辑树。第一个 fork() 的假 (0) 路径不会导致整个表达式在 && 处短路吗?我认为 && 和 || 的关联性优先级在 C 中是从右到左的,因此从左到右的简单评估可以使子表达式的其余部分短路(在任何包含括号内)。它与 fork() && (fork() || fork()) 相同,这将解释仅从这一行开始的 4 个(不是 5 个)进程,总共 16 个。在 C++ 或 C# 中可能会有所不同,但这个问题是在 C 中。 这回答了使用fork() &amp;&amp; fork() || fork();的问题,而这里的问题使用fork() &amp;&amp; (fork() || fork());。有一个合并,如此处所述:“meta.***.com/questions/281729/…”。您可能想要编辑您的答案,通知未来的读者。【参考方案4】:

执行fork() &amp;&amp; (fork() || fork()),会发生什么

每个fork 给出 2 个进程,其值分别为 pid(父)和 0(子)

第一次分叉:

父返回值为pid not null => 执行&amp;&amp; (fork() || fork()) 第二个 fork 父值是 pid 不是 null 停止执行 || 部分 => 打印 forked second fork child value = 0 => 执行|| fork() 第三个叉父打印forked 第三个叉子打印forked 子返回值为 0 停止执行 && 部分 => 打印 forked

总数:4 forked

【讨论】:

【参考方案5】:

我喜欢所有已经提交的答案。也许如果您在 printf 语句中添加更多变量,您会更容易看到发生了什么。

#include<stdio.h>
#include<unistd.h>

int main()

   long child = fork() && (fork() || fork());
   printf("forked! PID=%ld Child=%ld\n", getpid(), child);
   return 0;

在我的机器上它产生了这个输出:

forked! PID=3694 Child = 0
forked! PID=3696 Child = 0
forked! PID=3693 Child = 1
forked! PID=3695 Child = 1

【讨论】:

每次调用 fork() 返回的值如何?怎么样:long f1,f2,f3; (f1 = fork()) &amp;&amp; ((f2 = fork()) || (f3 = fork()));,然后打印 PID 和三个单独的值。【参考方案6】:

这段代码:

fork();
fork() && fork() || fork();
fork();

为自己获得 20 个进程,Printf 将执行 20 次。

对于

fork() && fork() || fork();

printf 一共会执行 5 次。

【讨论】:

这回答了使用fork() &amp;&amp; fork() || fork();的问题,而这里的问题使用fork() &amp;&amp; (fork() || fork());。有一个合并,如此处所述:“meta.***.com/questions/281729/…”。您可能想要编辑您的答案,通知未来的读者。

以上是关于为啥这个程序打印“fork”! 4次?的主要内容,如果未能解决你的问题,请参考以下文章

<程序编程> 计算上为啥会这样?

为啥分叉我的进程会导致文件被无限读取

execl调用的程序执行了多少次?

为啥我的信号量程序打印 20 秒而不是 10 秒?

为啥 foreach 没有给驱动程序带来任何东西?

为啥我的EXCEL文件第一次打开是空白文档,第二次才能打开?