In any program that writes significant data to stdout
, you should close stdout
immediately before exiting, so that you can check for and report delayed write errors. (Delayed write errors are a design mistake; it ought to be impossible for fclose
or close
to fail. But we are stuck with them.)
The usual construct is, at the very end of main
,
if (ferror(stdout) || fclose(stdout)) {
perror("stdout: write error");
return 1;
}
return 0;
Some programs stick an fflush
in there too, but ISO C requires fclose
to perform a fflush
, so it shouldn't be necessary. This construct is entirely portable.
It's important for this to be the very last thing you do before exiting. It is relatively common for libraries to assume that stdout
is never closed, so they may malfunction if you call into them after closing stdout
. stdin
and stderr
are also troublesome that way, but I've yet to encounter a situation where one wanted to close those.
It does sometimes happen that you want to close stdout
before your program is completely done. In that case you should actually leave the FILE
open but close the underlying "file descriptor" and replace it with a dummy.
int rfd = open("/dev/null", O_WRONLY);
if (rfd == -1) perror_exit("/dev/null");
if (fflush(stdout) || close(1)) perror_exit("stdout: write error");
dup2(rfd, 1);
close(rfd);
This construct is NOT portable to Windows. There is an equivalent, but I don't know what it is. It's also not thread-safe: another thread could call open
in between the close
and dup2
operations and be assigned fd 1, or it could attempt to write something to stdout
in that window and get a spurious write error. For thread safety you have to duplicate the old fd 1 and close it via that handle:
// These allocate new fds, which can always fail, e.g. because
// the program already has too many files open.
int new_stdout = open("/dev/null", O_WRONLY);
if (new_stdout == -1) perror_exit("/dev/null");
int old_stdout = dup(1);
if (old_stdout == -1) perror_exit("dup(1)");
flockfile(stdout);
if (fflush(stdout)) perror_exit("stdout: write error");
dup2 (new_stdout, 1); // cannot fail, atomically replaces fd 1
funlockfile(stdout);
// this close may receive delayed write errors from previous writes
// to stdout
if (close (old_stdout)) perror_exit("stdout: write error");
// this close cannot fail, because it only drops an alternative
// reference to the open file description now installed as fd 1
close (new_stdout);
Order of operations is critical: the open
, dup
and fflush
calls must happen before the dup2
call, both close
calls must happen after the dup2
call, and stdout must be locked from before the fflush
call until after the dup2
call.
Additional possible complications, dealing with which is left as an exercise:
- Cleaning up temporary fds and locks on error, when you don't want to stop the whole program on error
- If the thread might be canceled mid-operation
- If a concurrent thread might call
fork
and execve
mid-operation