puts() doesn't flush the buffer in io redirection program

384 Views Asked by At

the code as follows:

int main(int argc, char **argv)
{
   const char *file = "/tmp/out"; 
   int fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);
   int stdout_tmp = dup(1);
   close(1);
   dup(fd);
   puts("hello world!");
   // fflush(stdout);
   close(1);
   close(fd);
   dup(stdout_tmp);
   puts("redirect completed!");
   exit(0);
}

I compile the code succssfully without any warning using gcc10.2.0, Againest my expectation, the two line both are output to the stdout instead "hello world" in the /tmp/out file and "redirect completed!" in stdout. When uncomment the fflush(stdout), it works!

I guess that the puts() doesn't refresh the buffer in user space, after restoring the stdout and exit, the buffer is auto refreshed.

gets() output string with trailing '\n' and the stdout buffer will be auto refreshed when encounter '\n'. Why need to call fflush(stdout) manually?

2

There are 2 best solutions below

2
On

man 3 setvbuf says:

Normally all files are block buffered. If a stream refers to a terminal (as stdout normally does), it is line buffered.

Since puts() uses stdout we should expect a flush (because of the \n).

However, since in your example stdout has not been used before the redirection, I guess that the buffering behaviour is not chosen yet. At your first write attempt to stdout, the underlying file descriptor is not a terminal anymore but a regular file. I guess the buffering behaviour is chosen at this moment.

If you add another call to puts() in your example before the redirection, then the buffering behaviour is chosen for the terminal and then does not change afterwards when the redirection is performed. In this case, your example works as you expect (without the explicit fflush()).


edit

Still in man 3 setvbuf:

When the first I/O operation occurs on a file, malloc(3) is called, and a buffer is obtained.

and further:

The setvbuf() function may only be used after opening a stream and before any other operations have been performed on it.

On linux, this is consistent with your example.

In the MSDN page for setvbuf:

stream must refer to an open file that has not undergone an I/O operation since it was opened.

1
On

The stdout file (where puts writes) is line-buffered (i.e. flushes the buffer on newline) only when connected to a terminal (basically when isatty(fileno(stdout)) is true).

When connected to another non-terminal output then it's fully buffered, which means you either need to fill the buffer completely, or call fflush explicitly to flush the buffer.