Skip to content

File I/O in C: Write and Read Operations on the Same File Stream

When working with file input/output in C, developers often encounter unexpected behavior when trying to read immediately after writing to the same file stream. This article explains the common pitfalls and provides robust solutions for proper file handling.

The Problem: Why Reading After Writing Fails

Consider this common scenario where a developer tries to write to a file and immediately read back the content:

c
#include <stdio.h>

#define SIZE 256
int main() {
  FILE *fptr = fopen("mem.txt","w+"); // Create/truncate for read/write
  char buffer[SIZE + 1];
  buffer[SIZE] = '\0';

  if(!fptr) {
    printf("Failed creation");
    return -1;
  }

  printf("Creation successful\n");
  fprintf(fptr, "Hello, World!"); // Write to file

  fread(buffer, sizeof(buffer), 1, fptr); // Try to read immediately
  printf("%s", buffer);

  fclose(fptr);
  return 0;
}

Expected output:

Creation successful
Hello, World!

Actual output:

Creation successful

The read operation fails because the file position indicator remains at the end of the file after the write operation.

Root Cause: File Positioning Requirements

C Standard Requirements

According to the C standard (section 7.23.5.3p7), when using a read/write stream ("w+" or "r+" mode):

Output shall not be directly followed by input without an intervening call to the fflush function or to a file positioning function (fseek, fsetpos, or rewind), and input shall not be directly followed by output without an intervening call to a file positioning function, unless the input operation encounters end-of-file.

This means you must reposition the file pointer between write and read operations.

Solution 1: Using File Positioning Functions

The simplest fix is to reposition the file pointer using rewind() or fseek():

c
fprintf(fptr, "Hello, World!");
rewind(fptr); // Reset to beginning of file
fread(buffer, sizeof(buffer), 1, fptr);

Or using fseek():

c
fprintf(fptr, "Hello, World!");
fseek(fptr, 0, SEEK_SET); // Seek to offset 0 from beginning
fread(buffer, sizeof(buffer), 1, fptr);

Solution 2: Proper Buffer Handling

Even after fixing the positioning issue, there are additional considerations for proper buffer management:

Correct fread() Usage

c
// Problematic usage:
fread(buffer, sizeof(buffer), 1, fptr); // Reads 1 element of sizeof(buffer) bytes

// Recommended usage:
size_t length = fread(buffer, 1, sizeof(buffer), fptr); // Reads sizeof(buffer) elements of 1 byte

The second approach is better because:

  • It returns the actual number of bytes read
  • It handles partial reads correctly

Safe String Output

Buffer Safety

fread() does not null-terminate the buffer, which can cause issues when using printf("%s", buffer).

Use one of these safe approaches:

c
// Option 1: Null-terminate manually
size_t length = fread(buffer, 1, sizeof(buffer) - 1, fptr);
buffer[length] = '\0';
printf("%s", buffer);

// Option 2: Use precision specifier
size_t length = fread(buffer, 1, sizeof(buffer), fptr);
printf("%.*s", (int)length, buffer);

// Option 3: Use fwrite for binary-safe output
size_t length = fread(buffer, 1, sizeof(buffer), fptr);
fwrite(buffer, 1, length, stdout);

Solution 3: Alternative Reading Methods

For text files, consider using fgets() which handles null termination automatically:

c
if (fgets(buffer, sizeof(buffer), fptr)) {
  printf("%s", buffer);
}

Solution 4: POSIX Alternative (Linux/macOS/BSD)

For systems that support POSIX APIs, you can use unbuffered I/O:

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

int main() {
    const int fd = open("mem.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("open");
        return -1;
    }
    
    const char text[] = "Hello, World!";
    const ssize_t writtenSize = pwrite(fd, text, sizeof(text) - 1, 0);
    
    char buffer[sizeof(text)];
    const ssize_t readSize = pread(fd, buffer, sizeof(buffer) - 1, 0);
    
    buffer[readSize] = 0;
    puts(buffer);
    
    close(fd);
    return 0;
}

Portability Warning

POSIX functions like pread() and pwrite() are not standard C and may not work on all systems (e.g., Windows).

Complete Working Example

Here's a robust implementation that handles all the discussed issues:

c
#include <stdio.h>
#include <stdlib.h>

#define SIZE 256

int main() {
    FILE *fptr = fopen("mem.txt", "w+");
    char buffer[SIZE];
    
    if (!fptr) {
        perror("Failed to open file");
        return EXIT_FAILURE;
    }
    
    printf("Creation successful\n");
    
    // Write to file
    const char *text = "Hello, World!";
    fprintf(fptr, "%s", text);
    
    // Reposition to beginning
    rewind(fptr);
    
    // Read with proper error checking
    size_t bytes_read = fread(buffer, 1, sizeof(buffer) - 1, fptr);
    if (ferror(fptr)) {
        perror("Error reading file");
        fclose(fptr);
        return EXIT_FAILURE;
    }
    
    // Null-terminate and print
    buffer[bytes_read] = '\0';
    printf("Read content: %s\n", buffer);
    
    fclose(fptr);
    return EXIT_SUCCESS;
}

Key Takeaways

  1. Always reposition the file pointer between write and read operations using rewind(), fseek(), or fsetpos()
  2. Check return values of all I/O operations for errors
  3. Properly handle buffering - either null-terminate strings or use appropriate output methods
  4. Consider using fgets() for text files as it handles null termination automatically
  5. Be cautious with platform-specific APIs if portability is a concern

By following these practices, you can avoid common file I/O pitfalls and create robust C programs that handle file operations correctly.