C Language track

Lesson 12 · Level 4: Deep Dive

File I/O

File streams and the range of I/O functions

Level 4: Deep Dive4 min readFree lesson

When you close the program so far, all data is lost. File input/output allows you to save data to disk and read it again.

Learning Objectives

  • Open and close files with fopen/fclose.
  • Use text mode and binary mode separately.
  • Text input and output is performed using fprintf/fscanf/fgets.
  • Perform binary input/output with fread/fwrite.

Why is file input/output necessary? — "Memory is volatile"

During program execution After program termination
┌──────────────┐                    ┌──────────────┐
│ Variable a = 10 │ │ │
│ Array [...] │ ──── End ────► │ Disappear │
│ Structure │ │ │
└──────────────┘                    └──────────────┘
(RAM, volatile) (vulnerable to power outages and shutdowns)

If you save it to a disk file, you can read it on the next run. The basis for all persistent data, including configuration files, logs, data backups, and results reports.

Key concepts

1) Opening and closing files

FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
    perror("Open failed");
    return 1;
}
/*...use...*/
fclose(fp);

fopen returns FILE * on success and NULL on failure. If not closed, operating system resources may leak + buffers may not be written to disk.

2) Mode string

mode Meaning If the file does not exist If the file exists
"r" Read failed (returns NULL) Read from beginning
"w" writing Create new After deleting content From the beginning
"a" Continue writing Create new append to end
"r+" Read+Write failure Open as is
"rb"/"wb" binary mode Same as above Same as above

3) Text input and output

Human-readable format. Patterns like one record per line are common.

fprintf(fp, "%s,%d\n", name, age);     //write
fscanf(fp, "%s %d", name, &age);       //read
fgets(buf, sizeof(buf), fp);            //Read one line (including spaces)

4) Binary input/output

Store the bytes in memory as is. Fast and precise but not human readable.

fwrite(&value, sizeof(value), 1, fp);
fread (&value, sizeof(value), 1, fp);
Memory (Record structure) File (data.bin)
┌──────────────────┐              ┌──────────────────┐
│ id: 1            │   fwrite     │ 01 00 00 00      │
│ name: "Kim..."   │  ────────►   │ 4B 69 6D 00 ...  │
│ score: 90.5      │              │ 00 00 00 00 ...  │
└──────────────────┘              └──────────────────┘

5) End of file (EOF) checking

char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
    /*Process line by line*/
}

The standard recommendation is to judge by the return value of the read function rather than feof(fp).

See as example

Example 1 — ex01_write_text.c: Write CSV format text

fprintf(fp, "Name, score\n");
fprintf(fp, "%s,%d\n", "Kim",  90);

Execution results (console)

Completed writing output.txt

output.txt

name, score
Kim,90
Lee,85
Park,78

Bottom line: Be careful because "w" mode deletes existing content.

Example 2 — ex02_read_text.c: Read line by line

while (fgets(line, sizeof(line), fp) != NULL) {
    printf("%2d: %s", ln++, line);
}

Execution result

1: Name, score
 2: Kim,90
 3: Lee,85
 4: Park,78

Key point: fgets reads up to the line break \n.

Example 3 — ex03_append.c: Continue (log)

FILE *fp = fopen("log.txt", "a");
fprintf(fp, "[%ld] Program execution history\n", (long)time(NULL));

Execution result (log.txt after running twice)

[1778403024] Program execution history
[1778403024] Program execution history

Bottom line: The "a" mod appends to the end, so existing content is preserved.

Example 4 — ex04_binary.c: Structure to binary

typedef struct { int id; char name[32]; double score; } Record;
fwrite(out, sizeof(Record), n, fp);
fread (in,  sizeof(Record), n, fp);

Execution result

Number of records read: 3
1 Kim    90.50
2 Lee    85.00
3 Park   77.50

Key point: Only programs that use the same structure definition can be safely exchanged. If you move To-OS/Compiler to another, it may be broken due to differences in padding and endianness.

A different perspective — text vs binary

Item Text Mode ("r"/"w") Binary mode ("rb"/"wb")
person reading possible Not possible
Save integer 1234 "1234" (4 byte characters) 0xD2 0x04 0x00 0x00 (4 bytes)
Line break handling Conversion by OS No conversion
Compatibility Very good Same environment recommended
speed/size Large Small and fast

Text is natural for configuration files, and binary is natural for game save data.

Common Mistakes

  1. Missing fopen result check: There is no file, but fp is used as is → Segfault.
  2. fclose missing: Data may not appear to be written to disk (buffering).
  3. Misuse of "w" mode: Unintentionally losing existing data.
  4. Relative path confusion: Find out where your current working directory is (pwd or getcwd).
  5. Compare binary files with text tools: Tools like diff are not suitable for binary files. Using cmp.

Cleanup

  • fopen-fclose are paired, and result NULL check is required.
  • Text mode is easier on the eyes, and binary mode is fast and accurate.
  • fprintf/fscanf/fgets are text, fwrite/fread are binary.
  • "w" is overwritten, "a" is continued.
  • It is safe to read and write binary files in the same environment (Compiler/OS).

Try it yourself

cd src
gcc -std=c11 -Wall -o ex01 ex01_write_text.c && ./ex01 && cat output.txt
gcc -std=c11 -Wall -o ex02 ex02_read_text.c  && ./ex02
gcc -std=c11 -Wall -o ex03 ex03_append.c     && ./ex03 && ./ex03 && cat log.txt
gcc -std=c11 -Wall -o ex04 ex04_binary.c     && ./ex04

Application:

  • After running ex01, open output.txt with a text editor, add lines directly, and run ex02.
  • Try to see if adding a new member to Record of ex04 can read the existing data.bin.

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

src/ex01_write_text.cC
#include <stdio.h>

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

    fprintf(fp, "Name, score\n");
    fprintf(fp, "%s,%d\n", "Kim",  90);
    fprintf(fp, "%s,%d\n", "Lee",  85);
    fprintf(fp, "%s,%d\n", "Park", 78);

    fclose(fp);
    printf("output.txt completed\n");
    return 0;
}
src/ex02_read_text.cC
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("output.txt", "r");
    if (!fp) {
        perror("output.txt");
        printf("Execute ex01_write_text first.\n");
        return 1;
    }

    char line[256];
    int ln = 1;
    while (fgets(line, sizeof(line), fp) != NULL) {
        printf("%2d: %s", ln++, line);
    }
    fclose(fp);
    return 0;
}
src/ex03_append.cC
#include <stdio.h>
#include <time.h>

int main(void) {
    FILE *fp = fopen("log.txt", "a");
    if (!fp) { perror("log.txt"); return 1; }

    time_t t = time(NULL);
    fprintf(fp, "[%ld] Program execution history\n", (long)t);

    fclose(fp);
    printf("One line added to log.txt\n");
    return 0;
}
src/ex04_binary.cC
#include <stdio.h>

typedef struct {
    int  id;
    char name[32];
    double score;
} Record;

int main(void) {
    Record out[] = {
        {1, "Kim",  90.5},
        {2, "Lee",  85.0},
        {3, "Park", 77.5},
    };
    int n = (int)(sizeof(out) / sizeof(out[0]));

    FILE *fp = fopen("data.bin", "wb");
    if (!fp) { perror("data.bin"); return 1; }
    fwrite(out, sizeof(Record), n, fp);
    fclose(fp);

    Record in[3];
    fp = fopen("data.bin", "rb");
    if (!fp) { perror("data.bin"); return 1; }
    size_t r = fread(in, sizeof(Record), n, fp);
    fclose(fp);

    printf("Number of records read: %zu\n", r);
    for (size_t i = 0; i < r; i++) {
        printf("%d %-6s %.2f\n", in[i].id, in[i].name, in[i].score);
    }
    return 0;
}

Assignment

Problem 1

Enter 5 integers from the user and write them into numbers.txt, one per line.

  • File name: hw01.c

Problem 2

Read numbers.txt and print the sum and average.

  • File name: hw02.c

Problem 3

Each time you run, increment the integer counter stored in count.bin by 1 and print that value. If the file does not exist, it starts from 0.

First run: 1
Second: 2
Third: 3
  • File name: hw03.c
Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

hw01.cC
#include <stdio.h>

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

    printf("Enter 5 integers:");
    for (int i = 0; i < 5; i++) {
        int n;
        scanf("%d", &n);
        fprintf(fp, "%d\n", n);
    }
    fclose(fp);
    printf("numbers.txt saved\n");
    return 0;
}
hw02.cC
#include <stdio.h>

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

    int n, sum = 0, count = 0;
    while (fscanf(fp, "%d", &n) == 1) {
        sum += n;
        count++;
    }
    fclose(fp);

    if (count == 0) { printf("There is no data.\n"); return 0; }
    printf("Total: %d\n", sum);
    printf("Average: %.2f\n", (double)sum / count);
    return 0;
}
hw03.cC
#include <stdio.h>

int main(void) {
    int count = 0;

    FILE *fp = fopen("count.bin", "rb");
    if (fp) {
        fread(&count, sizeof(int), 1, fp);
        fclose(fp);
    }

    count++;

    fp = fopen("count.bin", "wb");
    if (!fp) { perror("count.bin"); return 1; }
    fwrite(&count, sizeof(int), 1, fp);
    fclose(fp);

    printf("Run count: %d\n", count);
    return 0;
}

Learn this lesson with a mentor

Work through the C Language track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now