CBinary Files

Binary Files

So far every file example has dealt with human-readable text. Binary mode instead reads and writes raw bytes exactly as they are laid out in memory — no text formatting, no interpretation, just a direct copy of bits to and from disk.

Text Mode vs Binary Mode

The difference is selected by adding b to the fopen mode string: "rb", "wb", "ab", "rb+", and so on, versus the plain "r", "w", "a" used for text.

Where this matters most
On Windows, text mode automatically translates between the `\\n` line ending used internally by C and the `\\r\\n` line ending Windows text files use on disk — silently rewriting bytes as they are read or written. On Unix-like systems (Linux, macOS) there is no such translation, so text and binary mode behave almost identically there. Even so, it is good practice to always open a file in binary mode explicitly whenever you intend to read or write raw binary data, so the code behaves identically on every platform.
fread() and fwrite()

C
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *fp);
size_t fread(void *ptr, size_t size, size_t count, FILE *fp);

Both functions move count items of size bytes each, as one raw block of memory — no text formatting, no interpretation of the bytes involved. This makes them the natural fit for saving and loading structs, arrays, and other fixed-layout data directly.

Worked Example: An Array of Structs

C
#include <stdio.h>

typedef struct {
    char name[20];
    int score;
} Player;

int main(void)
{
    Player players[3] = {
        {"Alice", 95},
        {"Bob", 87},
        {"Carla", 91},
    };

    /* Write all three structs, raw, to a binary file */
    FILE *fp = fopen("players.bin", "wb");
    if (fp == NULL) {
        printf("Could not open file for writing.\n");
        return 1;
    }
    fwrite(players, sizeof(Player), 3, fp);
    fclose(fp);

    /* Read them back into a fresh array */
    Player loaded[3];
    fp = fopen("players.bin", "rb");
    if (fp == NULL) {
        printf("Could not open file for reading.\n");
        return 1;
    }

    size_t itemsRead = fread(loaded, sizeof(Player), 3, fp);
    fclose(fp);

    for (size_t i = 0; i < itemsRead; i++) {
        printf("%s: %d\n", loaded[i].name, loaded[i].score);
    }

    return 0;
}
Alice: 95
Bob: 87
Carla: 91

fwrite copies the exact in-memory bytes of the players array to disk in one call, and fread copies them straight back into loaded — no parsing, no formatting, just a byte-for-byte round trip.

Aspect

Text I/O (fprintf/fscanf/fgets)

Binary I/O (fread/fwrite)

Format

Human-readable characters

Raw memory bytes

Speed

Slower — formatting/parsing overhead

Faster — direct memory copy

Portability of the data

Very portable (plain text)

Not guaranteed portable across machines

Typical use

Config files, logs, CSV, human-edited data

Saving/loading structs, images, custom binary formats

Binary formats are not automatically portable
A binary file written by one compiler, on one platform, is not guaranteed to read back correctly on another. Two things commonly break portability: **struct padding/alignment** (compilers can insert different amounts of padding between struct members depending on platform and settings, so `sizeof(Player)` is not guaranteed to match across machines), and **endianness** (multi-byte numbers like `int` can be stored least-significant-byte-first or most-significant-byte-first depending on the CPU architecture). These topics are covered in depth on the Memory Alignment & Padding and Endianness pages — keep them in mind before shipping a custom binary file format across different machines or compilers.
  • Use binary mode + fread/fwrite for fast, exact in-memory-layout persistence on a single platform/compiler combination

  • Use text mode for anything meant to be portable, human-readable, or edited by hand

  • For a binary format that must be portable, define an explicit byte layout (fixed-width types, defined endianness) rather than dumping raw structs