Binary Files and Random Access
fread and fwrite on records, fseek and ftell, and when binary beats text.
The Other Agreement
Text files store the description of your data: the int 88 becomes the characters '8', '8', '\n', and reading it back means parsing. Binary files skip the description and store the bytes themselves: the same four bytes that hold 88 in memory go to disk verbatim, padding and all. Two functions carry the whole idea, fwrite copies memory to a stream and fread copies it back, and because every record is the same size, a third function, fseek, turns the file into something you can index like an array.
fwrite: Memory to Disk, Byte for Byte
fwrite(ptr, size, count, stream) writes count items of size bytes each, starting at ptr, and returns how many complete items it actually wrote. The sizes are always sizeof-driven: sizeof(struct Score) is the truth about the record, padding included, chapter 10 warned you never to add up the members yourself, and this is where that rule pays rent. fread is the mirror image with the same signature, returning how many complete items it read.
Both return counts, and both checks are load-bearing. A short count from fwrite means the write failed partway (full disk, most commonly). A short count from fread means the file ended early or errored, and the items it did not fill are uninitialized memory; use them and you are back in chapter 11's undefined-behaviour territory. != COUNT on each call is the entire guard.
The mode strings grow the promised b: "wb" and "rb". On this platform binary and text mode behave identically, but on some systems text mode rewrites line-ending bytes, which shreds binary data that merely happens to contain a byte that looks like '\n'. Write the b every time the content is not text; it costs nothing and documents the agreement.
fseek and ftell: The File as an Array
Because every struct Score occupies exactly sizeof(struct Score) bytes, record i starts at byte i * sizeof(struct Score), the same multiply that array indexing has done since chapter 8, now aimed at a disk:
This prints 32 bytes on disk, 4 records and then record 2: id 12 with 88 points, no loop over records 0 and 1 required.
fseek(stream, offset, whence) moves the stream's position: the whence anchor is SEEK_SET (offset from the start), SEEK_CUR (from the current position), or SEEK_END (from the end), and it returns 0 on success. The offset parameter is a long, and sizeof produces a size_t, hence the (long) cast in the multiply. ftell(stream) reports the current position as a long (or -1L on failure), so the seek-to-end-then-tell pair is the idiom for file size, and dividing by the record size counts the records without reading one byte of them.
That is random access: jump straight to record i, read exactly one record, done. The text files of the last lesson cannot offer it, "72\n" and "105\n" are different widths, so record i's byte offset depends on every value before it, and the only way to find line 500 is to read 499 lines.
When Binary Beats Text
Binary earns its keep three ways. Exact round-trips: the bytes that come back are the bytes that went in, no formatting or parsing step to lose precision or choke on a stray character. Speed: fwrite is a memory copy, while fprintf must convert every value to characters and fscanf must convert them all back. Random access: fixed-size records plus fseek give constant-time jumps that text's variable-width lines cannot.
Now the bill. A binary file is not human-readable: open /tmp/scores.bin in an editor and you get garbage, no cat, no quick fix in a text editor, no debugging by looking. And it is not portable: the file's layout is whatever the writing machine's memory layout was. Chapter 10's padding is compiler business, so another compiler may lay struct Score out differently, and different CPUs even order the bytes within an int differently (the endianness split). Same program, same machine, same compiler: binary round-trips perfectly. Anything else, a defined text format, or a byte-order-specified binary format designed on purpose, is the professional answer.
Key Takeaways
fwrite(ptr, size, count, stream)andfread(...)copy whole records between memory and disk; sizes come fromsizeof, never from adding up members.- Both return the count of complete items transferred, and checking it is mandatory: a short
freadleaves the unfilled items as uninitialized memory. - Use
"wb"/"rb"for binary data; text mode may translate line-ending bytes on some platforms. fseek(f, (long)i * (long)sizeof(struct Score), SEEK_SET)jumps straight to recordi;SEEK_CURandSEEK_ENDanchor relative seeks, and seek-to-end plusftellyields file size.- Binary wins on exact round-trips, speed, and random access; it loses human readability and portability, padding and endianness make the file layout machine-specific.
How did you find this lesson?
Your rating helps us improve the content.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
Binary Files and Random Access - Quiz
Test your understanding of the lesson.
Practice Exercises
Seek the Second Record
Read 3 id/points pairs from standard input into struct Score records, fwrite them to /tmp/records.bin in one call, close it, reopen with "rb", fseek directly to the record at index 1, fread just that one record, and print it. No loop over the file: the seek does the work.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!