The program output changes when

I have a simple C program to start the time process (I would prefer not to publish the full code, as this is an active school assignment). My main function looks like this:

int main(void) { int i; for (i = 0; i < 5; i++) { printf("%lf\n", sample_time()); } exit(0); } 

sample_time() is a function that takes the time to deploy a new process and returns the result in seconds as a double . The sample_time() , which forks:

 double sample_time() { // timing stuff if (!fork()) exit(0); // immediately close new process // timing stuff return a_sample_time; } 

As expected, running the program, times , in the terminal displays 5 numbers as follows:

 $ ./times 0.000085 0.000075 0.000079 0.000071 0.000078 

However, an attempt to transfer this to a file (or to another location) on a Unix terminal produces unexpected results.

For example, ./times > times.out creates a file with fifteen numbers. Also ./times | wc -l ./times | wc -l prints 15 , confirming an earlier result. By running ./times | cat ./times | cat , I again see fifteen numbers , more than five of which are different .

Does anyone know what on earth can cause something like this? I have no ideas.

./times ! = ./times | cat ./times | cat . Wat.

+7
c linux unix command-line-interface
source share
1 answer

Necessary knowledge

  • Fact 1 When stdout is connected to TTY, it is buffered by line. When it is connected to a file or pipeline, it is filled with a buffer. This means that it only flushed every 8 KB, say , not every line.

  • Fact 2 Fork processes have duplicate copies of data in memory. This includes stdio output buffers if data has not yet been flushed.

  • Fact 3 Calling exit() flushes the stdio output buffers before the program exits.

Case 1: terminal output

When your program prints to the terminal, its output is buffered by line. Each call to printf() that ends with \n immediately prints. This means that each line is printed and the output buffer in memory is freed before fork() run.

Result : 5 lines of output.

Case 2: output to a pipeline or file

When libc sees that stdout is not connected to TTY, it switches to a more efficient strategy for full buffering. This causes output to be buffered until a value of 4 KB is accumulated. This means that the output from printf() stored in memory, and calls to write() deferred.

 if (!fork()) exit(0); 

After forcing, the child process has a copy of buffered output. Then calling exit() causes a buffer flush. However, this does not affect the parent process. Its output is still buffered .

Then, when the second line of output is printed, it has two lines, buffered. The next child process plugs, exits and prints these two lines. The parent saves two lines of output, etc.

Result: The child processes print 0, 1, 2, 3, and 4 lines of output. The main program prints 5 when it finally exits and resets its output. 0 + 1 + 2 + 3 + 4 + 5 = 15. 15 lines of output instead of 5!

Decision

  • Call _Exit() instead of exit() . The _Exit() function is similar to exit() , but does not call any functions registered with atexit() . That would be my preferred solution.

  • Explicitly set stdout to buffer strings: setvbuf(stdout, NULL, _IOLBF, 0);

  • Call fflush(stdout) after each printf .

+10
source share

All Articles