Binary Files
Text files are human-readable. You can open them in Notepad. But sometimes you want to save data exactly as it exists in memory.
That’s what binary files are for.
Why Binary Files?
Text files store everything as readable characters. The number 1000000 takes 7 characters (1, 0, 0, 0, 0, 0, 0).
Binary files store data as raw bytes. That same number takes just 4 bytes (the same space as in memory).
Benefits of binary:
- Faster - No conversion to/from text
- Smaller - Numbers take less space
- Exact - No rounding errors with decimals
Downsides:
- Not human-readable (looks like garbage in Notepad)
- Might not work on different types of computers
Writing Binary Data
Use fwrite to write binary data:
#include <stdio.h>
int main(void) {
int numbers[] = {10, 20, 30, 40, 50};
int count = sizeof(numbers) / sizeof(numbers[0]);
FILE *file = fopen("data.bin", "wb"); // "wb" = write binary
if (file == NULL) return 1;
fwrite(numbers, sizeof(int), count, file);
fclose(file);
printf("Saved %d numbers to binary file\n", count);
return 0;
}fwrite(data, size, count, file) means:
- Write from
data - Each item is
sizebytes - Write
countitems - Write to
file
Reading Binary Data
Use fread to read binary data:
#include <stdio.h>
int main(void) {
int numbers[5];
FILE *file = fopen("data.bin", "rb"); // "rb" = read binary
if (file == NULL) return 1;
int count = fread(numbers, sizeof(int), 5, file);
fclose(file);
printf("Read %d numbers: ", count);
for (int i = 0; i < count; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
return 0;
}fread returns how many items it actually read. Check this to know if you got everything.
Saving Structs to Binary Files
Binary files are perfect for saving structs:
#include <stdio.h>
typedef struct {
char name[50];
int age;
double gpa;
} Student;
int main(void) {
Student students[] = {
{"Alice", 15, 3.8},
{"Bob", 16, 3.2},
{"Charlie", 15, 3.9}
};
int count = sizeof(students) / sizeof(students[0]);
// Save
FILE *file = fopen("students.bin", "wb");
if (file == NULL) return 1;
fwrite(&count, sizeof(int), 1, file); // Save count first
fwrite(students, sizeof(Student), count, file);
fclose(file);
printf("Saved %d students\n", count);
// Load
Student loaded[10];
int loaded_count;
file = fopen("students.bin", "rb");
if (file == NULL) return 1;
fread(&loaded_count, sizeof(int), 1, file); // Read count first
fread(loaded, sizeof(Student), loaded_count, file);
fclose(file);
printf("\nLoaded students:\n");
for (int i = 0; i < loaded_count; i++) {
printf(" %s, age %d, GPA %.1f\n",
loaded[i].name, loaded[i].age, loaded[i].gpa);
}
return 0;
}We save the count first so we know how many structs to read back.
File Position
Every open file has a “current position” - like a cursor. Reading and writing happen at this position and move it forward.
You can move this cursor around:
long pos = ftell(file); // Get current position
fseek(file, 0, SEEK_SET); // Go to beginning
fseek(file, 0, SEEK_END); // Go to end
fseek(file, 10, SEEK_CUR); // Move 10 bytes forward
rewind(file); // Same as fseek(file, 0, SEEK_SET)The fseek function takes three arguments:
- The file
- How far to move (in bytes)
- Where to measure from
Where to measure from:
SEEK_SET- from the beginning of the fileSEEK_CUR- from the current positionSEEK_END- from the end of the file
Getting File Size
FILE *file = fopen("data.bin", "rb");
fseek(file, 0, SEEK_END); // Go to end
long size = ftell(file); // Position at end = file size
fseek(file, 0, SEEK_SET); // Go back to start
printf("File size: %ld bytes\n", size);Complete Example: Simple Database
Here’s a program that acts like a tiny database:
#include <stdio.h>
#include <string.h>
typedef struct {
int id;
char name[50];
double balance;
} Account;
void save_accounts(Account *accounts, int count, const char *filename) {
FILE *file = fopen(filename, "wb");
if (file == NULL) {
perror("Error saving");
return;
}
fwrite(&count, sizeof(int), 1, file);
fwrite(accounts, sizeof(Account), count, file);
fclose(file);
}
int load_accounts(Account *accounts, int max, const char *filename) {
FILE *file = fopen(filename, "rb");
if (file == NULL) {
return 0; // No file yet
}
int count;
fread(&count, sizeof(int), 1, file);
if (count > max) count = max; // Safety check
fread(accounts, sizeof(Account), count, file);
fclose(file);
return count;
}
void print_accounts(Account *accounts, int count) {
printf("\n%-5s %-20s %10s\n", "ID", "Name", "Balance");
printf("--------------------------------------\n");
for (int i = 0; i < count; i++) {
printf("%-5d %-20s $%9.2f\n",
accounts[i].id, accounts[i].name, accounts[i].balance);
}
}
int main(void) {
const char *filename = "accounts.dat";
Account accounts[100];
int count = load_accounts(accounts, 100, filename);
printf("Loaded %d accounts\n", count);
print_accounts(accounts, count);
// Add a new account
Account new_acc = {count + 1, "", 0.0};
printf("\nNew account name: ");
scanf("%49s", new_acc.name);
printf("Initial balance: ");
scanf("%lf", &new_acc.balance);
accounts[count] = new_acc;
count++;
save_accounts(accounts, count, filename);
printf("Saved %d accounts\n", count);
return 0;
}Run it multiple times and watch your accounts persist!
Standard Streams
C gives you three “files” that are already open:
stdin- Standard input (your keyboard)stdout- Standard output (your screen)stderr- Standard error (also your screen, for errors)
printf("hello") is actually the same as fprintf(stdout, "hello").
Use stderr for error messages:
fprintf(stderr, "Something went wrong!\n");Why use stderr? Because normal output and error messages can be separated. When you run a program, you might send its output to a file but still see errors on screen.
Text vs Binary: Quick Reference
| Text Files | Binary Files | |
|---|---|---|
| Human readable | Yes | No |
| Functions | fprintf, fscanf, fgets | fwrite, fread |
| Mode | “r”, “w”, “a” | “rb”, “wb”, “ab” |
| Numbers | Stored as characters | Stored as bytes |
| Size | Larger | Smaller |
| Speed | Slower | Faster |
| Portable | Yes | Maybe not |
Try It Yourself
- Write a program that saves an array of 5 test scores to a binary file, then reads and displays them
- Modify the accounts database to let you update an account’s balance
- Write a program that copies a binary file byte by byte
- Write a program that shows the size of any file the user specifies
Common Mistakes
- Forgetting the “b” in the mode (“wb” vs “w”)
- Mixing text and binary operations on the same file
- Not saving the count before an array (so you know how many to read)
- Assuming binary files work across different computer types
Next Up
In Part 23, we’ll learn about networking and sockets - how to make programs talk to each other across a network.
Enjoyed This?
If this helped something click, subscribe to my YouTube channel. More content like this, same approach - making things stick without insulting your intelligence. It’s free, it helps more people find this stuff, and it tells me what’s worth making more of.