More File Handling
Bytes, positions and errorsText files store numbers as digits; binary files store the bytes exactly as they are in memory. In the lab below you save the same number both ways, jump around a file with fseek, ftell and rewind, and copy a file safely — with proper error handling when the source is missing.
- fwrite / freadcopy raw bytes
- fseekjumps to a position
- perrorexplains what went wrong
Ready
Keyboard: ← → step · Space play / pause · R reset · Esc exit classroom mode
The ideas behind the lab
Exactly what you watched happen, in plain steps.
Text files vs binary files
A text file holds characters: the number 1000000 becomes seven digits, readable in any editor, and is written with fprintf and read with fscanf. A binary file holds the raw bytes from memory: fwrite copies sizeof(n) bytes as they are, and fread copies them back. Binary files are compact, exact and fast — ideal for arrays and structs — and with fseek you can jump straight to any record.
int n = 1000000; fprintf(text, "%d", n); // 7 bytes: '1' '0' '0' … fwrite(&n, sizeof n, 1, bin); // 4 bytes: 40 42 0f 00 fread(&n, sizeof n, 1, bin); // read it back fseek(bin, 0, SEEK_SET); // jump to the start long where = ftell(bin); // current position
| Call | What it does | Returns |
|---|---|---|
| fwrite(ptr, size, count, fp) | writes count items of size bytes from ptr | items written |
| fread(ptr, size, count, fp) | reads up to count items into ptr | items read (less at the end) |
| fseek(fp, offset, SEEK_SET) | moves to offset from the start | 0 on success |
| fseek(fp, offset, SEEK_CUR / SEEK_END) | moves from the current position / the end | 0 on success |
| ftell(fp) | tells the current position | a long |
| rewind(fp) | back to position 0 | — |
Binary files
- Open with a b in the mode: "wb", "rb", "ab".
- fwrite(&x, sizeof x, 1, fp) saves one variable; fwrite(arr, sizeof arr[0], n, fp) a whole array.
- fread uses exactly the same arguments to load the data back.
- Check the return value: fewer items than asked means end of file or an error.
- Binary files depend on the machine (sizes, byte order) — text files are portable.
Moving around
- Every open file has a position; reads and writes move it on.
- fseek(fp, k, SEEK_SET) jumps to byte k; SEEK_CUR and SEEK_END count from elsewhere.
- Record i of a binary file of structs starts at i * sizeof(struct).
- ftell(fp) gives the position; after fseek(fp, 0, SEEK_END) it is the file size.
- rewind(fp) goes back to the start.
Errors and safe habits
- Check every fopen; report problems with perror("name").
- Return a non-zero exit code (return 1) when the program cannot do its job.
- Close every file on every path — also before an early return.
- Use "a" to add to logs; "w" only when the old content may go.
- ferror(fp) tells whether a read or write failed; feof(fp) whether the end was reached.
Save an array of structs, then read one record directly
Every line, explained.
#include <stdio.h>
typedef struct {
int id;
double price;
} Item;
4 + 4 padding + 8 = 16 bytes.
int main(void)
{
Item items[3] = {{1, 9.5}, {2, 20.0}, {3, 4.25}};
FILE *fp = fopen("items.bin", "wb");
if (fp == NULL) { perror("items.bin"); return 1; }
size_t written = fwrite(items, sizeof(Item), 3, fp);
The whole array in one call.
fclose(fp);
printf("%zu items written\n", written);
Prints 3 items written
fp = fopen("items.bin", "rb");
if (fp == NULL) { perror("items.bin"); return 1; }
Item one;
fseek(fp, 2 * sizeof(Item), SEEK_SET);
Jump straight to record 2 (the third).
if (fread(&one, sizeof(Item), 1, fp) == 1)
printf("id %d costs %.2f\n", one.id, one.price);
Prints id 3 costs 4.25
fseek(fp, 0, SEEK_END);
printf("file size %ld bytes\n", ftell(fp));
Prints file size 48 bytes
fclose(fp);
return 0;
}
Spot the bug
Mistakes almost every beginner makes — and how to fix them.
Reading a binary file as text
fwrite(&n, sizeof n, 1, fp); … fscanf(fp, "%d", &n); // garbage
fwrite(&n, sizeof n, 1, fp); … fread(&n, sizeof n, 1, fp);
Read data the same way it was written: fprintf ↔ fscanf, fwrite ↔ fread.
Mixing up size and count
fwrite(arr, 10, sizeof(int), fp); // works by luck
fwrite(arr, sizeof(int), 10, fp); // size first, then count
The return value counts items of the given size; with the arguments swapped it means something else.
Ignoring the return value of fread
fread(&rec, sizeof rec, 1, fp); print(rec); // maybe old data
if (fread(&rec, sizeof rec, 1, fp) == 1)
print(rec);
At the end of the file fread returns 0 and leaves rec unchanged.
Seeking by record number instead of bytes
fseek(fp, 2, SEEK_SET); // byte 2, not record 2
fseek(fp, 2 * sizeof(Item), SEEK_SET);
fseek counts bytes. Multiply by the record size.
Leaving a file open on an error path
FILE *out = fopen("b.txt", "w");
if (in == NULL) return 1; // out never closed
if (in == NULL) { fclose(out); return 1; }
Every opened file must be closed, also when the program gives up early. Better: open in an order that avoids it.
Check your understanding
Answer each question — you will see the explanation straight away.
How many bytes does fprintf(fp, "%d", 12345) write?
One character per digit.
How many bytes does fwrite(&n, sizeof n, 1, fp) write for an int n (4-byte int)?
Always sizeof n bytes, whatever the value.
A file contains "ABCDEF". After fseek(fp, 3, SEEK_SET), what does fgetc return?
Position 3 is the fourth byte: D.
How do you find the size of a file in bytes?
At the end, the position equals the size.
Records of 16 bytes are stored in a binary file. Where does record 5 (counting from 0) start?
5 × 16 = 80.
More File Handling quiz
Test yourself: 10 questions, 70% to pass. Create a free account to take the quiz, save your progress and work towards the certificate.