Reading and writing files

Intermediate

Introduction

Everything a program holds in its variables disappears when it ends. Files are how data survives. Saving and loading is the difference between a toy and a useful program, and C's file functions are close relatives of the printf and scanf you already know.

How it works

Opening. fopen(name, mode) opens a file and returns a FILE *, or NULL if it could not.

ModeMeaning
"r"read an existing file
"w"write, and empty the file first
"a"add to the end

Why check for NULL. A file can be missing, locked or in a folder you cannot write to. These are normal events, not rare ones, and using a NULL file pointer crashes the program.

Writing. fprintf(f, ...) works just like printf, except that it writes to the file. fputs(text, f) writes a string.

Reading. fscanf(f, ...) works like scanf, and it returns how many values it read. fgets(buffer, size, f) reads one line, keeping the newline, and returns NULL at the end of the file.

Closing. fclose(f) finishes the work and releases the file. Written data may not reach the disk until then.

Reading a whole file. Do not count lines in advance. Let the read function say when it has run out:

#include <stdio.h>
#include <string.h>

int main(void) {
    FILE *f = fopen("notes.txt", "w");
    if (f == NULL) return 1;
    fputs("first line\n", f);
    fputs("second\n", f);
    fclose(f);

    f = fopen("notes.txt", "r");
    if (f == NULL) return 1;
    char line[50];
    int count = 0;
    while (fgets(line, sizeof line, f) != NULL) {
        count++;
        line[strcspn(line, "\n")] = '\0';
        printf("%d: %s (%d)\n", count, line, (int)strlen(line));
    }
    fclose(f);
    return 0;
}
Open in compiler

strcspn(line, "\n") finds where the newline is, so that writing a '\0' there removes it.

A note on these pages. In StepVioz, files are kept in memory while your program runs, and they disappear when it finishes. A program compiled on your own computer writes real files to its disk, using exactly the same code.

Syntax

FILE *f = fopen("data.txt", "r");
if (f == NULL) {
    printf("Could not open the file\n");
    return 1;
}
while (fscanf(f, "%d", &n) == 1) {
    ...
}
fclose(f);

Example

#include <stdio.h>

int main(void) {
    FILE *out = fopen("marks.txt", "w");
    if (out == NULL) {
        printf("Could not create the file\n");
        return 1;
    }
    fprintf(out, "%d %d\n", 72, 85);
    fprintf(out, "%d %d\n", 64, 90);
    fclose(out);

    FILE *in = fopen("marks.txt", "r");
    if (in == NULL) {
        printf("Could not open the file\n");
        return 1;
    }
    int a, b, total = 0;
    while (fscanf(in, "%d %d", &a, &b) == 2) {
        total += a + b;
    }
    fclose(in);
    printf("Total %d\n", total);
    return 0;
}
Open in compiler

Line by line

  1. fopen with "w" creates the file, and the program checks that it worked.
  2. The two fprintf calls write two lines of numbers.
  3. fclose finishes the writing.
  4. The file is opened again, this time for reading.
  5. fscanf returns 2 as long as it finds two numbers, so the loop ends when the file does.
  6. The total is printed after the file has been closed.

Expected result

Total 311

In the real world

Configuration files, logs and saved games are ordinary files. Databases are built on top of the same operations, with a great deal of extra care to survive a crash at the worst possible moment.

Common mistakes

  • Not checking fopen for NULL, and crashing on the next line.
  • Opening with "w" when you meant "a", which erases what was there.
  • Forgetting fclose, so that some data never gets written.
  • Using while (!feof(f)), which runs one time too many. Test the result of the read instead.

Best practices

  • Check every fopen, and report the problem clearly.
  • Close each file as soon as you are done with it.
  • Give fgets the true size of your array, using sizeof.

Summary

fopen opens a file and returns NULL on failure. fprintf and fputs write, fscanf and fgets read, and their results tell you when the file has ended. fclose finishes the job.

Quick check

1. What does fopen return when a file cannot be opened?
2. What does opening an existing file with mode "w" do?
3. How should a loop know when a file has ended?
4. Why is fclose important after writing?

Key takeaways

  • fopen returns NULL when it fails, so check every call.
  • Every fopen needs an fclose.
  • Loop on what the read function returns, not on a guess.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.