【问题标题】:Why does this program print "forked!" 4 times?为什么这个程序打印“fork”! 4次?
【发布时间】:2011-10-28 20:19:50
【问题描述】:

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

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

int main(void) {

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

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

【问题讨论】:

    标签: c linux unix fork systems-programming


    【解决方案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.stackoverflow.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.stackoverflow.com/questions/281729/…”。您可能想编辑您的答案,通知未来的读者。
        猜你喜欢
        • 2017-07-30
        • 1970-01-01
        • 1970-01-01
        • 2013-07-23
        • 2020-09-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多