Computer ScienceCore18 min read

File Handling in C

Making data outlive the program that created it

This topic appears in:

01

Why files at all

Everything a program holds in variables lives in RAM and disappears the moment the program ends. A file stores data on secondary storage, so it survives — which is what makes it possible to enter records today and read them tomorrow.

Files also let data move between programs, hold far more than would fit in memory, and provide a record that can be backed up.

FILE *fp;/* a file pointer */fp = fopen("marks.txt", "r");/* open, in read mode */if (fp == NULL) {/* ALWAYS check */printf("Cannot open file\n");} else {/* … read from fp … */fclose(fp);/* always close */}fopen returns NULL if the file cannot be opened — checking for it is not optional

Check for NULL every single time

A file may be missing, may be locked by another program, or may be in a folder you cannot write to. If fopen fails it returns NULL, and using a NULL file pointer crashes the program. The check costs two lines and turns an unexplained crash into a clear message — and it is worth a mark whenever file handling appears in an exam.

02

The modes

The second argument to fopen says what you intend to do, and choosing the wrong one is destructive.

ModeMeansIf the file existsIf it does not
"r"readopens itfails, returns NULL
"w"writeerases everything in itcreates it
"a"appendadds at the endcreates it
"r+"read and writeopens itfails
"w+"read and writeerases it firstcreates it
"a+"read and appendadds at the endcreates it

"w" destroys the file immediately

Opening an existing file in "w" mode truncates it to zero length at the moment it is opened — before you have written anything, and whether or not you go on to write anything at all. A program intended to add a record to a file, opened with "w" instead of "a", deletes every previous record. This is the single most costly mistake in the chapter.

03

Reading and writing

Each input and output function has a file version, taking the file pointer as an extra argument.

fprintf(fp, "%d\n", marks) writes formatted output. fscanf(fp, "%d", &marks) reads it back. fgets(line, 100, fp) reads a whole line including spaces. fputc and fgetc handle single characters.

Knowing when the file has ended matters. feof(fp) reports end of file, but it becomes true only after a read has already failed — so a loop written as while (!feof(fp)) processes the last record twice. The reliable pattern tests the return value of the read itself: while (fscanf(fp, "%d", &n) == 1) { … }.

Worked example

Write a program that appends a student's name and mark to a file, then reads the whole file back and prints it.

  1. fp = fopen("marks.txt", "a"); and check for NULL."a" adds to the end. "w" would erase every record already stored.
  2. fprintf(fp, "%s %d\n", name, marks); fclose(fp);The newline separates records. Closing flushes the buffer to disk — without it the data may never be written.
  3. Reopen for reading: fp = fopen("marks.txt", "r"); and check for NULL again.A separate open, because the mode is different.
  4. while (fscanf(fp, "%s %d", name, &marks) == 2) { printf("%s: %d\n", name, marks); }Testing that fscanf returned 2 — the number of items it was asked for — ends the loop exactly at the end of the data.
  5. fclose(fp);Every open needs a matching close, whichever mode was used.

Open with "a" to append, close, reopen with "r", and loop while fscanf returns the expected count.

04

Closing, and why it matters

Writes are buffered: the data is collected in memory and written to disk in blocks, because writing one byte at a time would be extremely slow. fclose flushes whatever is still buffered and releases the file.

A program that ends without closing may therefore lose its most recent writes entirely — the data was in the buffer, never on the disk. Open files also consume a limited operating-system resource, so a long-running program that never closes anything eventually cannot open any more.

Before you leave this chapter

  1. Files persist after the program ends; variables do not.
  2. fopen returns NULL on failure — check it every time.
  3. "r" reads, "w" ERASES then writes, "a" appends. Choosing "w" instead of "a" destroys the file.
  4. fprintf/fscanf are the file versions of printf/scanf, taking the file pointer first.
  5. Test the return value of the read rather than feof, and always fclose to flush the buffer.
05

Text files and binary files

The files in this chapter are text files: the number 12345 is stored as five characters, readable in any editor, and portable between systems. That readability is their advantage and their cost — five bytes for a number that would fit in two, and every value must be converted on the way in and out.

A binary file stores the raw bytes of the data instead, written with fwrite and read with fread. It is smaller and faster because no conversion happens, and an entire record can be written in one call. But it is unreadable in an editor and may not transfer between machines that store numbers differently.

Text fileBinary file
Storescharactersraw bytes
Readable in an editoryesno
Sizelargersmaller
Speedslower — values are convertedfaster — no conversion
Functionsfprintf, fscanf, fgetsfwrite, fread
Portable between systemsyesnot always

Which to choose

Use a text file when a person may need to read or edit it, when it must be shared between different programs or systems, or when it is a configuration or log. Use a binary file for large volumes of numeric data where size and speed matter and only your own program will read it. For the quantities in a school project, text is almost always the right answer.

Practice questions

6 questions · 20 marks · full working on every one

Try each one on paper first, then open the working. The marks are shown where they are actually awarded, because that is where they are actually lost.

Short questions

3 · 6 marks

Two marks each, in the style of the short-question section of the paper. Answer in two or three lines.

SQ1[2 marks]
Why are files needed when a program already has variables?
Model answer

Variables are held in RAM, which is volatile — their contents are lost the moment the program ends or the power fails. A file is stored on secondary storage, so the data persists and can be read by the same program on another day or by a different program entirely.

Examiner tip. The word "persist" or "non-volatile" is the mark. Add that files also allow data to be shared between programs.

SQ2[2 marks]
What does fopen return if it fails, and why must this be checked?
Model answer

It returns NULL. Using a NULL file pointer in any subsequent read or write causes the program to crash, so the check turns an unexplained failure into a clear message such as "cannot open file".

Examiner tip. Give the consequence of not checking. It is what makes the check worth two lines of code.

SQ3[2 marks]
State the difference between opening a file in "w" mode and "a" mode.
Model answer

"w" erases the entire contents of an existing file the moment it is opened, then writes from the beginning. "a" preserves the contents and adds new data at the end. Both create the file if it does not exist.

Examiner tip. Emphasise that "w" truncates on opening, before anything is written. That timing is what makes the mistake so destructive.

Solved numericals

2 · 8 marks

Full working, one step per line, with the marks shown where they are awarded.

N1[4 marks]
Write a C fragment that opens "data.txt" for reading, checks it opened, reads and prints integers until the end, and closes it.
Full working
  1. FILE *fp; int n; fp = fopen("data.txt", "r");[1]
  2. if (fp == NULL) { printf("Cannot open file\n"); return 1; }the NULL check is a mark[1]
  3. while (fscanf(fp, "%d", &n) == 1) { printf("%d\n", n); }testing the return value, not feof[1]
  4. fclose(fp);[1]

Open, check NULL, loop while fscanf returns 1, close.

Examiner tip. Testing fscanf(...) == 1 rather than !feof(fp) is the detail that separates a correct loop from one that processes the last value twice.

N2[4 marks]
A program should add a new record to an existing file but instead the file contains only the newest record. Explain the fault and its correction.
Full working
  1. The file has been opened in "w" mode instead of "a"[1]
  2. "w" truncates the file to zero length as soon as it is opened, destroying every existing recordthe truncation happens on opening[1]
  3. The new record is then written into an empty file, which is why only it remains[1]
  4. Correct it by opening with "a", which preserves the contents and appends at the end[1]

The file was opened with "w", which truncates it. Use "a" to append.

Examiner tip. This fault destroys data irrecoverably, which is why it is worth the habit of writing "a" first and changing it to "w" only deliberately.

Long questions

1 · 6 marks

Theory and numerical together, as they appear in the long-question section.

LQ1[6 marks]
A school stores student marks in a text file, one record per line as a name followed by a mark.
  1. Write the code to read the file and count how many students scored 50 or above.
  2. Explain why fclose must be called.
  3. Explain why while (!feof(fp)) is an unreliable loop condition.
Mark scheme
  1. Opens the file in "r" mode and checks the pointer is not NULL[1]
  2. while (fscanf(fp, "%s %d", name, &marks) == 2) {two items requested, two expected back[1]
  3. if (marks >= 50) count++; } then prints the count after the loopcount initialised before the loop[1]
  4. fclose flushes the output buffer, so data still held in memory is actually written to disk[1]
  5. It also releases the file handle, a limited operating-system resource that would otherwise run out[1]
  6. feof becomes true only after a read has already failed, so the loop body runs one extra time and the previous values are processed twicetesting the read's return value avoids this[1]

(a) open, check, loop on fscanf == 2 counting passes (b) flushes the buffer and releases the handle (c) feof is set after a failed read, so the last record is processed twice

Examiner tip. Part (c) is the sharpest point in the chapter. The rule is to test whether the read succeeded, not whether the end has been reached — because you only know the end was reached by failing to read.