Back to Curriculum

C File I/O and Streams

📚 Lesson 13 of 15 ⏱️ 40 min

C File I/O and Streams

40 min

C 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.

Code Editor

Output