2

So I wrote a test program and here is the code

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int i;
    printf("%s", "entering\n");
    fflush(stdout);
    for (i = 0 ; i < 3 ; i++)
    {
        fork();
        fflush(stdout);
    }

    printf("%s", "exiting\n");
}

When I compile and run it in the terminal it displays what I expect it to: "entering" once, and "exiting" some times. When I run it and redirect the output to a file it displays entering for every exiting.

1) Why does it not output the same thing to terminal and to a file every time?

2) Why does it display entering in the file 8 times but not entering in in the terminal only once (once is what I would expect it to do).

3) Do the fflush() statements make a difference when my output goes to a file?

2 Answers2

5

It has to do with the buffering on the standard file handles. From ISO C99 7.19.3/7 Files:

As initially opened, the standard error stream is not fully buffered; the standard input and standard output streams are fully buffered if and only if the stream can be determined not to refer to an interactive device.

When you're writing to the terminal, the output is most likely (a) line-buffered, meaning it will be flushed whenever a newline is sent.

With redirection, it's fully buffered meaning it will only flush when the buffer is full.

You can use setvbuf before operating on stdout, to set it to unbuffered or line buffered, and you won't have to worry about flushing.

setvbuf (stdout, NULL, _IONBF, BUFSIZ); // _IONBF for  no  buffering.
                                        // _IOFBF for full buffering.
                                        // _IOLBF for line buffering.

Just keep in mind that unbuffered output may affect performance quite a bit if you're sending the output to a file. Also keep in mind that support for this is implementation-defined, not guaranteed by the standard.

C99 section 7.19.3/3 is the relevant bit:

When a stream is unbuffered, characters are intended to appear from the source or at the destination as soon as possible. Otherwise characters may be accumulated and transmitted to or from the host environment as a block.

When a stream is fully buffered, characters are intended to be transmitted to or from the host environment as a block when a buffer is filled.

When a stream is line buffered, characters are intended to be transmitted to or from the host environment as a block when a new-line character is encountered.

Furthermore, characters are intended to be transmitted as a block to the host environment when a buffer is filled, when input is requested on an unbuffered stream, or when input is requested on a line buffered stream that requires the transmission of characters from the host environment.

Support for these characteristics is implementation-defined, and may be affected via the setbuf and setvbuf functions.


(a) Whether standard input and output are unbuffered or line buffered where the underlying file is possibly interactive is not specified by the standard (see here). Line buffering is the most common (by far) from what I've encountered.


As to why you're getting multiple entering messages even though you flush it before forking, that's a trickier one. You haven't listed your environment so this is supposition at best but I see a few possibilities (although there may well be more).

Firstly, the fflush may be failing for some reason. This is actually possible but easy to check since it behaves similarly to write (because it usually calls write under the covers).

In other words, check errno after the flush to see if there's been a problem. If so, the C runtime buffers will still be unflushed in all children so they'll all write entering.

Secondly, even if the buffers are flushed, there may be more buffering going on below (at the write level or within the terminal drivers themselves) which is duplicated by the fork, resulting in multiple outputs to the terminal. I consider this unlikely.

Thirdly, this may just be a weird platform-specific issue. When I run your code on my Ubuntu 11.04 box, I see no difference between the terminal output and the file output variants. They both output one entering and eight exiting messages.

If that third one is the case then you really have no recourse: ISO C doesn't mandate what happens in this case because ISO C knows nothing of fork. I can't find anything in POSIX.1 that seems to indicate one way or another but it may be there.

For what it's worth, if I comment out only the first fflush, I get entering twice followed by eight exiting messages. If I comment out only the second, it asts the same as if they're both there, one entering followed by eight exiting.

If I comment out both of them, I get eight entering/exiting pairs.

Community
  • 1
  • 1
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Wow....very thorough. Thanks I understand what is happening a lot better now....still not 100% but I will do some more reading –  Sep 13 '11 at 02:49
  • If I could ask....what would be the reason for not all of the "exiting" to print when the output is redirected to the file. It does not always print 8 of them. Is it because the process is ending before the buffer is flushed? Also, does each fork'ed process get it's own output buffer, or do they all use the same one? –  Sep 13 '11 at 02:51
  • They each get their own, but if there's anything in it they each get a copy of that data. – Random832 Sep 13 '11 at 02:59
  • If anyone could answer #2 it would greatly help my understanding of what is going on...that is all the questions I have..Thanks everyone –  Sep 13 '11 at 03:05
  • Ok...looks like I understand one thing.....THIS STUFF IS TRICKY!! Thanks for the help...looks like I still have a lot to understand –  Sep 13 '11 at 03:20
  • I am on Ubuntu. Ran the same code on my centos box and I printed 1 entering and 1 exiting to a file. printed 1 entering and 8 exiting to the terminal. –  Sep 13 '11 at 03:30
  • Your answer seems wrong; I think OP is showing us fake code. Unless `fflush` is failing (and it has no reason to fail), the behavior of this program is well-defined by POSIX, and entering must be printed only once. There is no further buffering in `write` that could be duplicated at `fork`. – R.. GitHub STOP HELPING ICE Sep 13 '11 at 05:07
  • The write buffering was a _possibility,_ admittedly unlikely. I couldn't find anything in POSIX.1 that specifically disallowed it, hence my final comment. And the code behaves itself in my environment. Since there was no indication of environment, I could only provide supposition - I'll make that clear. – paxdiablo Sep 13 '11 at 05:12
  • The 8 entering and 8 exiting is what I'm asking about....I don't understand why....very strange stuff....Thanks for the help either way. Just another unsolved mystery –  Sep 13 '11 at 14:15
  • @Rell3oT: Check the return value from `fflush()`. If it's failing, that would cause that. And, though unlikely, it _can_ fail. – paxdiablo Sep 13 '11 at 14:22
1

Regarding the multiple "entering" printed, it may be a bug(?) due to your environment.

I have already noticed the following strange behavior when running a program on zsh/Cygwin/WinXP compiled with MSVC98. If the program crashes, the stdout buffer is discarded and the program is rerun a small number of time.

For instance:

#include <stdio.h>
int main(int argc, char *argv[]) {
    printf("hello\n");
    fflush(stdout);
    *(char*) 0 = 1;
}

will give on my PC:

> cl.exe crash.c
[etc...]
> ./crash.exe
hello
hello
hello
hello
hello
hello

Without the flush(), nothing is printed.

The same program compiled with gcc has a more "expected" behavior, even without the flush():

hello
zsh: segmentation fault (core dumped)  ./a.exe

No idea why. I tried to investigate a bit, but without success.

calandoa
  • 5,668
  • 2
  • 28
  • 25