C Stream Buffering: fflush, setbuf, and setvbuf

Standard I/O in the C programming language uses stream buffering to optimize input and output operations by reducing the number of direct, expensive system calls. This article explains how stream buffering operates under the hood, details the differences between unbuffered, line-buffered, and fully buffered streams, and demonstrates how to control and flush these buffers using the standard library functions setvbuf, setbuf, and fflush.

How Standard Stream Buffering Works

When a program reads from or writes to a FILE pointer in C, the standard I/O library (stdio.h) does not immediately trigger an operating system read or write system call for every character. Instead, data is accumulated in an intermediate memory area called a buffer.

C defines three distinct buffering modes:

  • Fully Buffered (_IOFBF): Characters are stored in the buffer and only transmitted to the destination when the buffer fills up completely. This is the default behavior for file-based streams.
  • Line Buffered (_IOLBF): Characters are collected and transmitted whenever a newline character (\n) is written or when the buffer becomes full. This is the standard behavior for interactive devices, such as stdout connected to a terminal.
  • Unbuffered (_IONBF): Characters are transmitted directly to their destination as soon as they are written, without temporary storage. By default, standard error (stderr) is unbuffered so diagnostic messages appear immediately.

Customizing Buffers with setvbuf

The setvbuf function is the preferred, robust interface for controlling stream buffering behavior. It allows you to specify the stream, the buffering mode, an optional user-allocated buffer, and the buffer's size.

Function Signature

int setvbuf(FILE *stream, char *buffer, int mode, size_t size);

Parameters and Usage

  • stream: The pointer to the open FILE stream to configure.
  • buffer: A pointer to a user-allocated memory block to serve as the buffer. If set to NULL, the standard library automatically allocates an internal buffer of the requested size.
  • mode: The buffering mode, chosen from _IOFBF, _IOLBF, or _IONBF.
  • size: The size in bytes of the buffer (ignored if mode is _IONBF).

setvbuf must be called immediately after opening the stream and before performing any I/O operations. It returns 0 on success, or a non-zero value on failure (such as an invalid mode or invalid size).

Example

#include <stdio.h>

int main(void) {
    // Disable buffering on stdout completely
    setvbuf(stdout, NULL, _IONBF, 0);

    // Set a custom 4KB buffer with full buffering for a file
    FILE *file = fopen("output.log", "w");
    if (file) {
        char custom_buffer[4096];
        setvbuf(file, custom_buffer, _IOFBF, sizeof(custom_buffer));
        fputs("Buffered file output\n", file);
        fclose(file);
    }
    return 0;
}

Note: If providing a user-allocated array (like custom_buffer), ensure its lifetime exceeds the lifetime of the stream, or allocate it dynamically to avoid undefined behavior caused by out-of-scope stack memory.

Legacy Buffering with setbuf

The setbuf function is an older, simplified wrapper around setvbuf.

Function Signature

void setbuf(FILE *stream, char *buffer);

Mechanics

Passing a buffer pointer to setbuf is equivalent to:

(void)setvbuf(stream, buffer, _IOFBF, BUFSIZ);

Passing NULL disables buffering entirely, equivalent to:

(void)setvbuf(stream, NULL, _IONBF, 0);

setbuf does not provide a mechanism to specify custom buffer sizes (it assumes the implementation-defined constant BUFSIZ) and does not return status codes to indicate failure. For modern C development, setvbuf is preferred over setbuf.

Forcing Buffer Writes with fflush

While buffers normally empty automatically when full, on a newline (in line-buffered mode), or when the stream is closed via fclose, you often need to force pending writes to disk or the terminal manually. This is managed by fflush.

Function Signature

int fflush(FILE *stream);

Behavior

  • Output Streams: Calling fflush on an output or update stream writes any unwritten data from the stream's user-space buffer to the underlying operating system file descriptor.
  • All Open Streams: Passing NULL as the stream argument (fflush(NULL)) flushes all open output streams across the entire program.
  • Return Value: Returns 0 on success, or EOF if a write error occurs.

Input Stream Undefined Behavior

According to the ISO C standard, calling fflush on an input stream (such as stdin) results in undefined behavior. While some platform-specific extensions (such as Microsoft's MSVC runtime) define behavior to discard unread input, it is non-portable and should not be used in standard C.

Example

#include <stdio.h>
#include <unistd.h>

int main(void) {
    // stdout is typically line-buffered; without a newline, text remains in the buffer
    printf("Processing data, please wait...");
    
    // Explicitly write the buffer contents to the terminal immediately
    fflush(stdout);

    // Simulate work
    sleep(2);

    printf(" Done!\n");
    return 0;
}