Binary file I/O
Read and write raw binary data in C using fread, fwrite, fseek, and ftell — including struct serialisation and portability considerations.
- 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 beginningSEEK_CUR— offset from current positionSEEK_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.
Reading and writing text files
Read and write line-oriented text files in C using fgets, fputs, fprintf, and fscanf — with examples for line processing and formatted output.
Error handling with errno
Understand how C reports errors through errno and return values — using perror, strerror, and building robust error-handling patterns.