The behaviour of printf()
seems to depend on the location of stdout
.
stdout
is sent to the console, then printf()
is line-buffered and is flushed after a newline is printed.stdout
is redirected to a file, the buffer is not flushed unless fflush()
is called.printf()
is used before stdout
is redirected to file, subsequent writes (to the file) are line-buffered and are flushed after newline.When is stdout
line-buffered, and when does fflush()
need to be called?
void RedirectStdout2File(const char* log_path) { int fd = open(log_path, O_RDWR|O_APPEND|O_CREAT,S_IRWXU|S_IRWXG|S_IRWXO); dup2(fd,STDOUT_FILENO); if (fd != STDOUT_FILENO) close(fd); } int main_1(int argc, char* argv[]) { /* Case 1: stdout is line-buffered when run from console */ printf("No redirect; printed immediately\n"); sleep(10); } int main_2a(int argc, char* argv[]) { /* Case 2a: stdout is not line-buffered when redirected to file */ RedirectStdout2File(argv[0]); printf("Will not go to file!\n"); RedirectStdout2File("/dev/null"); } int main_2b(int argc, char* argv[]) { /* Case 2b: flushing stdout does send output to file */ RedirectStdout2File(argv[0]); printf("Will go to file if flushed\n"); fflush(stdout); RedirectStdout2File("/dev/null"); } int main_3(int argc, char* argv[]) { /* Case 3: printf before redirect; printf is line-buffered after */ printf("Before redirect\n"); RedirectStdout2File(argv[0]); printf("Does go to file!\n"); RedirectStdout2File("/dev/null"); }
Use the fflush Function to Flush stdout Output Stream in C As a result, there are buffers maintained by the C library for handling the input/output operations when using the stdio function calls. If the user needs to force writing to kernel buffers, it needs to flush the given stream provided by the fflush function.
stdout. flush() forces it to “flush” the buffer, meaning that it will write everything in the buffer to the terminal, even if normally it would wait before doing so.
When main() exits, all open streams are closed... to include stdout . Closing the open stream flushes stdout and what you've written to the buffer gets committed with or without the newline.
By default writes to stdout pass through a 4096 byte buffer, unless stdout happens to be a terminal/tty in which case it is line buffered. Hence the inconsistency between the immediate output when your program is writing to the terminal and the delayed output when it is writing to a pipe or file.
Flushing for stdout
is determined by its buffering behaviour. The buffering can be set to three modes: _IOFBF
(full buffering: waits until fflush()
if possible), _IOLBF
(line buffering: newline triggers automatic flush), and _IONBF
(direct write always used). "Support for these characteristics is implementation-defined, and may be affected via the setbuf()
and setvbuf()
functions." [C99:7.19.3.3]
"At program startup, three text streams are predefined and need not be opened explicitly — standard input (for reading conventional input), standard output (for writing conventional output), and standard error (for writing diagnostic output). 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." [C99:7.19.3.7]
So, what happens is that the implementation does something platform-specific to decide whether stdout
is going to be line-buffered. In most libc implementations, this test is done when the stream is first used.
printf()
is flushed automatically.fflush()
, unless you write gobloads of data to it.printf()
, stdout acquired the line-buffered mode. When we swap out the fd to go to file, it's still line-buffered, so the data is flushed automatically.Each libc has latitude in how it interprets these requirements, since C99 doesn't specify what an "interactive device" is, nor does POSIX's stdio entry extend this (beyond requiring stderr to be open for reading).
Glibc. See filedoalloc.c:L111. Here we use stat()
to test if the fd is a tty, and set the buffering mode accordingly. (This is called from fileops.c.) stdout
initially has a null buffer, and it's allocated on the first use of the stream based on the characteristics of fd 1.
BSD libc. Very similar, but much cleaner code to follow! See this line in makebuf.c
Your are wrongly combining buffered and unbuffered IO functions. Such a combination must be done very carefully especially when the code has to be portable. (and it is bad to write unportable code...)
It is certainly best to avoid combining buffered and unbuffered IO on the same file descriptor.
Buffered IO: fprintf()
, fopen()
, fclose()
, freopen()
...
Unbuffered IO: write()
, open()
, close()
, dup()
...
When you use dup2()
to redirect stdout. The function is not aware of the buffer which was filled by fprintf()
. So when dup2()
closes the old descriptor 1 it does not flush the buffer and the content could be flushed to a different output. In your case 2a it was sent to /dev/null
.
In your case it is best to use freopen()
instead of dup2()
. This solves all your problems:
FILE
stream. (case 2a)Here is the correct implementation of your function:
void RedirectStdout2File(const char* log_path) { if(freopen(log_path, "a+", stdout) == NULL) err(EXIT_FAILURE, NULL); }
Unfortunately with buffered IO you cannot directly set permissions of a newly created file. You have to use other calls to change the permissions or you can use unportable glibc extensions. See the fopen() man page
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With