Why does a program with fork() sometimes print its output multiple times?

In Program 1 Hello world gets printed just once, but when I remove n and run it (Program 2), the output gets printed 8 times. Can someone please explain me the significance of n here and how it affects the fork()?

Program 1

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("hello world...n");
    fork();
    fork();
    fork();
}

Output 1:

hello world...

Program 2

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("hello world...");
    fork();
    fork();
    fork();
}

Output 2:

hello world... hello world...hello world...hello world...hello world...hello world...hello world...hello world...

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

When outputting to standard output using the C library’s printf() function, the output is usually buffered. The buffer is not flushed until you output a newline, call fflush(stdout) or exit the program (not through calling _exit() though). The standard output stream is by default line-buffered in this way when it’s connected to a TTY.

When you fork the process in “Program 2”, the child processes inherits every part of the parent process, including the unflushed output buffer. This effectively copies the unflushed buffer to each child process.

When the process terminates, the buffers are flushed. You start a grand total of eight processes (including the original process), and the unflushed buffer will be flushed at the termination of each individual process.

It’s eight because at each fork() you get twice the number of processes you had before the fork() (since they are unconditional), and you have three of these (23 = 8).

Method 2

It does not affect the fork in any way.

In the first case, you end up with 8 processes with nothing to write, because the output buffer was emptied already (due to the n).

In the second case you still have 8 processes, each one with a buffer containing “Hello world…” and the buffer is written at process end.

Method 3

@Kusalananda explained why the output is repeated. If you are curious why the output is repeated 8 times and not only 4 times (the base program + 3 forks):

int main()
{
    printf("hello world...");
    fork(); // here it creates a copy of itself --> 2 instances
    fork(); // each of the 2 instances creates another copy of itself --> 4 instances
    fork(); // each of the 4 instances creates another copy of itself --> 8 instances
}

Method 4

The important background here is that stdout is required to be line buffered by the standard as default setup.

This causes a n to flush the output.

Since the second example does not contain the newline, the output is not flushed and as fork() copies the whole process, it also copies the state of the stdout buffer.

Now, these fork() calls in your example create 8 processes in total – all of them with a copy of the state of the stdout buffer.

By definition, all these processes call exit() when returning from main() and exit() calls fflush() followed by fclose() on all active stdio streams. This includes stdout and as a result, you see the same content eight times.

It is good practice to call fflush() on all streams with pending output before calling fork() or to let the forked child call explicitly _exit() that only exits the process without flushing the stdio streams.

Note that calling exec() does not flush the stdio buffers, so it is OK not to care about the stdio buffers if you (after calling fork()) call exec() and (if that fails) call _exit().

BTW: To understand that wrong buffering may cause, here is a former bug in Linux that has been recently fixed:

The standard requires stderr to be unbuffered by default, but Linux ignored this and made stderr line buffered and (even worse) fully buffered in case that stderr was redirected through a pipe. So programs written for UNIX did output stuff without newline too late on Linux.

See comment below, it seems to be fixed now.

This is what I do in order to work around this Linux problem:

    /* 
     * Linux comes with a broken libc that makes "stderr" buffered even 
     * though POSIX requires "stderr" to be never "fully buffered". 
     * As a result, we would get garbled output once our fork()d child 
     * calls exit(). We work around the Linux bug by calling fflush() 
     * before fork()ing. 
     */ 
    fflush(stderr);

This code does not harm on other platforms since calling fflush() on a stream that was just flushed is a noop.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x