C File I/O and Streams
40 minC provides comprehensive file I/O capabilities through both high-level stream functions (stdio.h) and low-level system calls. File I/O enables programs to persist data, read configuration files, process data files, and interact with the filesystem. Understanding file I/O is essential for building practical C applications. File operations include opening, reading, writing, positioning, and closing files.
File streams (FILE*) provide buffered I/O, improving performance by reducing system calls. Text streams handle line endings and character encoding, while binary streams handle raw bytes. Stream functions include fprintf()/fscanf() for formatted I/O, fgetc()/fputc() for character I/O, and fgets()/fputs() for line I/O. Understanding streams enables efficient file operations.
Binary file operations use fread() and fwrite() to read and write raw bytes. Binary files are more efficient for structured data and are platform-independent. Binary I/O requires knowing the exact data layout and endianness. Understanding binary I/O enables efficient data storage and retrieval.
File positioning functions (fseek(), ftell(), rewind()) enable random access to files. fseek() moves the file position, ftell() returns the current position, and rewind() resets to the beginning. Random access enables efficient file operations when you need to read or write at specific positions. Understanding file positioning enables advanced file operations.
Error handling is crucial for file operations. Always check if fopen() returns NULL, verify read/write success by checking return values, and use errno and perror() for error messages. File operations can fail due to permissions, disk space, or missing files. Understanding error handling prevents program crashes and data loss.
Buffering affects file I/O performance. Streams are buffered by default, reducing system calls. fflush() forces buffer write, setvbuf() controls buffering mode, and setbuf() sets buffer. Understanding buffering enables performance optimization for file operations.
Key Concepts
- File streams provide buffered I/O for efficiency.
- Text and binary streams handle data differently.
- fread() and fwrite() enable binary file operations.
- File positioning enables random access.
- Error handling is essential for reliable file operations.
Learning Objectives
Master
- Using file streams for text and binary I/O
- Implementing error handling for file operations
- Using file positioning for random access
- Understanding buffering and performance
Develop
- Understanding file I/O patterns
- Implementing robust file handling
- Optimizing file I/O performance
Tips
- Check fopen() return: if (file == NULL) handle error.
- Use binary mode for structured data: "rb", "wb" for efficiency.
- Check read/write success: verify fread()/fwrite() return values.
- Use fseek() for random access: fseek(file, offset, SEEK_SET).
Common Pitfalls
- Not checking file operations, causing crashes on errors.
- Not closing files, causing resource leaks.
- Mixing text and binary modes, corrupting data.
- Not handling partial reads/writes, causing data loss.
Summary
- File I/O enables data persistence and file processing.
- Streams provide efficient buffered I/O.
- Error handling is essential for reliable file operations.
- Understanding file I/O enables practical C applications.
Exercise
Create a program that demonstrates various file I/O operations and error handling.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
// File operation result structure
typedef struct {
int success;
char error_message[256];
} FileResult;
// Safe file open with error handling
FileResult safeOpen(const char *filename, const char *mode) {
FileResult result = {0, ""};
FILE *file = fopen(filename, mode);
if (!file) {
result.success = 0;
snprintf(result.error_message, sizeof(result.error_message),
"Failed to open %s: %s", filename, strerror(errno));
return result;
}
result.success = 1;
return result;
}
// Copy file with progress
int copyFile(const char *source, const char *destination) {
FILE *src, *dst;
char buffer[4096];
size_t bytes_read, total_bytes = 0;
// Open source file
src = fopen(source, "rb");
if (!src) {
perror("Error opening source file");
return 0;
}
// Open destination file
dst = fopen(destination, "wb");
if (!dst) {
perror("Error opening destination file");
fclose(src);
return 0;
}
// Copy data
while ((bytes_read = fread(buffer, 1, sizeof(buffer), src)) > 0) {
if (fwrite(buffer, 1, bytes_read, dst) != bytes_read) {
perror("Error writing to destination file");
fclose(src);
fclose(dst);
return 0;
}
total_bytes += bytes_read;
printf("Copied %zu bytes...
", total_bytes);
}
printf("\nFile copied successfully! Total bytes: %zu\n", total_bytes);
fclose(src);
fclose(dst);
return 1;
}
// Binary file operations
typedef struct {
int id;
char name[50];
float score;
} Student;
int writeStudentsToFile(const char *filename, Student *students, int count) {
FILE *file = fopen(filename, "wb");
if (!file) {
perror("Error opening file for writing");
return 0;
}
// Write count first
if (fwrite(&count, sizeof(int), 1, file) != 1) {
perror("Error writing count");
fclose(file);
return 0;
}
// Write students
if (fwrite(students, sizeof(Student), count, file) != count) {
perror("Error writing students");
fclose(file);
return 0;
}
fclose(file);
printf("Written %d students to %s\n", count, filename);
return 1;
}
Student* readStudentsFromFile(const char *filename, int *count) {
FILE *file = fopen(filename, "rb");
if (!file) {
perror("Error opening file for reading");
return NULL;
}
// Read count
if (fread(count, sizeof(int), 1, file) != 1) {
perror("Error reading count");
fclose(file);
return NULL;
}
// Allocate memory for students
Student *students = malloc(*count * sizeof(Student));
if (!students) {
perror("Error allocating memory");
fclose(file);
return NULL;
}
// Read students
if (fread(students, sizeof(Student), *count, file) != *count) {
perror("Error reading students");
free(students);
fclose(file);
return NULL;
}
fclose(file);
printf("Read %d students from %s\n", *count, filename);
return students;
}
// Text file processing
int processTextFile(const char *filename) {
FILE *file = fopen(filename, "r");
if (!file) {
perror("Error opening text file");
return 0;
}
char line[1024];
int line_count = 0, word_count = 0, char_count = 0;
while (fgets(line, sizeof(line), file)) {
line_count++;
char_count += strlen(line);
// Count words in line
char *token = strtok(line, " \t\n");
while (token) {
word_count++;
token = strtok(NULL, " \t\n");
}
}
printf("File statistics:\n");
printf(" Lines: %d\n", line_count);
printf(" Words: %d\n", word_count);
printf(" Characters: %d\n", char_count);
fclose(file);
return 1;
}
// Random access file operations
int updateStudentScore(const char *filename, int student_id, float new_score) {
FILE *file = fopen(filename, "r+b");
if (!file) {
perror("Error opening file for update");
return 0;
}
// Skip count
fseek(file, sizeof(int), SEEK_SET);
Student student;
int found = 0;
// Search for student
while (fread(&student, sizeof(Student), 1, file) == 1) {
if (student.id == student_id) {
// Update score
student.score = new_score;
// Go back and write updated student
fseek(file, -sizeof(Student), SEEK_CUR);
if (fwrite(&student, sizeof(Student), 1, file) != 1) {
perror("Error updating student");
fclose(file);
return 0;
}
found = 1;
break;
}
}
fclose(file);
if (found) {
printf("Updated score for student %d to %.2f\n", student_id, new_score);
return 1;
} else {
printf("Student %d not found\n", student_id);
return 0;
}
}
int main() {
printf("=== File I/O Operations Demo ===\n\n");
// Create sample students
Student students[] = {
{1, "Alice Johnson", 95.5},
{2, "Bob Smith", 87.2},
{3, "Charlie Brown", 92.8},
{4, "Diana Prince", 89.1},
{5, "Eve Wilson", 94.3}
};
int student_count = 5;
// Write students to binary file
printf("1. Writing students to binary file...\n");
if (writeStudentsToFile("students.bin", students, student_count)) {
printf("Success!\n\n");
}
// Read students from binary file
printf("2. Reading students from binary file...\n");
int read_count;
Student *read_students = readStudentsFromFile("students.bin", &read_count);
if (read_students) {
printf("Students read:\n");
for (int i = 0; i < read_count; i++) {
printf(" %d: %s (Score: %.2f)\n",
read_students[i].id, read_students[i].name, read_students[i].score);
}
printf("\n");
free(read_students);
}
// Update a student's score
printf("3. Updating student score...\n");
updateStudentScore("students.bin", 2, 90.0);
printf("\n");
// Create a text file
printf("4. Creating sample text file...\n");
FILE *text_file = fopen("sample.txt", "w");
if (text_file) {
fprintf(text_file, "This is a sample text file.\n");
fprintf(text_file, "It contains multiple lines of text.\n");
fprintf(text_file, "We will process this file to count words and characters.\n");
fclose(text_file);
printf("Text file created!\n\n");
}
// Process text file
printf("5. Processing text file...\n");
processTextFile("sample.txt");
printf("\n");
// Copy file
printf("6. Copying file...\n");
copyFile("students.bin", "students_backup.bin");
printf("\n");
// Clean up
remove("students.bin");
remove("students_backup.bin");
remove("sample.txt");
printf("Temporary files cleaned up.\n");
return 0;
}
Exercise Tips
- Use binary mode for structured data: "rb", "wb" for efficiency.
- Check all file operations: verify fread()/fwrite() return values.
- Use fseek() for random access: fseek(file, offset, SEEK_SET).
- Handle errors: use errno and perror() for error messages.