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
| Mode | Opens for | File exists | File missing |
|---|---|---|---|
"r" | reading | reads from the start | returns NULL |
"w" | writing | empties it first | creates it |
"a" | writing | adds at the end | creates 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
fgetsbuffer as is. It still ends in\n, soprintf("%s\n", ...)prints an empty line after every line. - Writing without
\n, or with the text as the format. Lines run together into one, or100% doneprints 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.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…