这个程序为什么会打印出“forked!”四次?
#include <stdio.h>
#include <unistd.h>
int main(void) {
fork() && (fork() || fork());
printf("forked!\n");
return 0;
}
这个程序为什么会打印出“forked!”四次?
#include <stdio.h>
#include <unistd.h>
int main(void) {
fork() && (fork() || fork());
printf("forked!\n");
return 0;
}
其中一个来自于 main()
,另外三个来自于每个fork()
。
请注意所有三个forks()
都将被执行。您可能想参考ref:
返回值
成功完成后,fork()将向子进程返回0,并向父进程返回子进程的进程ID。两个进程都将继续从fork()函数执行。否则,将向父进程返回-1,不会创建子进程,并设置errno以指示错误。
请注意,进程ID不能为零,如here所述。
那么到底发生了什么?
我们有:
fork() && (fork() || fork());
fork()
将会返回给父进程它的非零进程 id,而返回 0 给子进程。这意味着逻辑表达式的第一个 fork 在父进程中将会被计算为 true,而在子进程中将会被计算为 false,并且由于 短路求值,它不会调用剩下的两个 fork()
。fork()
)。fork()
,它会执行并向父进程返回一个非零值,在子进程中返回一个零值。fork()
(由于短路),而子进程将执行最后一个 fork,因为 ||
的第一个操作数为 0。短路运算
在这里,“短路运算”基本上意味着如果 && 的第一个操作数为零,则不评估其他操作数。根据同样的逻辑,如果 || 的一个操作数为1,则不需要评估其他操作数。这是因为其他操作数不能改变逻辑表达式的结果,所以它们不需要执行,从而节省时间。
请参见下面的示例。
进程
请记住,父进程会创建子进程,子进程又会创建其他进程,如此循环,导致进程之间形成了层次结构(可以说是一棵树)。
图示
我还制作了这张图,希望对您有所帮助。我假设每次调用pid的fork()
返回值分别为3、4和5。
请注意,某些fork()
上方有一个红色的X,这意味着它们由于逻辑表达式的短路评估而未被执行。
位于顶部的fork()
不会被执行,因为运算符&&
的第一个操作数为0,因此整个表达式的结果将为0,因此没有必要执行&&
的其余操作数。
位于底部的fork()
也不会被执行,因为它是||
的第二个操作数,其中第一个操作数是非零数字,因此表达式的结果已经计算为true,无论第二个操作数是什么。
接下来的图片显示了进程层次结构: 基于前面的图示。
短路示例
#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个printf
。
你可以将这个问题分解为三行代码,第一行和最后一行都只是简单地将进程数量加倍。
fork() && fork() || fork();
运算符具有短路特性,因此您将得到以下结果:
fork()
/ \
0/ \>0
|| fork() && fork()
/\ / \
/ \ 0/ \>0
* * || fork() *
/ \
* *
所以总共有4*5=20个进程,每个进程打印一行。
注意:如果由于某些原因fork()失败(例如,您对进程数量进行了限制),它会返回-1,那么您可能会得到不同的结果。
fork() && fork() || fork();
的问题,而这里的问题是使用fork() && (fork() || fork());
。正如在此处讨论的那样进行了合并:“http://meta.stackoverflow.com/questions/281729/my-top-answer-was-deleted-and-my-worst-question-is-still-not-deleted”。您可能需要编辑您的答案,通知未来的读者。 - gsamarasfork() && (fork() || fork())
,会发生什么?fork
命令会产生 2 个进程,分别是 pid(父进程)和 0(子进程)。&& (fork() || fork())
- 第二次执行 fork 的父进程返回值非空的 pid,停止执行 ||
部分 => 输出 forked
- 第二次执行 fork 的子进程返回值为 0,执行 || fork()
- 第三次执行 fork 的父进程输出 forked
- 第三次执行 fork 的子进程输出 forked
- 子进程的返回值是 0,停止执行 && 部分 => 输出 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和三个单独的值。 - Rob Parker这段代码:
fork();
fork() && fork() || fork();
fork();
它会得到20个进程,并且Printf函数会运行20次。
而对于
fork() && fork() || fork();
printf函数将会运行5次。
fork() && fork() || fork();
的问题,而这里的问题使用了 fork() && (fork() || fork());
。正如在这里讨论的那样,进行了合并:"http://meta.stackoverflow.com/questions/281729/my-top-answer-was-deleted-and-my-worst-question-is-still-not-deleted"。你可能想要编辑你的答案,通知未来的读者。 - gsamaras
fork()
,但却不知道什么是短路。这是一个学校的问题吗? - Sebastian Mach