Programs That Remember

Everything your programs have computed has died with them: variables perish at exit, and the terminal scrolls away. Files are how programs persist data, and C's file API is a small, pointer-shaped extension of the I/O you already know: a FILE * names an open stream, the printf and fgets families grow an f prefix, and every operation keeps the guarded-call discipline you have practiced since chapter 5.

fopen, and the Check That Is Not Optional

fopen(path, mode) asks the operating system for the file and returns a FILE *: a pointer to a stream object that every later operation takes as its handle. Or it returns NULL, and it genuinely does: missing files, bad paths, no permission, full disks. The NULL check on fopen is the least optional guard in this course, because every subsequent call dereferences that pointer, and chapter 11 told you exactly what dereferencing NULL is.

The mode string sets the relationship: "r" reads an existing file (NULL if absent), "w" writes a fresh file, truncating any existing content to nothing, the data-loss trap exams and real life share, and "a" appends, creating if needed, preserving what is there. A b suffix ("rb", "wb") selects binary mode, next lessons' business.

fclose(out) ends the relationship: buffered output is flushed to disk, the handle is surrendered, and the pointer becomes unusable, using a closed FILE * is undefined behaviour, the file world's dangling pointer. The rule mirrors the pointer discipline: every successful fopen has exactly one fclose, on every path out of the code, the shape chapter 5's goto carve-out anticipated.

The f-Family

Each console function you know is the file version of itself with the stream made explicit:

Console File
printf(...) fprintf(stream, ...) formatted out
scanf(...) fscanf(stream, ...) formatted in
getchar() fgetc(stream) one char in
putchar(c) fputc(c, stream) one char out
fgets(buf, n, stdin) fgets(buf, n, stream) it always took a stream

The last row is the reveal: stdin was a FILE * all along, one of three streams every program starts with, stdin, stdout, stderr, already open. printf(...) is fprintf(stdout, ...); your whole I/O education transfers by prefix.

Round Trip

Writing is only half a proof; reading it back is the other:

Open-write-close, open-read-close, every fopen checked, every stream closed on every path (note the fclose(in) inside the empty-file branch), and chapter 8's fgets trim doing its usual work. This write-then-verify round trip is the file world's hello-world, and the shape of the exercise below.

Where Files Live

The path "/tmp/note.txt" is absolute; a bare "note.txt" is relative to the program's working directory, which depends on how the program was launched, a classic source of "it worked yesterday". This platform's sandbox guarantees /tmp is writable, so course exercises anchor there; on your own machine, relative paths in a directory you control are the norm. The principle over the particulars: know where your program's paths resolve, and treat a NULL from fopen as the answer to a question, not an outrage.

Key Takeaways

  • fopen(path, mode) returns a FILE * stream handle or NULL, and the NULL check is mandatory: everything after dereferences that pointer.
  • Modes: "r" read-existing, "w" write-truncate (the data-loss trap), "a" append-preserve; b for binary later.
  • Every successful fopen gets exactly one fclose on every path; a closed stream pointer is the file world's dangling pointer.
  • The f-family is your console I/O with the stream explicit, and stdin/stdout/stderr are pre-opened FILE *s: printf is fprintf(stdout, ...).
  • Absolute vs relative paths decide where files land; course exercises anchor in /tmp for the sandbox's sake.