Code of the Day
IntermediateFile I/O

Binary file I/O

Read and write raw binary data in C using fread, fwrite, fseek, and ftell — including struct serialisation and portability considerations.

CIntermediate10 min read
By the end of this lesson you will be able to:
  • Write binary data with fwrite and read it back with fread
  • Use fseek and ftell to navigate within a file
  • Serialise a struct to a binary file and deserialise it
  • Explain the portability risks of binary struct serialisation

Text I/O is convenient for human-readable data, but binary I/O is essential for performance-sensitive data, binary file formats (images, audio, executables), and efficient storage of structured data. Binary I/O reads and writes raw bytes, with no newline translation or format parsing.

fwrite and fread

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 return the number of complete items (of size bytes each) successfully transferred.

Writing an array of ints

#include <stdio.h>

int main(void) {
    int numbers[] = {10, 20, 30, 40, 50};
    int n = 5;

    FILE *fp = fopen("numbers.bin", "wb"); /* b = binary mode */
    if (!fp) { perror("fopen"); return 1; }

    size_t written = fwrite(numbers, sizeof(int), n, fp);
    if (written != (size_t)n) {
        fprintf(stderr, "Write failed: wrote %zu of %d\n", written, n);
        fclose(fp);
        return 1;
    }

    fclose(fp);
    printf("Wrote %zu ints\n", written);
    return 0;
}

Reading it back

#include <stdio.h>

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

    int numbers[5];
    size_t read_count = fread(numbers, sizeof(int), 5, fp);
    fclose(fp);

    printf("Read %zu ints:", read_count);
    for (size_t i = 0; i < read_count; i++) {
        printf(" %d", numbers[i]);
    }
    printf("\n");
    return 0;
}

Serialising a struct

You can write a struct directly to a file as raw bytes:

#include <stdio.h>
#include <string.h>

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

int save_record(const Record *r, const char *filename) {
    FILE *fp = fopen(filename, "wb");
    if (!fp) { return -1; }
    fwrite(r, sizeof(Record), 1, fp);
    fclose(fp);
    return 0;
}

int load_record(Record *r, const char *filename) {
    FILE *fp = fopen(filename, "rb");
    if (!fp) { return -1; }
    size_t n = fread(r, sizeof(Record), 1, fp);
    fclose(fp);
    return (n == 1) ? 0 : -1;
}

int main(void) {
    Record r = { .id = 42, .score = 9.5f };
    snprintf(r.name, sizeof(r.name), "Alice");

    save_record(&r, "record.bin");

    Record loaded;
    if (load_record(&loaded, "record.bin") == 0) {
        printf("id=%d name=%s score=%.1f\n", loaded.id, loaded.name, loaded.score);
    }

    return 0;
}

Struct serialisation is not portable. The layout of a struct depends on alignment, padding, and endianness — all of which vary between compilers, architectures, and platforms. A file written on a little-endian x86 machine cannot be reliably read on a big-endian ARM machine (or vice versa) without explicit byte-order handling. For portable binary formats, define an exact byte layout and read/write field by field, or use a serialisation library.

Seeking within a file

int fseek(FILE *fp, long offset, int whence);
long ftell(FILE *fp);
void rewind(FILE *fp);

whence is one of:

  • SEEK_SET — offset from beginning
  • SEEK_CUR — offset from current position
  • SEEK_END — offset from end (usually to find file size)
/* Find file size */
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
rewind(fp); /* equivalent to fseek(fp, 0, SEEK_SET) */

printf("File size: %ld bytes\n", size);

Seeking to a specific record:

int index = 2; /* third record */
fseek(fp, index * sizeof(Record), SEEK_SET);
fread(&r, sizeof(Record), 1, fp);

Binary files with fixed-size records support O(1) random access — much faster than scanning a text file line by line.

Where to go next

Next: error handling with errno — how C propagates error codes from system calls, and how to build robust error-reporting patterns using perror and strerror.

Finished reading? Mark it complete to track your progress.

On this page