Search code examples
cfork

What does (fork() && fork()) || fork() print?


Im trying to understand what this code exactly print

int main(void) {

    pid_t r = (fork() && fork()) || fork();

    if (r) {
        printf("a");
    } else {
        printf("b");
    }
}

In particular I want to understand how many "a" and "b" will be printed. The first solution I found is: 3 "a" and 2 "b" but Im not really sure about it, by the way I thought this could be the explanation:

The first fork() returns 0 to the child process (p1) and a non-zero value to the parent (p0) so the && operator for the children returns 0 and the shortcircuit for || is taken and it needs to be evaluated.

At this point p1 will fork() a child process (p2) with the corresponding return values (0 for p1 and a non-zero for p2). After this operations what follows is that the variable r for p1 is a non-zero value while for p2 is 0. This means that the program will print "a" and "b".

Heading back to p0, since the left part of && is non-zero values, it need to evaluate the right side, so there will be another fork() that will return a non-zero value to p0 and 0 to the child process (p3). Now we need to check what happens with the || operator:

  • p0 comes out with a non-zero value so we don't need to evaluate the right part of ||
    • p0 will have r = non-zero
    • the output will be "b"
  • p3 comes out with 0 and we need to evaluate the right part of the ||
    • there will be the fork() call, another child (p4) will be created with return value = 0 and the r value for p4 will also be 0, while the r value for p3 will be a non-zero, so the print is "a" and "b".

I know that this kind of question has already received a reply but I just want to be sure that my reasoning makes sense.


Solution

  • What we know:

    • From man fork: fork() shall return 0 to the child process and shall return the process ID of the child process to the parent process.
    • PID is always non-zero.
    • || and && are short-circuited.
    • && does not execute the right side when the left side is nonzero.
    • || does not execute the right side when the left side is zero.
    • || is true when one of the sides is nonzero.
    • && is true when both sides are nonzero.
    • false is zero, true is nonzero.

    Ok what happens:

     pid_t r = (fork() && fork()) || fork();
    

    Let's summarize that into a table. Each r = line one fork() is in bold - it means this fork() will be executed. When a child has fork() = 0 that means it is "spawned" - a parent has child's PID then as the return value of fork().

    stage main process child1 child2 child3 child4
    r = (fork() && fork()) || fork()
    fork()= child1pid 0
    r = (child1pid && fork()) || fork() (0 && ignored) || fork()
    fork()= child2pid child3pid 0 0
    r = (child1pid && child2pid) || ignored (0 && ignored) || childpid3 (child1pid && 0) || fork() (0 && ignored) || 0
    fork()= child4pid 0
    r = (child1pid && child2pid) || ignored (0 && ignored) || childpid3 (child1pid && 0) || child4pid (0 && ignored) || 0 (child1pid && 0) || 0
    r = true || ignored false || childpid3 false || child4pid false || 0 false || 0
    r = true true true false false
    print a a a b b

    3 times a and 2 times b will be printed.