Today I was fixing a bug for the logger in a project that I’m currently working on. The bug is that when the process is killed or it crashed, the logger sometimes doesn’t write all the logs into the log file with the fprintf
function call. I knew this is an I/O buffer problem, but I didn’t know too much about how to control it and what are the I/O buffering modes besides flushing the buffer every time. Therefore, I decided to investigate about it.
Buffering modes
In C/C++, there are three buffering modes. They are full buffering (_IOFBF
), line buffering (_IOLBF
), and no buffering (_IONBF
). From C++ reference,
Full buffering: On output, data is written once the buffer is full (or flushed). On Input, the buffer is filled when an input operation is requested and the buffer is empty.
Line buffering: On output, data is written when a newline character is inserted into the stream or when the buffer is full (or flushed), whatever happens first. On Input, the buffer is filled up to the next newline character when an input operation is requested and the buffer is empty.
No buffering: No buffer is used. Each I/O operation is written as soon as possible. In this case, the buffer and size parameters are ignored.
Usually, stdout
is in line buffering mode, stderr
is in no buffering mode, and normal files are in full buffering mode. If you want to force a write, you could of course use the fflush
function to flush the buffer. However, sometimes changing the mode instead would be more handy. For example, for the logger in the project, setting the file buffer to line buffering mode is perfect because logs are written in lines.
To change the buffering mechanism, the setvbuf
function should be used (available in stdio.h
). This function is actually used to assign a buffer to a FILE
object, but in the meanwhile it allows you to specify the mode for the buffer. The function has the following signature
int setvbuf( FILE * stream, char * buffer, int mode, size_t size);
If you want the system to allocate a buffer automatically, set buffer
to null
. Control the behavior of the buffer by setting mode
and size
. The return value indicates whether a buffer is successfully assigned to the stream
. Notice that you should call this function after you open a file and before any I/O is incurred.
Conclusion
In this post, I explained the different buffering modes available to C/C++ stream buffers and how they could be set via the setvbuf
function. I hope now you have a better understanding about these mechanisms and are able to fix buffer related problems in C/C++ programs.