Interleaved Read/Write Restrictions in C

The Problem

When you open a file in C with read-write mode (like r+, w+, or a+), you cannot directly switch between reading and writing without calling synchronization functions in between. Violating this rule leads to undefined behavior that can corrupt your file or give incorrect data.

Standard I/O functions use an internal buffer in memory instead of talking directly to the disk every time. When a file is opened in read-write mode, the system maintains two separate position markers: an R pointer for reading and a W pointer for writing. If you read some data (moving the R pointer forward) and then immediately write, the W pointer might still be pointing somewhere else entirely. The two pointers become inconsistent, and the system doesn’t know where you actually meant to operate.

The Synchronization Rules

You must call one of these functions when switching between operations:

After writing, before reading: use fflush(), fseek(), fsetpos(), or rewind()

After reading, before writing: use fseek(), fsetpos(), rewind(), or read until EOF

The most common is fseek(fp, 0, SEEK_CUR), which seeks to the current position. This forces the system to synchronize its internal state and coordinate the R and W pointers.

Example: Wrong vs Right

Suppose we have a file “ABCDEFGHIJ” and want to read “ABCDE”, overwrite “FGH” with “XXX”, then read “IJ”.

Wrong way (causes undefined behavior):

FILE *fp = fopen("test.txt", "r+");
char buffer[20];
 
fread(buffer, 1, 5, fp);  // Read "ABCDE"
fwrite("XXX", 1, 3, fp);  // BUG: No synchronization before writing!
fread(buffer, 1, 2, fp);  // BUG: No synchronization before reading!
 
fclose(fp);

Correct way:

FILE *fp = fopen("test.txt", "r+");
char buffer[20];
 
fread(buffer, 1, 5, fp);       // Read "ABCDE"
fseek(fp, 0, SEEK_CUR);        // Synchronize before switching to write
fwrite("XXX", 1, 3, fp);       // Write "XXX"
fseek(fp, 0, SEEK_CUR);        // Synchronize before switching to read
fread(buffer, 1, 2, fp);       // Read "IJ"
 
fclose(fp);

What Goes Wrong Without Synchronization

When you call fread() to read 5 bytes, the system actually reads a larger chunk (maybe 1024 bytes) into its internal buffer for efficiency. The R pointer moves to position 5, and your buffer gets “ABCDE”.

Now when you immediately call fwrite("XXX", 1, 3, fp) without synchronizing, the W pointer hasn’t been told where the R pointer ended up. This causes several possible problems:

The W pointer might still be at position 0, so “XXX” overwrites “ABC” instead of “FGH”, giving you “XXXDEFGHIJ” instead of “ABCDEXXXIJ”. Or the system might get confused about which buffer to use, leading to inconsistent data in memory. Or the write might appear to work, but when you read next, you get stale data from the old read buffer that doesn’t reflect what you just wrote.

The worst part is that your program might work correctly most of the time and fail unpredictably, making this bug extremely difficult to track down. You might expect “ABCDEXXXIJ” but get “ABCDEXXXHIJ” with missing letters, or “XXXDEFGHIJ” with data at the wrong position entirely.

Why This Happens

Standard I/O maintains both a buffer in memory and file position information. When you read, it fills the buffer from disk and updates the read position. When you write, it needs to know where to put data in the buffer and where that buffer should be written to disk. Without synchronization, these positions get out of sync, the read buffer might have old cached data, and the system has no way to know which information is correct.

Think of it like two people working on the same document where one is reading from paragraph 3 and another starts writing at paragraph 1, but they never communicated. The result is a mess because they’re not coordinating their work. The synchronization functions force the system to pause, flush pending writes, update file positions, and ensure both pointers are consistent before you switch operations.

Key Takeaways

Always synchronize when switching between reading and writing in read-write mode. Use fseek(fp, 0, SEEK_CUR) as the simplest method. Without proper synchronization, you get undefined behavior that corrupts files, produces wrong data, or crashes unpredictably. This is a subtle C rule that causes difficult-to-debug problems, so always be vigilant when working with read-write files.