2

Friend sent me a program:

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

void chain(int n, int current_nr) {
    printf("Running %d\n", current_nr);

    pid_t pid = fork();

    if (pid != 0) { //parent
        return; 
    }
    else { //child
        if (current_nr == n - 2) {
            return;
        }
        else {
            chain(n, current_nr + 1);
        }
    }
}

int main(void) {
    puts("Program started");

    chain(5, 0);

    wait(NULL);

    return 0;
}

If program is executed as ./program then the output is:

Program started
Running 0
Running 1
Running 2
Running 3

But after executing as ./program > outputfile then the outputfile content is:

Program started
Running 0
Running 1
Running 2
Running 3
Program started
Running 0
Running 1
Running 2
Running 3
Program started
Running 0
Running 1
Running 2
Program started
Running 0
Running 1
Program started
Running 0

What is going on?

Filip Czaplicki
  • 189
  • 3
  • 12
  • 2
    I think it has something to do with the fact that `fork` is duplicating the file descriptors and the output is buffered... – Eugene Sh. Nov 05 '15 at 20:17
  • 1
    Both the parent and the child call `exit` (implicitly on return from `main`). That means any "end of process" activities that the parent had to do get done *twice*. You should call `_exit` in the child when you're done to avoid this. – David Schwartz Nov 05 '15 at 20:41
  • This program demonstrates very nicely, how `FILE*` buffering works when you do `fork()`. – hyde Nov 06 '15 at 05:40

2 Answers2

7

Background: In Unix-like OSes, C standard library detects if output is going to a terminal or if it is being redirected to a file or a pipe. On terminal, stdout output is line buffered, which means it is flushed when you print a new line character '\n'. When it goes to a file/pipe, stdout is fully buffered by default, which means it is only flushed when buffer is full (or stdout is closed, typically at program exit).

More background: Third buffering mode would be no buffering, which means everything you print is sent to the kernel right away. stdout (used implicitly by printf) is a global FILE* variable which writes to process's standard output, and the buffering here is just the normal buffering done for all FILE* files, the only special thing is the default buffering mode "magic". The different buffering modes exist for performance reasons. Buffer flush means system call and all kinds of things starting to happen in the background. When data is sent to kernel in as big chunks as possible, there's least possible overhead. When printing to terminal, line buffering allows user to see the text, while when writing to file, full buffering maximizes throughput. Also, stdout buffer is flushed before reading from stdin, which is why code like printf("Prompt: " /* no newline */); fgets(...); works without changing buffering mode or explicitly flushing.

Answer to the question: So, when fully buffered, and you print the first line, it stays in the process's own buffer. Then you fork, and buffer gets duplicated to child process. This happens a few times and applies also to other output lines. This is why you get same output multiple times: It was in parent process buffer still when you forked. And then when processes exit, their buffers get flushed all at once, so you get output of each process as one piece. Here you print so little, that everything fits in the buffer, so there is no output due to buffer being full. This is why your output is so clean. If there were flushes in between due to buffer being full, then output would be more messed up, which you can test by setting full buffering with small buffer size, like 7.

How to fix: You can change buffering mode with setvbuf standard C function, to be either line buffered or no buffering. You can also flush explicitly using fflush standard C function before doing fork (and your program nicely demonstrates the need to take care of this whole issue when forking).

hyde
  • 60,639
  • 21
  • 115
  • 176
1

likely linked to different buffering and serializing when "to file" or console is used. if you force buffering flush fflush(stdout)

after any printf or disable buffering in main, (How to turn off buffering of stdout in C)

setvbuf(stdout, NULL, _IONBF, 0);

you may get close behavior in both run scenarios

Community
  • 1
  • 1
Michel Sanches
  • 428
  • 3
  • 9