C/C++ Arena

Step 7 of 8

Binary files

So far every file has been text: fprintf(f, "%d", 1234) writes the characters 1, 2, 3, 4. A binary file stores the raw bytes of values exactly as they sit in memory instead: an int is always 4 bytes (on this site), whatever its value. Binary files are smaller, faster to read and write, and let you jump straight to the 1,000th record without reading the 999 before it. Images, audio, databases and save games are binary.

#include <stdio.h>

struct Score {
    char name[16];
    int points;
};

int main(void) {
    struct Score out[3] = {{"ada", 90}, {"linus", 75}, {"grace", 88}};
    FILE *f = fopen("scores.bin", "wb");
    if (f == NULL) return 1;
    size_t written = fwrite(out, sizeof out[0], 3, f);
    fclose(f);

    f = fopen("scores.bin", "rb");
    if (f == NULL) return 1;
    fseek(f, 0, SEEK_END);
    long bytes = ftell(f);                       /* position at the end = the file's size */
    fseek(f, sizeof(struct Score), SEEK_SET);    /* jump straight to record 1 */
    struct Score s;
    size_t got = fread(&s, sizeof s, 1, f);
    fclose(f);

    printf("wrote %zu records, %ld bytes\n", written, bytes);
    printf("record 1: %s %d (read %zu)\n", s.name, s.points, got);
    return 0;
}
wrote 3 records, 60 bytes
record 1: linus 75 (read 1)

How it works

Raw bytes aren't portable

Writing a struct's bytes directly is fine for a file your own program reads back on the same machine. It's not a safe file format for anything else:

Real file formats (PNG, ZIP, databases) define the exact size, order and byte order of every field, and programs write them field by field using fixed-size types like uint32_t (from <stdint.h>, covered in the integer types module).

Your turn: write three functions for a file of raw ints. save_ints writes n values and returns 0, or -1 if the file can't be opened or not everything was written. load_ints reads up to max values into out and returns how many it read, or -1 if the file can't be opened. read_at reads the value at position index into *out and returns 1, returns 0 if there's no value there, or -1 if the file can't be opened.

Previous: Safe formatting with snprintf Next: Challenge: a CSV report