student@ubuntu:~$
advanced-c Lesson 12 10 min read

File I/O in C

Reading and writing files — fopen, fclose, and the feof pitfall

Reading: Hanly & Koffman: §11.1–11.2 (pp. 630–655)

Quick check before you start: In Java, you might use Scanner to read a file and PrintWriter to 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 fopen and fclose
  • Write formatted output to files with fprintf
  • Read lines from files with fgets
  • Check for file-open errors
  • Avoid the feof pitfall

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.


Check Your Understanding
What does fopen return if the file cannot be opened?
AAn empty FILE pointer
B-1
CNULL
DIt throws an exception
Answer: C. 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.

Next: Function Pointers