C/C++ POSIX File I/O Operations: A Practical Guide

Common I/O Operations in C, C++, and POSIX

This guide covers essential input/output operations across three paradigms:

  • File-to-memory and memory-to-file transfers
  • In-memory stream operations
  • Buffered versus unbuffered POSIX I/O

File and Memory Operations

The following examples demonstrate reading from and writing to files using standard C and C++ libraries.

// Read file contents into memory buffer
void read_file_content()
{
    char buffer[BUFFER_SIZE];
    size_t bytes_read = 0;
    FILE *file_handle = fopen("data.txt", "r+");

    if (file_handle != NULL) {
        bytes_read = fread(buffer, sizeof(char), BUFFER_SIZE, file_handle);
        printf("Bytes read: %zu\n", bytes_read);
        printf("Content:\n%s\n\n", buffer);
        fclose(file_handle);
    }
}

// Write memory buffer contents to file
void write_file_content()
{
    const char *data = "Sample text data";
    char buffer[BUFFER_SIZE];
    strncpy(buffer, data, BUFFER_SIZE);
    size_t bytes_written = 0;
    FILE *file_handle = fopen("data.txt", "w+");

    if (file_handle != NULL) {
        bytes_written = fwrite(buffer, sizeof(char), strlen(buffer), file_handle);
        printf("Bytes written: %zu\n\n", bytes_written);
        fclose(file_handle);
    }
}

// Read formatted data from file using scanf semantics
void scan_file_content()
{
    char buffer[BUFFER_SIZE];
    FILE *file_handle = fopen("data.txt", "r+");

    if (file_handle != NULL) {
        while (fscanf(file_handle, "%s", buffer) != EOF) {
            printf("Scanned: %s\n", buffer);
        }
        printf("\n");
        fclose(file_handle);
    }
}

// C++ stream-based file reading
void read_with_ifstream()
{
    ifstream input("data.txt");
    string token;

    while (input >> token) {
        cout << "Token: " << token << endl;
    }

    input.close();
    cout << endl;
}

// Write formatted data to file
void write_formatted_output()
{
    const char *message = "Formatted output";
    FILE *file_handle = fopen("output.txt", "w+");
    int chars_written = fprintf(file_handle, "%s", message);
    printf("Characters written: %d\n", chars_written);
    fclose(file_handle);
}

// C++ stream-based file writing
void write_with_ofstream()
{
    ofstream output("output.txt", ios::app | ios::out);
    string content = "Appended content";
    output.write(content.c_str(), content.length());
    output.close();
}

In-Memory Stream Operations

These operations handle string buffers instead of files, enabling string parsing and formatting.

// Parse formatted data from string buffer
void parse_string_buffer()
{
    const char *source = "Parse this string";
    char destination[BUFFER_SIZE];

    sscanf(source, "%s", destination);
    printf("Parsed result: %s\n\n", destination);
}

// C++ string stream for tokenization
void tokenize_with_stringstream()
{
    string text = "Tokenize this sentence";
    string word;

    istringstream stream(text);
    while (stream >> word) {
        cout << "Word: " << word << endl;
    }
}

// Format data into string buffer
void format_into_string()
{
    const char *source = "Format this";
    char destination[BUFFER_SIZE];

    sprintf(destination, "%s", source);
    printf("Formatted: %s\n\n", destination);
}

// C++ string stream for building strings
void build_string_with_stream()
{
    string data = "String data";
    ostringstream stream;
    stream << data;
    string result = stream.str();
    cout << "Result: " << result << endl;
}

Buffered and Unbuffered POSIX I/O

POSIX provides both buffered and unbuffered I/O operations. The unbuffered functions (read/write) interact directly with the kernel, while buffered versions reduce system call overhead.

// Robust read function handling partial reads
ssize_t read_all(int file_descriptor, void *user_buffer, size_t requested_bytes)
{
    size_t remaining = requested_bytes;
    ssize_t bytes_received;
    char *ptr = user_buffer;

    while (remaining > 0) {
        bytes_received = read(file_descriptor, ptr, remaining);

        if (bytes_received < 0) {
            if (errno == EINTR) {
                bytes_received = 0;  // Retry on signal interrupt
            } else {
                return -1;          // Actual error occurred
            }
        } else if (bytes_received == 0) {
            break;                   // End of file reached
        }

        remaining -= bytes_received;
        ptr += bytes_received;
    }

    return (requested_bytes - remaining);
}

// Robust write function handling partial writes
ssize_t write_all(int file_descriptor, void *user_buffer, size_t requested_bytes)
{
    size_t remaining = requested_bytes;
    ssize_t bytes_sent;
    char *ptr = user_buffer;

    while (remaining > 0) {
        bytes_sent = write(file_descriptor, ptr, remaining);

        if (bytes_sent <= 0) {
            if (errno == EINTR) {
                bytes_sent = 0;     // Retry on signal interrupt
            } else {
                return -1;          // Write failed
            }
        }

        remaining -= bytes_sent;
        ptr += bytes_sent;
    }

    return requested_bytes;
}

// Buffered I/O context structure
typedef struct {
    int fd;                    // File descriptor
    int available;             // Bytes currently in buffer
    char *current_ptr;         // Current position in buffer
    char internal_buffer[8192]; // Internal read buffer
} buffered_io_t;

// Buffered read with internal buffering
static ssize_t buffered_read(buffered_io_t *ctx, char *dest, size_t count)
{
    int bytes_to_copy;

    // Refill internal buffer when empty
    while (ctx->available <= 0) {
        ctx->available = read(ctx->fd, ctx->internal_buffer, sizeof(ctx->internal_buffer));

        if (ctx->available < 0) {
            if (errno != EINTR) {
                return -1;     // Error (not signal interruption)
            }
            // Retry on EINTR
        } else if (ctx->available == 0) {
            return 0;          // EOF reached
        } else {
            ctx->current_ptr = ctx->internal_buffer;  // Reset pointer
        }
    }

    // Transfer min(count, available) bytes
    bytes_to_copy = count;
    if (ctx->available < count) {
        bytes_to_copy = ctx->available;
    }

    memcpy(dest, ctx->current_ptr, bytes_to_copy);
    ctx->current_ptr += bytes_to_copy;
    ctx->available -= bytes_to_copy;

    return bytes_to_copy;
}

Conceptual Overview

The fundamental pattern across these APIs involves redirecting the data source or destination. The following table illustrates this principle:

// C standard library - destination varies
printf()     -> stdout          scanf()      <- stdin
fprintf()    -> FILE*           fscanf()     <- FILE*
sprintf()    -> char*           sscanf()     <- char*

// C++ stream library - mirrors C patterns with buffering
ostream                      istream
ofstream                     ifstream
ostringstream                istringstream

Buffered implementations like the custom buffered_read() demonstrate significant performance advantages by minimizing transitions between user space and kernel space, allowing larger chunks of data to be transferred in single system calls.

Tags: C C++ POSIX file-io buffered-io

Posted on Sun, 10 May 2026 04:15:17 +0000 by michalurban