50

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...
  • 10
    Try running Program 1 with output to a file (./prog1 > prog1.out) or a pipe (./prog1 | cat).  Prepare to have your mind blown.    :-)    ⁠ – G-Man Jun 5 '18 at 5:12
  • Relevant Q+A covering another variant of this issue: C system(“bash”) ignores stdin – Michael Homer Jun 5 '18 at 5:36
  • 13
    This has gathered some close votes, so a comment on that: questions on "UNIX C API and System Interfaces" are explicitly allowed. Buffering issues are a common encounter also in shell scripts, and fork() is somewhat unix-specific too, so it would seem that this is quite on-topic for unix.SE. – ilkkachu Jun 5 '18 at 12:07
  • @ilkkachu actually, if you read that link, and click the meta question it refers to, it spells out very clearly that this is off topic. Just because something is C, and unix has C, doesn't make it on-topic. – Patrick Jun 5 '18 at 14:28
  • @Patrick, actually, I did. And I still think it fits the "within reason" clause, but of course that's just me. – ilkkachu Jun 5 '18 at 14:36
93

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).

17

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.

12

@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
}
  • 2
    this is basic of fork – Prvt_Yadav Jun 5 '18 at 6:50
  • 3
    @Debian_yadav probably obvious only if you're familiar with its implications. Like flushing stdio buffers, for example. – roaima Jun 5 '18 at 7:28
  • 2
    @Debian_yadav: en.wikipedia.org/wiki/False_consensus_effect - why should we ask questions if everybody knows everything? – Honza Zidek Jun 5 '18 at 7:55
  • 8
    @Debian_yadav I cannot read the OP's mind so I do not know. Anyway, stackexchange is a place where also others search for knowledge and I think my answer can be a useful addition to Kulasandra's good answer. My answer adds something (basic but useful), compared to the edc65's one which just repeats what Kulasandra said 2 hours before him. – Honza Zidek Jun 5 '18 at 8:04
  • 2
    This is just a short comment to an answer, not an actual answer. The question asks about "multiple times" not why it's exactly 8. – pipe Jun 5 '18 at 13:50
3

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.

  • 2
    No, stdout is required to be fully-buffered unless it's an interactive device in which case it's unspecified, but in practice it's then line-buffered. stderr is required to not be fully buffered. See pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/… – Stéphane Chazelas Jun 5 '18 at 12:51
  • My man page for setbuf(), on Debian (the one on man7.org looks similar), states that "The standard error stream stderr is always unbuffered by default." and a simple test seems to act that way, regardless of if the output goes to a file, a pipe or a terminal. Do you have any reference for what version of the C library would do otherwise? – ilkkachu Jun 5 '18 at 12:53
  • 4
    Linux is a kernel, stdio buffering is a userland feature, the kernel is not involved there. There are a number of libc implementations available for Linux kernels, the most common in server/workstation-type systems is the GNU implementation, with which stdout is full-buffered (line buffered if tty), and stderr is unbuffered. – Stéphane Chazelas Jun 5 '18 at 12:53
  • 1
    @schily, just the test I ran: paste.dy.fi/xk4 . I got the same result with a horridly out-of-date system too. – ilkkachu Jun 5 '18 at 13:35
  • 1
    @schily That's not true. For example, I'm writing this comment using Alpine Linux, which uses musl instead. – NieDzejkob Jun 5 '18 at 17:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.