0

//this is the code mentioned below.

#include <stdio.h>
#include <unistd.h>
int main(){
    fork();
    fork() && fork()||fork();
    fork();
    printf("YES");
    return 0;
}

I don't understand the line 5 of the code. Please can someone clarify as to how many number of fork operations were executed and why? Thanking all in anticipation :D

Aayu
  • 19
  • 4
  • 1
    It may help you to draw a tree of the programs at each of the five `fork` calls. Don't forget that `fork` returns 0 in the child and nonzero in the parent, and that the `&&` and `||` operators both short-circuit. – nanofarad Apr 25 '23 at 17:45
  • `fork()` returns `0` in the child process, non-zero in the parent process. Non-zero is truthy, so in the parent it will execute the second and third `fork()`, none of the children will execute them. – Barmar Apr 25 '23 at 17:45
  • Well, why did you write it that way? Or did you just find it online? Why do you think it's forking 20 times? Did you look up C operators to find what `&&` and `||` do and what their precedence is? Did you run the code to see what it does? – Robert Apr 25 '23 at 17:46
  • The logical operator `&&` and `||` performs [*short-circuit evaluation](https://en.wikipedia.org/wiki/Short-circuit_evaluation). For example `A && B` will evaluate `A`. If it's false then `B` will *not* be evaluated and the result will be false. For `A || B` is `A` is *true* then `B` will not be evaluated and the result will be true. – Some programmer dude Apr 25 '23 at 17:49
  • For values that are numeric, like `fork()` which returns an integer that is either zero (in the child process), positive (in the parent process) or negative (if it fails), you have to remember that only zero is considered false. Everything non-zero is true. – Some programmer dude Apr 25 '23 at 17:50
  • @Robert Don't ask silly questions. He didn't write it, it's an exercise or puzzle he's trying to solve. – Barmar Apr 25 '23 at 17:51
  • 1
    And of course, like any decent book, tutorial or class should have taught you, `fork()` returns *twice*: Once in the old (parent) process, and once in the newly created child process. Look at it as a *fork* along a path. You come to the fork on one single path, which is the old process. The fork continues with two paths. One of the two will be the old (parent process) path, and the other will be the new (child process) path. The result of the `fork()` call tells you which path was taken. – Some programmer dude Apr 25 '23 at 17:51
  • How would one add a equivalent of `printf "PID now = " $$ "\n"` at the top? ( I'm using shell syntax as a p-code). ParentPID as well? That might help the O.P. understand betterr what is going on. Good luck to all. – shellter Apr 25 '23 at 18:25

3 Answers3

6

&& has higher precedence than ||, so

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

is parsed the same as

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

(despite the suspicious spacing implying otherwise).


Short-circuit boolean algrebra dictates the following equivalencies

r = p() || q();     ⇔     r = p();  if ( !r ) r = q();

r = p() && q();     ⇔     r = p();  if ( r ) r = q();

(Not truly equivalent since && and || always return 0 or 1, whereas my version returns 0 or a non-zero value. But that's good enough for us.)

As such,

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

is equivalent to

pid_t r = fork();
if ( r ) r = fork();
if ( !r ) r = fork();

Finally, fork returns zero in the child and non-zero in the parent (ignoring errors).

We have all that we need to work out the flow of the program.

Let's annotate the lines.

fork();                 // A
pid_t r = fork();       // B
if ( r ) r = fork();    // C
if ( !r ) r = fork();   // D
fork();                 // E
printf("YES");          // F
       A       B       C       D       E       F
main...fork....fork....fork............fork     1
       |       |       v               child    2
       |       |       child...fork....fork     3
       |       |               v       child    4
       |       |               child...fork     5
       |       v                       child    6
       |       child...........fork....fork     7
       |                       v       child    8
       |                       child...fork     9
       v                               child   10
       child...fork....fork............fork    11
               |       v               child   12
               |       child...fork....fork    13
               |               v       child   14
               |               child...fork    15
               v                       child   16
               child...........fork....fork    17
                               v       child   18
                               child...fork    19
                                       child   20
ikegami
  • 367,544
  • 15
  • 269
  • 518
1

Due to short-circuiting and precedence (&& higher than ||), in A && B || C, B executes only if A is true, and C executes only if A or B (or both) are false.

Here are the processes and truth table. Red denotes a new child process created by that fork. Grey is the state of the previous fork() return values copied from the parent. Blanks denote where short-circuiting occurred. Assume a 1 would actually be the PID of the child process.

table of return values of each fork

Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
1

I've added some extra code to your program that may help explain the hierarchy.

It's derived from my answer for a similar fork question: Different output when redirecting to a file

Messages printed have extra information:

  1. timestamps relative to the starting execution of the program
  2. The current pid and parent pid for each message [process].
  3. Each pid is printed relative to the original parent.
  4. The original parent is tp
  5. The pid of the parent shell (e.g. bash) that invokes your program is sh
  6. Because the program has no wait calls, the top parent will exit before any children are reaped.
  7. These children become "zombie" processes.
  8. They are reparented to systemd. These are shown with a parent of sd
// fork5.c -- fork test program

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/wait.h>
#include <termios.h>

pid_t pidsh;
pid_t pidtop;

double tsczero;
FILE *xfout;

double
tscgetf(void)
{
    struct timespec ts;
    double sec;

    clock_gettime(CLOCK_MONOTONIC,&ts);

    sec = ts.tv_nsec;
    sec /= 1e9;
    sec += ts.tv_sec;

    sec -= tsczero;

    return sec;
}

char *
tlsbuf(void)
{
    static char buf[8][100];
    static int idx = 0;

    char *bp = buf[idx];
    *bp = 0;

    idx += 1;
    idx %= 8;

    return bp;
}

const char *
pidoff(pid_t pid)
{
    char *buf = tlsbuf();
    char *bp = buf;
    pid_t dif = pid - pidtop;

    do {
        if (pid == pidsh) {
            bp += sprintf(bp,"sh");
            break;
        }

        if (dif == 0) {
            bp += sprintf(bp,"tp");
            break;
        }

        if ((dif >= 0) && (dif < 100)) {
            bp += sprintf(bp,"%2.2d",dif);
            break;
        }

        char proc[100];
        sprintf(proc,"/proc/%d/exe",pid);
        char exe[100];

        ssize_t rlen = readlink(proc,exe,sizeof(exe));
        if (rlen < 0)
            rlen = 0;
        exe[rlen] = 0;

        if (strstr(exe,"systemd") != NULL) {
            bp += sprintf(bp,"sd");
            break;
        }

        bp += sprintf(bp,"%d",pid);
    } while (0);

    return buf;
}

void
print(const char *str)
{

    fprintf(xfout,"[%.9f p:%s pp:%s] %s\n",
        tscgetf(),pidoff(getpid()),pidoff(getppid()),str);
}

int
main(int argc,char **argv)
{
    pid_t pid;

    tsczero = tscgetf();

    // get pid of invoking shell
    pidsh = getppid();

    // get pid of top parent
    pidtop = getpid();

    unlink("log");
    xfout = fopen("log","a");
    setlinebuf(xfout);

    fprintf(xfout,"pidsh=%d pidtop=%d\n",pidsh,pidtop);

    fork();
    fork() && fork() || fork();
    fork();
    print("YES");

    return 0;
}

Here is the log output:

pidsh=2809522 pidtop=2112506
[0.000313518 p:tp pp:sh] YES
[0.000408357 p:05 pp:tp] YES
[0.000514334 p:01 pp:sd] YES
[0.000527882 p:07 pp:01] YES
[0.000574537 p:06 pp:01] YES
[0.000579229 p:02 pp:sd] YES
[0.000606260 p:04 pp:01] YES
[0.000624681 p:03 pp:sd] YES
[0.000814343 p:16 pp:08] YES
[0.000815907 p:12 pp:sd] YES
[0.000815955 p:08 pp:sd] YES
[0.000825911 p:14 pp:sd] YES
[0.000823831 p:15 pp:sd] YES
[0.000834658 p:09 pp:sd] YES
[0.000886670 p:11 pp:sd] YES
[0.001012454 p:17 pp:09] YES
[0.001021875 p:10 pp:sd] YES
[0.001034197 p:19 pp:10] YES
[0.001047726 p:13 pp:sd] YES
[0.001118155 p:18 pp:sd] YES
Craig Estey
  • 30,627
  • 4
  • 24
  • 48