1

我正在学习如何使用 fork 创建进程,但我对以下内容感到困惑。这是代码:

int main() {
    int ret = fork();
    // printf("%d\n", ret);
    ret = ret && fork(); /* Here is where I am confused*/
    // print("ret: %d\n", ret);
    if(ret == 0) {
        fork();
    }
    printf("Hello world\n");
    return 1;
}

那么,双与号到底有什么用呢?我用“printf”运行程序来知道究竟是什么值,但它变得更加混乱,因为第一个“printf”的输出是 0,第二个“printf”是“1”。所以我不太确定双&符号在做什么。

感谢您的帮助!

4

8 回答 8

8

它被称为逻辑与运算符。这将评估两个操作数的逻辑与结果。该运算符的属性是:

首先评估左侧操作数,如果它为 TRUE(非零),则评估右侧操作数。如果它也为真,则整个表达式为真,否则为假。另一方面,如果左侧操作数为 FALSE,则根本不计算右侧操作数。可以这样做是因为,由于其中一个操作数为假,因此无论另一个操作数是什么,表达式都为假。这被称为短路

在您的代码中,如果左侧 ifret为真,则仅评估右侧部分,最终调用fork ()系统调用。调用的返回值与 的当前值进行“与”运算ret,并重新分配给ret

基本上它的工作原理是

if (ret == TRUE)
{
  retval = fork ();
  ret = ret && retval;
}

读这个:

成功时,父进程返回子进程的PID,子进程返回0。失败时,在父进程中返回 -1,不创建子进程,并适当设置 errno。

考虑下面的叉树。每个树“节点”显示每个单独语句中执行语句的顺序。一项工作在一条线上。

    (p1)
+--+ret = fork ();
|   printf 1 shows pid
|   && allows         fork (), ret = 1 = pid1 && pid2 
|   printf 2 shows 1    +
|   `if' not entered    |
|   show hello          |
|                       |    (p3)
|                       +--+ ret = 0 = ret && fork () (this is 0 here)
+-----+                      printf 2 shows 0
      |                      `if' is entered
      |                      fork ()
      |                      show hello
      |                            +
      |                            |
      +                            |
     (p2)                          |
    level 1                        +-------+
    print 0 in 1st printf                  |
    && DOES NOT allow fork ()            (p5)
    print 0 in 2st printf             show hello
   `if' entered
    fork () +-----------+
    show hello          |
                        |
                        +
                      (p4)
                    show hello

这是每个过程中发生的事情。

p1 执行fork ()一次,并且在 ret 中有一个 pid(非零)。打印 pid 短路允许执行 fork()。因为这是父进程,所以它返回另一个 pid,它与前一个子 pid 相加,计算结果为 1。因此ret现在包含 1,它在第二个 printf 中打印。与ret1 一样,if不执行。打印你好。

p2 p1 的孩子,所以 ret 有 0。在第一个 printf 中打印 0。短路不允许fork ()通话。ifbody 被输入并被fork ()调用,这使得 (p4)。现在 (p2) 继续打印 Hello。

p3 p1 的子级,所以fork ()return 为 0,与 ret 进行 AND 运算,赋值后为 0。这是在第一个 printf 之后产生的,因此只有第二个 printf 显示 0。 if输入,fork ()执行,这使得 (p5)。现在 p4 继续打印 Hello。

p4ifbody 开始,出来打印 Hello

p5ifbody 开始,出来打印 Hello

上面我已经尝试过表达进程生成树,每个进程中的工作顺序都表达在树上进程“节点”的每一行。边表示生成,边从相应的分叉处开始。

于 2012-02-15T19:16:43.117 回答
6

In C, the double ampersand && is a short-circuit logical AND operation. If you have something like a && b, then this will evaluate as follows: it will return true if both a and b are true, but if a is false then b will never be executed.

As an added example:

int a = 0;
if (a && myfunc(b)) {
    do_something();
}

In this example, short-circuit evaluation guarantees that myfunc(b) is never called. This is because a evaluates to false. This feature permits two useful programming constructs. Firstly, if the first sub-expression checks whether an expensive computation is needed and the check evaluates to false, one can eliminate expensive computation in the second argument. Secondly, it permits a construct where the first expression guarantees a condition without which the second expression may cause a run-time error.

So your code only calls fork() if ret is true. ret is then assigned either 0 (ret is 0) or 1 (both ret and fork() are true).

于 2012-02-15T18:52:54.883 回答
2

这是一个逻辑与,意思是如果ret为真(非零),并且结果fork()为真(非零)将真分配给ret,否则将假(零)分配给ret

由于这个操作符是短路的,只有当它为真时fork()才会被调用。ret

于 2012-02-15T18:50:09.903 回答
1

我认为错误是对 fork() 工作原理的误解。如果您在 UNIX 上,则需要执行“man fork”,因为根据我阅读的内容:

说明 fork() 通过复制调用进程来创建一个新进程。新进程,称为子进程,是调用进程(称为父进程)的完全复制品,

和..

返回值 成功时,在父进程中返回子进程的 PID,在子进程中返回 0。失败时,在父进程中返回 -1,不创建子进程,并适当设置 errno。

我怀疑可能发生的情况是,您可能会看到来自多个分叉进程的输出,而这些输出只会让您感到困惑。您的程序的确切完整输出是什么?

这不太可能是短路问题,因为即使第二个失败,至少第一个 fork 应该成功,因此如果该 fork 成功,您应该从第一个 printfs 中的至少一个获得 pid。

于 2012-02-15T19:01:24.557 回答
0

这是一个惰性逻辑AND函数。AND如果您需要更多信息,请搜索真值表。它是懒惰的,因为如果 ret 为假,fork()则不会被评估,因为任何 ANDedfalse都是 always false

于 2012-02-15T18:51:53.587 回答
0

第一个分叉,ret - fork(),将产生 3 种可能的结果:

  • > 0 => 父母得到孩子的 pid
  • = 0 => 子进程
  • < 0 => 分叉错误

第二个 fork,ret = ret && fork(),只有当 ret 不为零时才会执行 fork()。这可能发生在父级好案例和父级错误案例中。该语句的结果可以是:

  • == 0 => ret 不为零, fork() 为零。
  • != 0 => ret 非零,fork() 非零。

第三个 fork,if (ret == 0) { fork() },只会在 ret 为零时执行。

那么这一切意味着什么呢?第二个分叉似乎很可疑,因为在父级成功或失败的情况下,第一个分叉的返回值可能不为零!我不知道这是否是预期的结果,但这似乎是可疑的。

如果第一个分叉返回在子节点中,第三个分叉就会发生,第二个分叉不会被执行,但第三个会执行。如果第一个 fork 在父上下文中并且第二个 fork 在子上下文中,则第三个 fork 也可以执行。

有人检查这个逻辑,因为我永远不会写这种代码。

于 2012-02-15T19:02:39.737 回答
0

在 C 语言中,&&运算符的行为是这样的:

  1. 计算第一个参数。如果为零,则不计算第二个参数,结果为 0。
  2. 如果第一个参数不为零,则计算第二个参数。如果为零,则答案为 0。否则,为 1。

该函数fork将 0 返回给子进程,并将另一个数字(孩子的 pid)返回给父亲,所以在你的第一个 之后fork,在父进程中ret>0,在子进程中,ret==0。如果您取消注释第一个printf,您将获得 0 和另一个数字。

然后,您运行您的生产线。在孩子中,ret是 0,因此在&&fork 之前停止计算,并且ret保持为 0。在父亲中ret>0,,所以它运行fork(),并创建另一个孩子。在父进程中,fork返回正数,因此ret为 1,在第二个子进程中,fork返回 0,因此ret为 0。因此,如果您仅取消注释第二个printf,您将得到0, 0, 1(可能顺序不同)。

然后,你做if (ret==0) fork();,所以两个孩子(他们ret是 0)每个创建新的进程。现在,您总共有 5 个进程,因此该行将Hello world\n被打印 5 次。

(在搞乱的同时使用输出函数是非常危险的fork——你有 5 个进程写入同一个文件而没有任何锁定,所以你可以获得类似的结果HHHHHeeeeellllllllllooooo wwwwwooooorrrrrlllllddddd\n\n\n\n\n

于 2012-02-15T19:15:01.000 回答
0

这是一个短路逻辑与。如果ret为 0,则执行fork(). 如果没有,它不会。我可以为您介绍代码。

//we fork a child process
int ret = fork();

//ret is 0 if we are in the child process, -1 if fork failed
//otherwise ret is the process id of the child process

//because of this, the fork below executes only within the child process
ret = ret && fork(); /* Here is where I am confused*/

//at this point, if we did fork a process (in the child), then ret is 0 in the child
//then we fork again
if(ret == 0) {
    fork();
}

所以我们有我们的第一个进程,进程 1 执行这段代码。让我们假设所有的分叉都是成功的(但你应该确保检查这个......我认为它应该在现有代码中处理,但它并不那么明显)。

  • 进程 1 分叉,创建一个进程 ID 为 2 的子进程。
  • ret 现在在进程 1 中为 2,在进程 2 中为 0。
  • 在过程 1 中,由于 ret 不为零,因此不会发生分叉。
  • 在进程 2 中,ret 为 0,因此我们 fork 一个进程 ID 为 3 的子进程。
  • 现在进程 1、2 和 3 中的 ret 分别为 2、3 和 0。
  • 现在进程 3 将派生一个新的孩子。
于 2012-02-15T19:00:33.887 回答