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:
#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
, orrewind
), 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()
:
fprintf(fptr, "Hello, World!");
rewind(fptr); // Reset to beginning of file
fread(buffer, sizeof(buffer), 1, fptr);
Or using fseek()
:
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
// 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:
// 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:
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:
#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:
#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
- Always reposition the file pointer between write and read operations using
rewind()
,fseek()
, orfsetpos()
- Check return values of all I/O operations for errors
- Properly handle buffering - either null-terminate strings or use appropriate output methods
- Consider using
fgets()
for text files as it handles null termination automatically - 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.