【发布时间】: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
为什么这个程序打印“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
一个来自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 不会被执行,因为&& 的第一个操作数是0,所以整个表达式的结果是0,所以执行剩下的操作数没有意义(s) &&。
底部的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
【讨论】:
第一个fork()在调用进程中返回一个非零值(称为p0),在子进程中返回0(称为p1)。
在 p1 中,&& 发生短路,进程调用 printf 并终止。在 p0 中,该过程必须计算表达式的其余部分。然后它再次调用fork(),从而创建一个新的子进程(p2)。
在p0中fork()返回一个非零值,对||进行短路,所以进程调用printf并终止。
在 p2 中,fork() 返回 0,所以 || 的余数必须进行评估,也就是最后一个fork();这导致为 p2 创建一个子级(称为 p3)。
P2 然后执行printf 并终止。
P3 然后执行printf 并终止。
然后执行 4 个printfs。
【讨论】:
&& 的第一个操作数为零,则不计算其他操作数。同样的逻辑,如果|| 的一个操作数是 1,那么其余的操作数不需要求值。这是因为剩下的操作数不能改变逻辑表达式的结果,所以它们不需要被执行,这样我们就节省了时间。现在更好罗娜?顺便说一句,这是个好问题,我不明白为什么会投反对票。你得到了我的 +1。
fork(),但我什至不知道什么是短路。这是学校的问题吗?
对于所有反对者,这是来自一个合并但不同的问题。责怪SO。谢谢。
您可以将问题分解为三行,第一行和最后一行都只是将进程数加倍。
fork() && fork() || fork();
运算符短路了,所以这就是你得到的:
fork()
/ \
0/ \>0
|| fork() && fork()
/\ / \
/ \ 0/ \>0
* * || fork() *
/ \
* *
所以这总共是 4 * 5 = 20 个进程,每个进程打印一行。
注意:如果由于某种原因 fork() 失败(例如,您对进程数有一些限制),它会返回 -1,然后您可以获得不同的结果。
【讨论】:
fork() && fork() || fork();的问题,而这里的问题使用fork() && (fork() || fork());。有一个合并,如此处所述:“meta.stackoverflow.com/questions/281729/…”。您可能想要编辑您的答案,通知未来的读者。
执行fork() && (fork() || fork()),会发生什么
每个fork 给出 2 个进程,其值分别为 pid(父)和 0(子)
第一次分叉:
&& (fork() || fork())
|| 部分 => 打印 forked
|| fork()
forked
forked
forked
总数:4 forked
【讨论】:
我喜欢所有已经提交的答案。也许如果您在 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
【讨论】:
long f1,f2,f3; (f1 = fork()) && ((f2 = fork()) || (f3 = fork()));,然后打印 PID 和三个单独的值。
这段代码:
fork();
fork() && fork() || fork();
fork();
为自己获取 20 个进程,Printf 将执行 20 次。
对于
fork() && fork() || fork();
printf 总共会执行 5 次。
【讨论】:
fork() && fork() || fork();的问题,而这里的问题使用fork() && (fork() || fork());。有一个合并,如此处所述:“meta.stackoverflow.com/questions/281729/…”。您可能想编辑您的答案,通知未来的读者。