File Handling · Lesson 32 of 35
Binary Files and Random Access in C
Text files are easy to read but slow to search: to find record 500 you must read the 499 before it. A binary file stores data exactly as it sits in memory. With fixed-size records you can jump straight to any record with fseek – the idea behind databases and the classic "student record management" mini project.
In this lesson
- Writing and reading structures with
fwriteandfread - Jumping to a record with
fseekand finding size withftell - Updating a record in place
- Text vs binary files
Writing and reading records
#include <stdio.h>
typedef struct {
int roll;
char name[20];
float marks;
} Student;
int main(void) {
Student cls[] = {{1, "Asha", 88.5f}, {2, "Ravi", 74.0f}, {3, "Meera", 91.0f}, {4, "Kiran", 66.5f}};
int n = sizeof cls / sizeof cls[0];
FILE *fp = fopen("students.dat", "wb");
if (fp == NULL) { perror("students.dat"); return 1; }
size_t written = fwrite(cls, sizeof cls[0], (size_t)n, fp); // the whole array in one call
fclose(fp);
printf("wrote %zu records of %zu bytes\n", written, sizeof(Student));
fp = fopen("students.dat", "rb");
if (fp == NULL) { perror("students.dat"); return 1; }
Student s;
while (fread(&s, sizeof s, 1, fp) == 1) { // one record at a time
printf("%d %-6s %.1f\n", s.roll, s.name, s.marks);
}
fclose(fp);
return 0;
}
wrote 4 records of 28 bytes 1 Asha 88.5 2 Ravi 74.0 3 Meera 91.0 4 Kiran 66.5
fwrite(ptr, size, count, fp) writes count items of size bytes; fread reads the same way and returns how many complete items it read – loop while it returns what you asked for.
Random access with fseek and ftell
#include <stdio.h>
typedef struct { int roll; char name[20]; float marks; } Student;
int main(void) {
Student cls[] = {{1, "Asha", 88.5f}, {2, "Ravi", 74.0f}, {3, "Meera", 91.0f}, {4, "Kiran", 66.5f}};
FILE *fp = fopen("students.dat", "wb");
if (fp == NULL) return 1;
fwrite(cls, sizeof cls[0], 4, fp);
fclose(fp);
fp = fopen("students.dat", "r+b"); // read and update
if (fp == NULL) return 1;
fseek(fp, 0, SEEK_END); // jump to the end
long bytes = ftell(fp); // position = file size
printf("file size %ld bytes = %ld records\n", bytes, bytes / (long)sizeof(Student));
Student s;
fseek(fp, 2 * (long)sizeof(Student), SEEK_SET); // record #3 (index 2), no reading of 1 and 2
fread(&s, sizeof s, 1, fp);
printf("record 3: %s %.1f\n", s.name, s.marks);
s.marks = 95.0f; // update it in place
fseek(fp, -(long)sizeof(Student), SEEK_CUR); // step back one record
fwrite(&s, sizeof s, 1, fp);
rewind(fp);
while (fread(&s, sizeof s, 1, fp) == 1) printf("%d %-6s %.1f\n", s.roll, s.name, s.marks);
fclose(fp);
return 0;
}
file size 112 bytes = 4 records record 3: Meera 91.0 1 Asha 88.5 2 Ravi 74.0 3 Meera 95.0 4 Kiran 66.5
Finding record k (0-based)
- Offsetk × sizeof(Student) bytes from the start
- fseek(fp, offset, SEEK_SET)jump there directly
- fread one recordno need to read the earlier ones
| Function | Does |
|---|---|
fseek(fp, offset, SEEK_SET) | move to offset bytes from the start |
fseek(fp, offset, SEEK_CUR) | move relative to the current position |
fseek(fp, offset, SEEK_END) | move relative to the end |
ftell(fp) | current position in bytes |
rewind(fp) | back to the start |
Switching between reading and writing
In "r+" mode you must call fseek (or fflush) between a read and a following write – as the example does with the SEEK_CUR step back. Skipping it is undefined behaviour.
Text vs binary files
| Text file | Binary file | |
|---|---|---|
| Contents | human-readable characters | raw bytes, exactly as in memory |
| Size of 88.5 | 4 characters ("88.5") | 4 bytes (a float), always |
| Random access | hard – lines have different lengths | easy with fixed-size records |
| Portable between systems | yes | no – depends on sizes, padding and byte order |
| Good for | configs, logs, CSV exports | records, images, fast save/load |
Portability
A binary file written on one machine may not read correctly on another with different int sizes, struct padding or byte order (endianness). For data shared between systems, use text formats (CSV, JSON) or write each field with a fixed size and byte order.
Practice
Try it yourself
Write the numbers 1–10 as ints to a binary file, then read back and print only the 5th number using fseek.
Show solution
#include <stdio.h> int main(void) { FILE *fp = fopen("nums.bin", "w+b"); if (fp == NULL) return 1; for (int i = 1; i <= 10; i++) fwrite(&i, sizeof i, 1, fp); int x; fseek(fp, 4 * (long)sizeof x, SEEK_SET); fread(&x, sizeof x, 1, fp); printf("5th number: %d\n", x); fclose(fp); return 0; }5th number: 5
Quick check
What does fread return?
The number of complete items read.
How do you find a file's size in bytes?
fseek(fp, 0, SEEK_END); long size = ftell(fp);
Why are binary files fast for record lookups?
Fixed-size records let you compute any record's offset and fseek straight to it.
Summary
- fwrite/fread move raw bytes – whole structs or arrays at once (open with "wb"/"rb").
- fseek + ftell give random access; update records in place with "r+b".
- Binary is compact and fast but not portable; use text formats to share data.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.