on
playing with buffering modes in stdio.h
I was recently debugging a crashing program written in C using printf
statements. I noticed that even if some printf
statements executed before the crash, there was no output. I remembered something I read about stdio
(the C standard I/O library) using buffering top of the read
and write
system calls to increase efficiency. I am writing this post as a reminder to my future self 😅
setvbuf and the buffering modes
After some research, I found that setvbuf
is the function that toggles buffering when a stream is opened (stdin, stdout, and stderr are considered streams as well). The man setvbuf
page states the following:
The three types of buffering available are unbuffered, block buffered, and line buffered. When an output stream is unbuffered, information appears on the destination file or terminal as soon as written; when it is block buffered, many characters are saved up and written as a block; when it is line buffered, characters are saved up until a newline is output or input is read from any stream attached to a terminal device (typically stdin). The function fflush(3) may be used to force the block out early. (See fclose(3).) Normally all files are block buffered. If a stream refers to a terminal (as stdout normally does), it is line buffered. The standard error stream stderr is always unbuffered by default. The setvbuf() function may be used on any open stream to change its buffer. The mode argument must be one of the following three macros: _IONBF unbuffered _IOLBF line buffered _IOFBF fully buffered
That was it. The answer is why I was not seeing some prints when the program I was debugging abruplty crashed was because stdout
was opened in line-buffered mode. As a result, something is sent to stdout if a new line character is printed or a call to the fflush
function is explicitly performed. A normal exit from a program also leads to buffers being flushed. I simply added a \n
to the end of all the prints, and I was able to see them displayed. To understand how the C standard I/O library buffering works, let’s play with setvbuf
.
Examples:
line buffered:
Let’s take the following program as an example. According to the man page, when the stream refers to stdout
(which is what printf
does), the buffering mode is line-buffered by default.
#include<stdio.h>
// needed for the sleep
#include <unistd.h>
int main() {
printf("first print ");
printf("second print ");
printf("third print ");
sleep(5);
printf("exiting");
return 0;
}
Running the following program, you will see that nothing is output before the program sleeps. Only after the program exits (which flushes the buffered data) are all the prints are diplayed.
Let’s try adding a new line to the first print statement:
printf("first print\n");
Now the first print is displayed before the sleep.
Let’s try removing the new line character from the first print and adding a fflush call after the second print:
printf("first print ");
printf("second print ");
fflush(stdout);
// the rest stays the same
Result:
As you can see, the first two prints are now displayed before the sleep occurs.
fully buffered:
To set the buffering mode to fully buffered, we need to call the setvbuf function. The fully buffered mode uses a buffer with a predefined size. The library keeps writing to the buffer until it fills up, and then it flushes the content to the stream in question (using the write
system call). As we have seen earlier in the manual page, the setvbuf should be used before any other operations have been performed on the stream. Accordingly, we need to call setvbuf at the beginning of our program:
#include<stdio.h>
// needed for the sleep
#include <unistd.h>
char buffer[300];
int main() {
if (setvbuf(stdout, buffer, _IOFBF, sizeof(buffer)) != 0) {
return 1;
}
printf("first print ");
printf("second print ");
printf("third print ");
sleep(5);
printf("exiting");
return 0;
}
With a buffer size of 200 bytes, nothing is output before the program exits. Let’s try adding a new line charachter at the end of each print:
printf("first print\n");
printf("second print\n");
printf("third print\n");
sleep(5);
printf("exiting\n");
Unlike the line buffered mode, adding new lines did not trigger the display of the prints because our buffer did not get filled. Let’s try reducing the size of our buffer to 20:
char buffer[20];
As you can see, this triggers the printing of the first three statements because the first print is 13 characters, the second is 14, and the third is 13. The buffer gets filled twice, triggering the flush.
unbuffered:
As mentioned, the unbuffered mode causes every print to be immediately output by the I/O library. This means that every print will trigger a write
system call. To set the mode to unbuffered, we have to modify the mode argument of the setvbuf function:
if (setvbuf(stdout, NULL, _IONBF, 0) != 0) {
return 1;
}
Because a buffer is not needed in this mode, we can use NULL
for the buffer argument and a size of 0.
The unbuffered mode is the default mode when writing to stderr.
Takeaways
To better understand the behavior of your C programs, it’s important to understand the different buffering modes and their performance implications. I hope this write-up helps a little. The manual page of the setvbuf
contains more details. I also found some useful informations in the section 3 of the chapter 5 (Standard I/O library) of the book: Advanced Programming in the Unix Environment (third edition).