File I/O in C
Reading and writing files — fopen, fclose, and the feof pitfall
Quick check before you start: In Java, you might use
Scannerto read a file andPrintWriterto write one. C has similar functions, just more manual. Read on.Practice this topic: File I/O skill drill
After this lesson, you will be able to:
- Open and close files with
fopenandfclose - Write formatted output to files with
fprintf - Read lines from files with
fgets - Check for file-open errors
- Avoid the
feofpitfall
Opening and Closing Files
Every file operation starts with fopen and ends with fclose:
FILE *fp = fopen("data.txt", "r"); // open for reading
if (fp == NULL) {
fprintf(stderr, "Error: cannot open data.txt\n");
exit(EXIT_FAILURE);
}
// ... use the file ...
fclose(fp);
The second argument is the mode:
| Mode | Meaning |
|---|---|
"r" |
Read (file must exist) |
"w" |
Write (creates or overwrites) |
"a" |
Append (creates or adds to end) |
In Java, you wrap everything in try-with-resources. In C, you call fclose yourself. Forgetting it can lose buffered data.
Writing to Files with fprintf
fprintf works exactly like printf, but sends output to a file:
FILE *fp = fopen("output.txt", "w");
if (fp == NULL) { exit(EXIT_FAILURE); }
fprintf(fp, "Name: %s\n", name);
fprintf(fp, "Score: %d\n", score);
fclose(fp);
You already know printf. fprintf just adds a file pointer as the first argument.
Reading with fscanf
fscanf mirrors scanf but reads from a file:
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) { exit(EXIT_FAILURE); }
char name[50];
int score;
fscanf(fp, "%s %d", name, &score);
fclose(fp);
fscanf stops at whitespace for %s. For lines with spaces, use fgets.
Reading Lines with fgets
fgets reads an entire line, including spaces:
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
printf("Read: %s", line); // line includes \n
}
fgets takes three arguments: the buffer, the buffer size, and the file pointer. It reads at most size - 1 characters and adds a null terminator. It returns NULL at end-of-file.
The feof Pitfall
This is wrong:
// BAD — do not do this
while (!feof(fp)) {
fgets(line, sizeof(line), fp);
printf("%s", line); // prints last line twice
}
feof returns true after a read has already failed. So the last iteration reads nothing, fgets returns NULL, but you still print the old buffer contents.
The correct pattern is to check the return value of the read function:
// GOOD
while (fgets(line, sizeof(line), fp) != NULL) {
printf("%s", line);
}
Let the read function tell you when it is done. Do not ask feof in the loop condition.
Error Checking Pattern
A complete file-reading function:
void read_file(const char *filename) {
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
perror(filename); // prints "filename: No such file or directory"
return;
}
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
printf("%s", line);
}
fclose(fp);
}
perror prints the system error message for the last failed call. It is more informative than a generic “file not found” message.
fopen return if the file cannot be opened?fopen returns NULL when it cannot open the file. C has no exceptions. You must check the return value after every fopen call and handle the error yourself.
What Comes Next
You can read and write files. Next week, you will learn one of C’s most powerful features: function pointers — passing behavior as data, just like Java lambdas.