Lesson 137 min read

File I/O

Files are like notebooks on a shelf β€” open one, read or write, then put it back.

The Notebook Analogy

Imagine a shelf full of notebooks. To use one, you:

  1. Pull it off the shelf β€” fopen()
  2. Read pages or write new ones β€” fprintf(), fscanf(), fgets()
  3. Put it back β€” fclose()

If the notebook doesn't exist and you try to read it, you'll get a NULL pointer. If you open it for writing, C will create a brand-new notebook for you.

fopen Modes

The second argument to fopen tells C how to open the file:

  • "r" β€” Read only. File must exist.
  • "w" β€” Write only. Creates file or erases existing content.
  • "a" β€” Append. Writes to the end without erasing.
  • "r+" β€” Read and write. File must exist.
  • "rb", "wb" β€” Binary mode (for images, audio, etc.).

Writing to a Text File

#include <stdio.h>
int main() {
FILE *fp = fopen("scores.txt", "w");
if (fp == NULL) {
printf("Error: could not create file\n");
return 1;
}
fprintf(fp, "Alice: %d\n", 95);
fprintf(fp, "Bob: %d\n", 87);
fprintf(fp, "Carol: %d\n", 92);
fclose(fp);
printf("Scores written to scores.txt\n");
return 0;
}
Output
Scores written to scores.txt
Note: Always check if fopen returns NULL β€” the file might not exist, you might not have permission, or the disk could be full. Always call fclose when you're done β€” unclosed files can lose data because writes may be buffered in memory and never flushed to disk.

Reading Line by Line

fgets() reads one line at a time into a buffer you provide. It's the safest way to read text files because you specify a maximum length β€” no buffer overflow risk.

The loop pattern while (fgets(buffer, size, fp) != NULL) reads until the end of the file. When there's nothing left, fgets returns NULL.

Reading Line by Line with fgets

#include <stdio.h>
int main() {
FILE *fp = fopen("scores.txt", "r");
if (fp == NULL) {
printf("Error: cannot open scores.txt\n");
return 1;
}
char line[256];
int lineNum = 1;
while (fgets(line, sizeof(line), fp) != NULL) {
printf("Line %d: %s", lineNum, line);
lineNum++;
}
fclose(fp);
return 0;
}
Output
Line 1: Alice: 95
Line 2: Bob: 87
Line 3: Carol: 92

Binary Files β€” fread and fwrite

Text files store data as human-readable characters. Binary files store raw bytes β€” exactly how data looks in memory. This is faster and more compact, but you can't open a binary file in Notepad and make sense of it.

fwrite(data, elementSize, count, fp) writes raw bytes. fread(buffer, elementSize, count, fp) reads them back.

Binary File Read/Write

#include <stdio.h>
typedef struct {
char name[20];
int score;
} Record;
int main() {
// Write binary data
Record players[] = {
{"Alice", 950},
{"Bob", 870},
{"Carol", 920}
};
int count = 3;
FILE *fp = fopen("players.dat", "wb");
if (!fp) return 1;
fwrite(players, sizeof(Record), count, fp);
fclose(fp);
// Read binary data back
Record loaded[3];
fp = fopen("players.dat", "rb");
if (!fp) return 1;
fread(loaded, sizeof(Record), count, fp);
fclose(fp);
for (int i = 0; i < count; i++) {
printf("%s: %d\n", loaded[i].name, loaded[i].score);
}
return 0;
}
Output
Alice: 950
Bob: 870
Carol: 920

Error Checking & EOF

Robust file code checks for errors at every step:

  • fopen returns NULL on failure
  • fgets returns NULL at end-of-file (or on error)
  • fread returns the number of items actually read β€” if it's less than you asked for, you've hit EOF or an error
  • feof(fp) returns true if you've reached the end of the file
  • ferror(fp) returns true if a read/write error occurred

Robust Error Checking

#include <stdio.h>
int main() {
FILE *fp = fopen("nonexistent.txt", "r");
if (fp == NULL) {
perror("fopen failed");
// perror prints the system error message
return 1;
}
char buf[100];
while (fgets(buf, sizeof(buf), fp)) {
printf("%s", buf);
}
if (ferror(fp)) {
printf("A read error occurred!\n");
} else if (feof(fp)) {
printf("Reached end of file.\n");
}
fclose(fp);
return 0;
}
Output
fopen failed: No such file or directory
Note: Opening a file with "w" mode will ERASE everything in it! If you want to add to an existing file without destroying its contents, use "a" (append) mode instead.
Challenge

Quick check

What does fopen return if the file cannot be opened?
← Structs & TypedefPreprocessor & Macros β†’