Nearby lessons

118 of 124

C - Close a File

fclose flushes buffered data to disk and releases the file handle. Learn why closing is not optional, what buffering means for your data, how to check whether the close succeeded, and how to close correctly on every exit path.

The Syntax

One argument, one return value. The return value is worth checking:

Example01
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 FILE *fp = fopen("data.txt", "w");
6 if (fp == NULL) { perror("fopen"); return 1; }
7 
8 fprintf(fp, "Some data\n");
9 
10 if (fclose(fp) == 0)
11 printf("Closed successfully\n");
12 else
13 perror("fclose");
14 
15 return 0;
16}
Output
Closed successfully

What Closing Actually Does

In simple words: your writes do not go straight to disk. They collect in a memory buffer, and only travel to the disk when the buffer fills, when you call fflush, or when you call fclose. That is why forgetting to close can lose data that your program is certain it wrote.
StepWhat happens
1Any buffered writes are flushed to the operating system
2The buffer memory is released
3The OS file handle is released
4The FILE structure becomes invalid
Example02
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 FILE *fp = fopen("buffered.txt", "w");
6 if (fp == NULL) return 1;
7 
8 fprintf(fp, "This text sits in a memory buffer\n");
9 printf("Written to the buffer - the file may still be 0 bytes\n");
10 
11 fflush(fp); /* force it out without closing */
12 printf("After fflush - the data is now with the OS\n");
13 
14 fprintf(fp, "More buffered text\n");
15 fclose(fp); /* flushes the rest and releases everything */
16 printf("After fclose - everything is written\n");
17 return 0;
18}
Output
Written to the buffer - the file may still be 0 bytes
After fflush - the data is now with the OS
After fclose - everything is written

Losing Data by Not Closing

If the program crashes or is killed before fclose, buffered data is gone. A normal return from main flushes open streams for you, which is exactly why this bug hides so well — it only appears when something goes wrong, which is when you need the log file most.
Example03
CCode Cell
1#include <stdio.h>
2#include <stdlib.h>
3 
4int main()
5{
6 FILE *fp = fopen("log.txt", "w");
7 if (fp == NULL) return 1;
8 
9 fprintf(fp, "Step 1 complete\n");
10 fprintf(fp, "Step 2 complete\n");
11 
12 /* If the program aborted here, log.txt would very likely be empty:
13 
14 abort(); <- buffers are NOT flushed
15 exit(1); <- buffers ARE flushed
16 return 1; <- buffers ARE flushed
17 */
18 
19 fflush(fp); /* flush after each important entry */
20 printf("Log is safe on disk even if we crash now\n");
21 
22 fclose(fp);
23 return 0;
24}
Output
Log is safe on disk even if we crash now

The Return Value Matters

fclose is where a full disk finally reports itself. The earlier fprintf calls succeeded because they only wrote to memory:

Example04
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 FILE *fp = fopen("report.txt", "w");
6 if (fp == NULL) { perror("fopen"); return 1; }
7 
8 fprintf(fp, "Line 1\n"); /* succeeds - buffered in memory */
9 fprintf(fp, "Line 2\n"); /* succeeds - buffered in memory */
10 
11 /* The actual disk write happens here */
12 if (fclose(fp) != 0)
13 {
14 perror("fclose"); /* e.g. No space left on device */
15 printf("The data may NOT be on disk\n");
16 return 1;
17 }
18 
19 printf("Confirmed on disk\n");
20 return 0;
21}
Output
Confirmed on disk

Never Use a Closed Pointer

After fclose, the FILE * is dangling. Setting it to NULL turns a silent disaster into a testable condition:

Example05
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 FILE *fp = fopen("test.txt", "w");
6 if (fp == NULL) return 1;
7 
8 fprintf(fp, "before closing\n");
9 fclose(fp);
10 
11 /* fprintf(fp, "after"); UNDEFINED BEHAVIOUR - dangling pointer */
12 /* fclose(fp); UNDEFINED BEHAVIOUR - double close */
13 
14 fp = NULL; /* the fix */
15 
16 if (fp != NULL) fprintf(fp, "safe\n");
17 else printf("fp is NULL - safely testable\n");
18 return 0;
19}
Output
fp is NULL - safely testable

One fclose Per fopen

Handles are a limited resource. A loop that opens without closing exhausts them and every later fopen starts failing:

Example06
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 FILE *fp;
6 char name[20];
7 int i;
8 
9 /* WRONG - leaks a handle on every iteration
10 for (i = 0; i < 1000; i++)
11 fp = fopen("data.txt", "r"); no fclose
12 */
13 
14 /* RIGHT - matched pairs */
15 for (i = 0; i < 3; i++)
16 {
17 sprintf(name, "file%d.txt", i);
18 
19 fp = fopen(name, "w");
20 if (fp == NULL) { perror(name); continue; }
21 
22 fprintf(fp, "File number %d\n", i);
23 fclose(fp); /* closed before the next open */
24 }
25 
26 printf("3 files created, 3 handles released\n");
27 return 0;
28}
Output
3 files created, 3 handles released

fflush vs fclose

Use fflush when you want the data on disk but still need the file open:

fflush(fp)fclose(fp)
Flushes the bufferYesYes
Releases the handleNoYes
File stays usableYesNo
Typical useLive logs, progress filesFinished with the file
Example07
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 FILE *log = fopen("progress.log", "w");
6 int step;
7 
8 if (log == NULL) return 1;
9 
10 for (step = 1; step <= 3; step++)
11 {
12 fprintf(log, "Step %d finished\n", step);
13 fflush(log); /* readable by another program immediately */
14 printf("Step %d logged and flushed\n", step);
15 }
16 
17 fclose(log);
18 return 0;
19}
Output
Step 1 logged and flushed
Step 2 logged and flushed
Step 3 logged and flushed

Closing on Every Exit Path

An early return in the middle of a function is the classic way to leak a handle. Use a single cleanup point:

Example08
CCode Cell
1#include <stdio.h>
2 
3int processFile(const char *name)
4{
5 FILE *fp = fopen(name, "r");
6 char line[100];
7 int result = 0;
8 
9 if (fp == NULL) { perror(name); return 1; }
10 
11 if (fgets(line, sizeof line, fp) == NULL)
12 {
13 printf("The file is empty\n");
14 result = 1;
15 goto cleanup; /* one exit, one fclose */
16 }
17 
18 printf("First line: %s", line);
19 
20cleanup:
21 fclose(fp);
22 return result;
23}
24 
25int main()
26{
27 FILE *fp = fopen("input.txt", "w");
28 if (fp) { fprintf(fp, "Hello from the file\n"); fclose(fp); }
29 
30 return processFile("input.txt");
31}
Output
First line: Hello from the file

Closing Several Files

Close each one, and if a later open fails, close the ones already open before returning:

Example09
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 FILE *in = NULL, *out = NULL;
6 int ch, status = 0;
7 
8 in = fopen("source.txt", "w");
9 if (in) { fprintf(in, "data to copy\n"); fclose(in); }
10 
11 in = fopen("source.txt", "r");
12 if (in == NULL) { perror("source"); return 1; }
13 
14 out = fopen("dest.txt", "w");
15 if (out == NULL)
16 {
17 perror("dest");
18 fclose(in); /* release what we hold */
19 return 1;
20 }
21 
22 while ((ch = fgetc(in)) != EOF) fputc(ch, out);
23 
24 if (fclose(in) != 0) status = 1;
25 if (fclose(out) != 0) status = 1; /* check both */
26 
27 printf(status ? "A close failed\n" : "Copied and closed cleanly\n");
28 return status;
29}
Output
Copied and closed cleanly

fcloseall and Program Exit

A normal exit flushes and closes open streams for you. Relying on it is still poor practice — it hides errors and delays writes:

Example10
CCode Cell
1#include <stdio.h>
2#include <stdlib.h>
3 
4int main()
5{
6 FILE *fp = fopen("auto.txt", "w");
7 if (fp == NULL) return 1;
8 
9 fprintf(fp, "Flushed automatically at normal exit\n");
10 
11 /* All of these flush and close open streams:
12 return from main
13 exit(status)
14 
15 These do NOT:
16 abort()
17 _Exit(status)
18 a segmentation fault
19 the process being killed */
20 
21 printf("Returning without an explicit fclose\n");
22 printf("It works, but you lose the error check.\n");
23 return 0;
24}
Output
Returning without an explicit fclose
It works, but you lose the error check.

Common Mistakes

  • Forgetting fclose — leaks a handle and risks unwritten data.
  • Ignoring the return value — that is where a full-disk error appears.
  • Using a FILE * after closing it — undefined behaviour; set it to NULL.
  • Closing twice — also undefined behaviour.
  • Closing a NULL pointer — unlike free(NULL), this is not safe.
  • Opening in a loop without closing — exhausts the handle limit.
  • Returning early past the fclose — use a single cleanup label.
  • Assuming fprintf reaching the disk — it reaches a buffer.
Trainer's Note: write the fclose immediately after the fopen and its NULL check, then fill in the work between them. You will never forget it, and every early return you add afterwards will visibly sit above a close that is already there.
📝 Key Takeaways
  • fclose(fp) returns 0 on success and EOF on failure.
  • Closing flushes the buffer — without it, written data can be lost.
  • Every successful fopen needs exactly one fclose.
  • Never use a FILE pointer after closing it; set it to NULL.
  • fflush writes the buffer out without closing the file.

🧠 Test Your Knowledge

4 Questions
Progress: 0 / 4