Skip to content

Intermediate · Structs, unions, enums and files · topic 35 of 60 · 25 min

Binary files and records

Explanation

Binary files store data as raw bytes, the same way it sits in memory. fwrite and fread copy whole structs or arrays in one call, and fseek, ftell and rewind move around the file, which allows fixed-size record databases with random access. Open binary files with "rb"/"wb", and remember that raw structs are not portable across compilers and platforms because of padding, sizes and byte order.

Text versus binary

In a text file, the number 123456 is stored as the six characters "123456". In a binary file, an int 123456 is stored as its 4 raw bytes. Binary files are smaller and faster to read and write (no formatting or parsing), and every record can have a fixed size, but humans cannot read them in an editor.

Writing and reading records

c
typedef struct {
    int roll;
    char name[30];
    float marks;
} Student;

Student list[3] = { {1, "Asha", 91.5f}, {2, "Ravi", 78.0f}, {3, "Meena", 85.5f} };

FILE *out = fopen("students.dat", "wb");
if (!out) { perror("students.dat"); return 1; }
size_t written = fwrite(list, sizeof list[0], 3, out);   /* element size, count */
fclose(out);

Student s;
FILE *in = fopen("students.dat", "rb");
if (!in) { perror("students.dat"); return 1; }
while (fread(&s, sizeof s, 1, in) == 1) {                 /* read one record at a time */
    printf("%d %s %.1f\n", s.roll, s.name, s.marks);
}
fclose(in);
fwrite and fread return the number of ELEMENTS (records) transferred; check it.

Random access with fseek and ftell

c
/* read record number k (0-based) directly */
int read_student(FILE *f, long k, Student *out) {
    if (fseek(f, k * (long) sizeof *out, SEEK_SET) != 0) return -1;   /* jump to the record */
    return fread(out, sizeof *out, 1, f) == 1 ? 0 : -1;
}

fseek(f, 0, SEEK_END);              /* go to the end */
long bytes = ftell(f);              /* current position = file size in bytes */
long records = bytes / (long) sizeof(Student);
rewind(f);                          /* back to the start */
CallMeaning
fseek(f, offset, SEEK_SET)Move to offset bytes from the start
fseek(f, offset, SEEK_CUR)Move relative to the current position
fseek(f, offset, SEEK_END)Move relative to the end
ftell(f)Current position in bytes
rewind(f)Back to the start (and clear errors)

Updating record k in place: open with "r+b", fseek to k * sizeof(Student), fwrite the new record. When switching between reading and writing on the same stream, call fseek (or fflush) in between, as the standard requires.

Text versus binary mode on Windows

On Windows, text mode translates "\n" to "\r\n" when writing and back when reading, and treats byte 26 (Ctrl+Z) as end of file in text mode. For binary data this corrupts the bytes, so always use "rb"/"wb". On Linux and macOS there is no difference, which is why the bug often appears only when code moves to Windows.

Careful: A binary file written by fwrite of a struct depends on that compiler's padding, the sizes of int and long, and the machine's byte order (endianness: the order in which the bytes of a number are stored; x86 and most ARM systems are little-endian). Reading it on another platform or with a changed struct gives garbage. For files shared between systems, write each field explicitly in a defined order and size (for example with uint32_t and fixed byte order), or use a text format like CSV or JSON.
Think of it like this: A binary record file is like a cupboard of identical numbered drawers: because every drawer is the same size, you can go straight to drawer 57 without opening the others (fseek). A text file is like a notebook of handwritten lines of different lengths: to find line 57 you must read through the first 56.
Tip: Put a small header at the start of your own binary formats: a magic number (like "STU1"), a version and a record count. Reading code can then reject wrong or old files instead of producing garbage.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Binary files and records · 1/6

Binary files

In this lesson you will store records in binary files.

Prefer a video by a teacher? Find free videos on “c binary file fread fwrite fseek ftell struct records” on YouTube ↗

In real life: A fixed-size record file for a small library

A school library program keeps each book as a fixed-size binary record (accession number, title, issued flag). To mark book number 1042 as issued, it seeks directly to that record and rewrites it, which stays fast even with thousands of books.

c
typedef struct { int accession; char title[60]; int issued; } Book;

int set_issued(const char *path, long index, int issued) {
    FILE *f = fopen(path, "r+b");
    if (!f) return -1;
    Book b;
    if (fseek(f, index * (long) sizeof b, SEEK_SET) != 0 || fread(&b, sizeof b, 1, f) != 1) { fclose(f); return -1; }
    b.issued = issued;
    fseek(f, index * (long) sizeof b, SEEK_SET);      /* reposition before writing */
    int ok = fwrite(&b, sizeof b, 1, f) == 1;
    fclose(f);
    return ok ? 0 : -1;
}

Practice (0/3 done)

Task 1 · Question

fread(arr, sizeof arr[0], 10, f) returns 7. What does it mean?

Task 2 · Code · Javascript

Simulate fixed-size records and seeking in JavaScript, which runs here: a "file" is an array of bytes and each record is RECORD_SIZE = 4 bytes. Write readRecord(file, k) that returns the k-th record as an array of 4 bytes (like fseek to k * 4 then fread), or null if the file does not contain a whole record k.

Task 3 · Practice

Write a tiny student database in C: add_student appends a Student record to students.dat ("ab"), list_students reads all records, get_student(k) uses fseek to read record k directly, and count_students uses fseek/ftell. Test with five students and print record 3 directly.

Quick quiz

Question 1

1.Which mode should a binary file be opened with for reading?

Question 2

2.What does ftell(f) return after fseek(f, 0, SEEK_END)?

Question 3

3.Why is fwrite of a struct not a portable file format?

Question 4

4.What is "endianness"?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Which mode should a binary file be opened with for reading?

Answer: "rb"

b means binary (it matters on Windows).

What does ftell(f) return after fseek(f, 0, SEEK_END)?

Answer: The file size in bytes

The current position at the end.

Why is fwrite of a struct not a portable file format?

Answer: Padding, type sizes and byte order differ between compilers and machines

Write fields explicitly for portability.

What is "endianness"?

Answer: The order in which the bytes of a multi-byte number are stored

Little-endian vs big-endian.

Why does the compiler add padding to structs?Related

Answer: To keep members aligned for fast (or legal) access

Alignment.

From Unions, enums and memory layout

Which operator accesses a member of a struct variable?Related

Answer: .

-> is for pointers to structs.

From Structs

Which size is guaranteed to be 1 on every platform?Related

Answer: sizeof(char)

sizeof(char) is 1 by definition.

From Data types and their sizes

When is a struct a good choice?Related

Answer: For small, immutable, value-like data such as a point or money

Small, immutable values are the classic struct use.

From Structs, value types and reference types

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Structs, unions, enums and files”. The module exam comes next.
  2. Module 7 exam: Structs, unions, enums and files · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, The preprocessor, multi-file programs and make. 60 topics left in the course before the final exam.

← Previous: Reading and writing text files