Skip to content
Lesson 12 of 13

Step 1 of 7 · Reading · ~3 min

Learn

File I/O and the Preprocessor

File I/O

By the end of this lesson you can create a file, add to it later without destroying it, and read it back one line at a time, checking every step that can fail.

A stream: FILE *, fopen, fclose

A stream is C's name for a source or destination of bytes. fopen(path, mode) opens a file and returns a FILE *, a pointer to the library's bookkeeping for that stream. fclose(fp) finishes with it. You have used streams all course: stdin and stdout are pre-opened FILE * values.

FILE *fp = fopen("/tmp/scores.txt", "w");
if (fp == NULL) {            /* the ONLY failure report you get */
    perror("/tmp/scores.txt");   /* prints: /tmp/scores.txt: Permission denied */
    return 1;
}
fprintf(fp, "%s %d\n", "ana", 91);   /* printf with a stream in front */
fclose(fp);

A missing directory, a missing permission, too many open files: each arrives as NULL. Passing that NULL on to fprintf or fgets is undefined behaviour, usually a crash.

The mode string

ModeOpens forFile existsFile missing
"r"readingreads from the startreturns NULL
"w"writingempties it firstcreates it
"a"writingadds at the endcreates it

Suppose /tmp/scores.txt holds ana 91 and you open it again and write ben 78:

opened with "w"  ->  ben 78            (ana is gone)
opened with "a"  ->  ana 91 / ben 78   (two lines)

"w" truncates the instant fopen returns, before you write anything. Use it to start fresh; use "a" to add.

Buffering: why fclose comes before reading

fprintf usually copies your bytes into a memory buffer and returns; they reach the file when the buffer fills or the stream is flushed or closed. So this goes wrong:

FILE *w = fopen("/tmp/scores.txt", "a");
fprintf(w, "cai 64\n");
FILE *r = fopen("/tmp/scores.txt", "r");   /* w NOT closed yet */
/* reading r now shows the old lines only: "cai 64" is still in w's buffer */

Close the writer first, then open the reader.

Reading line by line

fgets(buf, size, fp) works on any stream exactly as it did on stdin: it keeps the newline and returns NULL at end of file. Here is a different problem, finding the longest line in a file:

char buf[128];
int best = 0;
while (fgets(buf, sizeof buf, fp) != NULL) {
    int n = (int)strlen(buf);
    if (n > 0 && buf[n - 1] == '\n') n--;   /* do not count the newline */
    if (n > best) best = n;
}
fclose(fp);
printf("longest: %d\n", best);

For a file holding ana 91, a blank line and ben 78, the loop sees lengths 6, 0, 6 and prints longest: 6. A blank line is still a line: fgets returns "\n", not NULL.

Writing text safely

Write a stored string with fprintf(fp, "%s\n", text) or fputs. Never fprintf(fp, text): the text becomes the format string, and a line like 50% off makes fprintf hunt for an argument that does not exist.

Common mistakes

  • Opening with "w" the second time. The first batch vanishes and only the later lines come back.
  • Reading before closing the writer. The lines still in the buffer are missing from what you read.
  • Printing the fgets buffer as is. It still ends in \n, so printf("%s\n", ...) prints an empty line after every line.
  • Writing without \n, or with the text as the format. Lines run together into one, or 100% done prints garbage.

Your exercise

The starter reads two batches of lines into morning and evening. Put the morning lines into a new file, add the evening lines to the same file with a second open, then read the file back and number every line. The likely slip is the second mode.

Up nextThe PreprocessorFile I/O and the Preprocessor

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…