ccompiler.inOnline C Compiler & Docs
Engineering Blog2026-08-147 min read

Complete guide to file handling in C using fopen, fclose, fread, fwrite, fgets, and fprintf. Includes robust error handling and file modes.

In C programming, all data stored in variables, arrays, and dynamic heap structures resides in volatile RAM and disappears the instant your application terminates.

To persist information permanently across runs, load configuration files, or process large datasets, you must use File Handling (Input/Output file streams).

In this comprehensive guide, you will master file handling in C from fundamental text read/write operations to binary struct serialization. We will explore stream pointers (FILE *), file access modes ("r", "w", "rb", "wb"), buffer safety with fgets(), random file positioning with fseek(), and bulletproof error handling with perror().


1. Quick Reference: File Access Modes in C

In C, files are accessed through a file stream handle of type FILE * created by the fopen() function:

c (ISO Standard)
FILE *fp = fopen("data.txt", "mode");
ModeStream TypeAction if File ExistsAction if File Does Not ExistFile Position
"r"Read TextOpens for readingReturns NULL (Error)Beginning
"w"Write TextTruncates (erases) file to 0 bytesCreates new fileBeginning
"a"Append TextPreserves existing data, writes to endCreates new fileEnd of file
"r+"Read/Write TextOpens for reading and writingReturns NULL (Error)Beginning
"w+"Read/Write TextTruncates existing fileCreates new fileBeginning
"a+"Read/Append TextPreserves existing dataCreates new fileEnd of file
"rb"Read BinaryOpens binary stream for readingReturns NULL (Error)Beginning
"wb"Write BinaryTruncates binary fileCreates new binary fileBeginning
"ab"Append BinaryAppends binary data to endCreates new binary fileEnd of file

2. The 4 Golden Steps of File Handling

Every robust file operation in C follows a 4-step sequence:

text (ISO Standard)
 1. Open Stream (fopen) 
          |
          v
 2. Validate Pointer (!fp -> perror)
          |
          v
 3. Perform I/O (fgets / fprintf / fread / fwrite)
          |
          v
 4. Close Stream (fclose)

Step 1 & 2: Open and Validate

Never assume fopen() succeeded. Always check for NULL to prevent segmentation faults:

c (ISO Standard)
#include <stdio.h>
#include <stdlib.h>

FILE *fp = fopen("config.txt", "r");
if (fp == NULL) {
    perror("Error opening file"); // Prints exact OS error (e.g., No such file or directory)
    return EXIT_FAILURE;
}

Step 4: Always Close Streams

Calling fclose(fp) flushes internal I/O buffers to physical disk and releases the operating system file descriptor handle.


3. Text File Operations: Reading & Writing


Writing Formatted Text: fprintf() and fputs()

c (ISO Standard)
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("output.txt", "w");
    if (!fp) {
        perror("Failed to open output.txt");
        return 1;
    }

    // Write formatted strings directly to file stream
    fprintf(fp, "Report Generated: %s\n", "2026-08-14");
    fprintf(fp, "Score: %d | Precision: %.2f\n", 98, 99.45);

    fclose(fp);
    printf("Successfully wrote to output.txt\n");
    return 0;
}

Safe Line-by-Line Reading: fgets()

Avoid dangerous functions like gets() (removed in C11) or unconstrained fscanf(). Always use fgets() with bounded buffer lengths:

c (ISO Standard)
#include <stdio.h>

#define BUFFER_SIZE 256

int main(void) {
    FILE *fp = fopen("output.txt", "r");
    if (!fp) {
        perror("Failed to open output.txt");
        return 1;
    }

    char line[BUFFER_SIZE];

    // fgets returns NULL upon reaching EOF or encountering an error
    while (fgets(line, sizeof(line), fp) != NULL) {
        printf("Read Line: %s", line);
    }

    fclose(fp);
    return 0;
}

4. Binary File Operations: fwrite() and fread()

When working with binary data, images, audio, or complex C structures, text serialization is slow and wastes space. Binary I/O reads and writes raw memory bytes directly.

Function Signatures:

c (ISO Standard)
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);

Saving and Loading Structs to Disk: Complete Example

c (ISO Standard)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int id;
    char name[32];
    float gpa;
} StudentRecord;

void saveStudent(const char *filename, const StudentRecord *student) {
    FILE *fp = fopen(filename, "wb"); // Write Binary
    if (!fp) {
        perror("Error opening file for write");
        return;
    }

    // Write 1 struct directly from RAM to disk
    fwrite(student, sizeof(StudentRecord), 1, fp);
    fclose(fp);
    printf("Student record saved successfully.\n");
}

void loadStudent(const char *filename, StudentRecord *student) {
    FILE *fp = fopen(filename, "rb"); // Read Binary
    if (!fp) {
        perror("Error opening file for read");
        return;
    }

    // Read 1 struct directly from disk into RAM
    if (fread(student, sizeof(StudentRecord), 1, fp) == 1) {
        printf("Loaded: ID=%d, Name=%s, GPA=%.2f\n", 
               student->id, student->name, student->gpa);
    }

    fclose(fp);
}

int main(void) {
    StudentRecord s1 = { .id = 101, .name = "Alice Smith", .gpa = 3.92f };
    StudentRecord s2;

    const char *dbFile = "student.bin";

    saveStudent(dbFile, &s1);
    loadStudent(dbFile, &s2);

    return 0;
}

5. Random File Access: fseek(), ftell(), and rewind()

By default, file reading is sequential. You can jump to arbitrary byte offsets inside a file using fseek():

c (ISO Standard)
int fseek(FILE *stream, long offset, int origin);

Origin Constants:

  • SEEK_SET: Offset relative to the beginning of the file.
  • SEEK_CUR: Offset relative to the current position pointer.
  • SEEK_END: Offset relative to the end of the file.

Professional Trick: Calculating Exact File Size in Bytes

c (ISO Standard)
#include <stdio.h>

long getFileSizeBytes(const char *filename) {
    FILE *fp = fopen(filename, "rb");
    if (!fp) return -1;

    // Seek to the end of the file
    fseek(fp, 0, SEEK_END);

    // Get current byte position (total size)
    long size = ftell(fp);

    fclose(fp);
    return size;
}

6. Common Pitfall: Why while (!feof(fp)) is a Bug

One of the most widespread bugs in C programming is writing:

c (ISO Standard)
// ⚠️ WRONG: feof() only returns true AFTER a read has ALREADY failed!
while (!feof(fp)) {
    fgets(buffer, sizeof(buffer), fp);
    printf("%s", buffer); // Duplicates the last line twice!
}

The Correct Pattern: Check the Return Value of the Read Call

c (ISO Standard)
// GOOD: Directly inspect the return status of fgets / fread / fscanf
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
    printf("%s", buffer);
}

Frequently Asked Questions (FAQ)

1. What is the difference between Text mode and Binary mode on Windows?

On Linux and macOS, text and binary modes are identical. On Windows systems, text mode automatically translates newline characters (\n) into Carriage Return + Line Feed (\r\n) during write operations, and translates \r\n back to \n during reads. In binary mode ("rb", "wb"), bytes are transferred identically with zero translation.

2. What happens if you forget to call fclose()?

If a program crashes or terminates abnormally without calling fclose(), data remaining in the C standard library's memory buffer may never be written to disk, leading to corrupted or incomplete files. Additionally, the process leaks operating system file descriptors until termination.

3. How do you delete or rename a file in C?

Standard C <stdio.h> provides built-in system utility functions:

  • remove("old_file.txt"); — Deletes the specified file. Returns 0 on success.
  • rename("old_name.txt", "new_name.txt"); — Renames or moves a file. Returns 0 on success.

Conclusion

File handling is an essential skill for turning transient in-memory computations into durable, production-grade applications. By structuring operations around the 4 golden steps, validating pointers with perror(), using safe reading functions like fgets() and fread(), and managing file offsets with fseek(), you can handle complex file storage requirements with total confidence.


Related Articles & References