60

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...
Jeff Schaller
  • 66,199
  • 35
  • 114
  • 250
lmaololrofl
  • 653
  • 1
  • 5
  • 6
  • 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 Says 'Reinstate Monica' Jun 05 '18 at 05:12
  • Relevant Q+A covering another variant of this issue: [C system(“bash”) ignores stdin](https://unix.stackexchange.com/a/411191/73093) – Michael Homer Jun 05 '18 at 05:36
  • 15
    This has gathered some close votes, so a comment on that: questions on "UNIX C API and System Interfaces" are [explicitly allowed](https://unix.stackexchange.com/help/on-topic). 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 05 '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. – phemmer Jun 05 '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 05 '18 at 14:36
  • I assume one hello world in the output of program has a space after the world by accident? – Ferrybig Jun 06 '18 at 07:02

4 Answers4

106

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

Kusalananda
  • 320,670
  • 36
  • 633
  • 936
  • 14
    Related: you can end `main` with `_exit(0)` to just make an exit system call without flushing buffers, and then it will be printed zero times without a newline. ([Syscall implementation of exit()](https://stackoverflow.com/q/46903180) and [How come \_exit(0) (exiting by syscall) prevents me from receiving any stdout content?](https://stackoverflow.com/q/9755027)). Or you can pipe Program1 into `cat` or redirect it to a file and see it get printed 8 times. (stdout is full-buffered by default when it's not a TTY). Or add an `fflush(stdout)` to the no-newline case before the 2nd `fork()`... – Peter Cordes Jun 05 '18 at 06:41
19

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.

edc65
  • 299
  • 1
  • 4
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
}
Honza Zidek
  • 263
  • 1
  • 9
  • 2
    this is basic of fork – Prvt_Yadav Jun 05 '18 at 06:50
  • 3
    @Debian_yadav probably obvious only if you're familiar with its implications. Like flushing _stdio_ buffers, for example. – roaima Jun 05 '18 at 07:28
  • 2
    @Debian_yadav: https://en.wikipedia.org/wiki/False_consensus_effect - why should we ask questions if everybody knows everything? – Honza Zidek Jun 05 '18 at 07: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 05 '18 at 08:04
  • 1
    @HonzaZidek you could be right finish it here. Thanks for your answer. – Prvt_Yadav Jun 05 '18 at 08:07
  • 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 05 '18 at 13:50
  • 2
    I agree with @pipe that this doesn't seem to be answering the question asked, although it does answer a different question. – David Z Jun 06 '18 at 01:45
5

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.

schily
  • 18,806
  • 5
  • 38
  • 60
  • 3
    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 http://pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/V2_chap02.html#tag_15_05 – Stéphane Chazelas Jun 05 '18 at 12:51
  • My man page for `setbuf()`, on Debian ([the one on man7.org looks similar](http://man7.org/linux/man-pages/man3/setbuf.3.html)), 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 05 '18 at 12:53
  • 5
    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 05 '18 at 12:53
  • All Linux distros usually depend on glibc and I guess that all Linux distros behave the same. The problem with a buffered stderr has been reported by the author of xcdroast and the wrong behavior has been verified. – schily Jun 05 '18 at 13:05
  • @ilkkachu: Do you have any reference for your claim? I guess that your test program did not wait but exit after printing things to stderr. If the bug has been fixed during the past 12 months, it would be interesting to know. – schily Jun 05 '18 at 13:15
  • 1
    @schily, just the test I ran: http://paste.dy.fi/xk4 . I got the same result with a horridly out-of-date system too. – ilkkachu Jun 05 '18 at 13:35
  • 1
    @schily That's not true. For example, I'm writing this comment using Alpine Linux, which uses musl instead. – Maya Jun 05 '18 at 17:01
  • @schily: this answer would be better without the mistaken information about a flaw in "Linux's stderr" (meaning glibc). It wasn't relevant to the question (and it wasn't even correct). – hackerb9 Jun 08 '18 at 06:36
  • Well it turned out, that this important bug has been recently fixed. It was of course correct. I now changed the text to use the past. – schily Jun 08 '18 at 08:37