File I/O

Course 2 · Ch 6
File I/O
Another manually-acquired, manually-released resource — and the function C's own standard body removed entirely

Every program so far has lived entirely in memory. This chapter opens the door to the filesystem — and introduces a resource-management discipline that should already feel familiar from c2-2's malloc/free pairing.

Opening a File

FILE *fp = fopen("data.txt", "r"); if (fp == NULL) { // failed to open — must be checked, same discipline as malloc }

File Modes

The mode string controls both direction and format: "r" read, "w" write (truncating), "a" append — each with a b variant ("rb", "wb", "ab") for binary.

Text Mode vs. Binary Mode

Text mode may translate line endings depending on the platform — on Windows, \n can be translated to/from \r\n on read/write. Binary mode transfers bytes exactly as-is, with no translation at all. This is genuinely platform-dependent behavior, worth knowing explicitly rather than discovering by accident. Rust's std::fs has no equivalent mode distinction at the language level — it's byte-oriented by default, with no automatic translation to opt out of.

Reading & Writing Text

fprintf(fp, "%d\n", 42); char line[256]; fgets(line, sizeof(line), fp); // bounded — safe

fgets takes a maximum buffer size, exactly the bounds-checking discipline c1-8 covered. Its predecessor, gets(), took no size argument at all — it was so fundamentally unfixable that C11 removed it from the standard entirely.

Reading & Writing Binary Data

int nums[5] = {1, 2, 3, 4, 5}; size_t written = fwrite(nums, sizeof(int), 5, fp); // written may be less than 5 — a short write is possible and must be checked

fread/fwrite operate on raw bytes: a pointer, the size of each element, and how many elements. Both return the actual number of items transferred — which can genuinely be less than requested, and isn't automatically an error condition to ignore.

Closing a File

fclose(fp);

Flushes any buffered writes and releases the underlying OS file handle. Forgetting it is the same class of mistake as c2-2's forgotten free — a resource acquired manually that must be released manually, just a file descriptor instead of heap memory.

ConceptRustC
Text/binary mode distinctionnone — byte-oriented by defaultexplicit — "r" vs "rb", genuinely platform-dependent
Closing a fileautomatic — Drop closes it when the handle goes out of scopemanual — fclose must be called explicitly
Unbounded line readingnot available — read_line always takes a growable buffergets() existed, then was removed entirely in C11
Check fopen exactly like malloc
A failed fopen returns NULL — the same discipline c2-2 established for malloc. A missing file, wrong permissions, or a full disk are all real, common causes; never assume the file opened successfully.
gets() was removed from the C standard — the strongest statement C makes about a function
gets() read a line with no way to specify a maximum length at all — genuinely, unfixably unsafe, no matter how carefully called. C11 didn't just discourage it; it removed it from the standard outright. Use fgets, always.

Coding Challenges

Challenge 1

Write a program that opens a file for writing, writes three lines of text to it with fprintf, closes it, then reopens the same file for reading and prints each line back out with fgets.

📄 View solution
Challenge 2

Write a program that writes an array of 5 ints to a file in binary mode with fwrite, then reads them back into a different array with fread, printing both arrays to confirm they match.

📄 View solution
Challenge 3

Explain specifically why gets() could never be made safe with better documentation or careful usage alone, and why fgets's extra parameter is what actually fixes the underlying problem.

📄 View solution

Chapter 6 Quick Reference

  • fopen(name, mode) — returns NULL on failure, must always be checked
  • "r"/"w"/"a" plus a b variant for binary — text mode may translate line endings, platform-dependently
  • fgets — bounded, safe; gets() — unbounded, removed entirely from C11
  • fread/fwrite — return the actual items transferred, which can be less than requested
  • fclose — manual, exactly like free; forgetting it leaks a file descriptor
  • Next chapter: Function Pointers — syntax, callbacks, and a vtable-style dispatch pattern