When you close the program so far, all data is lost. File input/output allows you to save data to disk and read it again.
Learning Objectives
- Open and close files with
fopen/fclose. - Use text mode and binary mode separately.
- Text input and output is performed using
fprintf/fscanf/fgets. - Perform binary input/output with
fread/fwrite.
Why is file input/output necessary? — "Memory is volatile"
During program execution After program termination
┌──────────────┐ ┌──────────────┐
│ Variable a = 10 │ │ │
│ Array [...] │ ──── End ────► │ Disappear │
│ Structure │ │ │
└──────────────┘ └──────────────┘
(RAM, volatile) (vulnerable to power outages and shutdowns)
If you save it to a disk file, you can read it on the next run. The basis for all persistent data, including configuration files, logs, data backups, and results reports.
Key concepts
1) Opening and closing files
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("Open failed");
return 1;
}
/*...use...*/
fclose(fp);
fopen returns FILE * on success and NULL on failure.
If not closed, operating system resources may leak + buffers may not be written to disk.
2) Mode string
| mode | Meaning | If the file does not exist | If the file exists |
|---|---|---|---|
"r" |
Read | failed (returns NULL) | Read from beginning |
"w" |
writing | Create new | After deleting content From the beginning |
"a" |
Continue writing | Create new | append to end |
"r+" |
Read+Write | failure | Open as is |
"rb"/"wb" |
binary mode | Same as above | Same as above |
3) Text input and output
Human-readable format. Patterns like one record per line are common.
fprintf(fp, "%s,%d\n", name, age); //write
fscanf(fp, "%s %d", name, &age); //read
fgets(buf, sizeof(buf), fp); //Read one line (including spaces)
4) Binary input/output
Store the bytes in memory as is. Fast and precise but not human readable.
fwrite(&value, sizeof(value), 1, fp);
fread (&value, sizeof(value), 1, fp);
Memory (Record structure) File (data.bin)
┌──────────────────┐ ┌──────────────────┐
│ id: 1 │ fwrite │ 01 00 00 00 │
│ name: "Kim..." │ ────────► │ 4B 69 6D 00 ... │
│ score: 90.5 │ │ 00 00 00 00 ... │
└──────────────────┘ └──────────────────┘
5) End of file (EOF) checking
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
/*Process line by line*/
}
The standard recommendation is to judge by the return value of the read function rather than feof(fp).
See as example
Example 1 — ex01_write_text.c: Write CSV format text
fprintf(fp, "Name, score\n");
fprintf(fp, "%s,%d\n", "Kim", 90);
Execution results (console)
Completed writing output.txt
output.txt
name, score
Kim,90
Lee,85
Park,78
Bottom line: Be careful because "w" mode deletes existing content.
Example 2 — ex02_read_text.c: Read line by line
while (fgets(line, sizeof(line), fp) != NULL) {
printf("%2d: %s", ln++, line);
}
Execution result
1: Name, score
2: Kim,90
3: Lee,85
4: Park,78
Key point: fgets reads up to the line break \n.
Example 3 — ex03_append.c: Continue (log)
FILE *fp = fopen("log.txt", "a");
fprintf(fp, "[%ld] Program execution history\n", (long)time(NULL));
Execution result (log.txt after running twice)
[1778403024] Program execution history
[1778403024] Program execution history
Bottom line: The "a" mod appends to the end, so existing content is preserved.
Example 4 — ex04_binary.c: Structure to binary
typedef struct { int id; char name[32]; double score; } Record;
fwrite(out, sizeof(Record), n, fp);
fread (in, sizeof(Record), n, fp);
Execution result
Number of records read: 3
1 Kim 90.50
2 Lee 85.00
3 Park 77.50
Key point: Only programs that use the same structure definition can be safely exchanged. If you move To-OS/Compiler to another, it may be broken due to differences in padding and endianness.
A different perspective — text vs binary
| Item | Text Mode ("r"/"w") |
Binary mode ("rb"/"wb") |
|---|---|---|
| person reading | possible | Not possible |
| Save integer 1234 | "1234" (4 byte characters) | 0xD2 0x04 0x00 0x00 (4 bytes) |
| Line break handling | Conversion by OS | No conversion |
| Compatibility | Very good | Same environment recommended |
| speed/size | Large | Small and fast |
Text is natural for configuration files, and binary is natural for game save data.
Common Mistakes
- Missing
fopenresult check: There is no file, butfpis used as is → Segfault. fclosemissing: Data may not appear to be written to disk (buffering).- Misuse of
"w"mode: Unintentionally losing existing data. - Relative path confusion: Find out where your current working directory is (
pwdorgetcwd). - Compare binary files with text tools: Tools like
diffare not suitable for binary files. Usingcmp.
Cleanup
fopen-fcloseare paired, and result NULL check is required.- Text mode is easier on the eyes, and binary mode is fast and accurate.
fprintf/fscanf/fgetsare text,fwrite/freadare binary."w"is overwritten,"a"is continued.- It is safe to read and write binary files in the same environment (Compiler/OS).
Try it yourself
cd src
gcc -std=c11 -Wall -o ex01 ex01_write_text.c && ./ex01 && cat output.txt
gcc -std=c11 -Wall -o ex02 ex02_read_text.c && ./ex02
gcc -std=c11 -Wall -o ex03 ex03_append.c && ./ex03 && ./ex03 && cat log.txt
gcc -std=c11 -Wall -o ex04 ex04_binary.c && ./ex04
Application:
- After running
ex01, openoutput.txtwith a text editor, add lines directly, and runex02. - Try to see if adding a new member to
Recordofex04can read the existingdata.bin.