Code of the Day
IntermediateFile I/O

Opening and closing files

Open, close, and check the status of files in C using fopen, fclose, and the FILE type — including mode strings and error handling.

CIntermediate10 min read
Recommended first
By the end of this lesson you will be able to:
  • Open a file with fopen and check for failure
  • Identify the common mode strings and their effects
  • Close a file with fclose and understand why it matters
  • Distinguish between text mode and binary mode

Every meaningful program eventually reads from or writes to persistent storage. C's file I/O is built around the FILE type — an opaque struct that represents an open file and manages the underlying buffering. The interface is part of the C standard library and works identically on Linux, macOS, and Windows (with some platform differences in text mode).

fopen — opening a file

FILE *fopen(const char *filename, const char *mode);

fopen returns a FILE * on success, NULL on failure.

#include <stdio.h>

int main(void) {
    FILE *fp = fopen("data.txt", "r");
    if (fp == NULL) {
        perror("fopen"); /* prints: "fopen: No such file or directory" */
        return 1;
    }

    /* ... use the file ... */

    fclose(fp);
    return 0;
}

perror(prefix) prints prefix: followed by the human-readable error message for the most recent system error. Always call it immediately after a failed system call.

Mode strings

ModeEffect
"r"Read. File must exist.
"w"Write. Creates file; truncates if exists.
"a"Append. Creates file; writes at end if exists.
"r+"Read and write. File must exist.
"w+"Read and write. Truncates or creates.
"a+"Read and append.
"rb", "wb", etc.Binary mode (no newline translation).

On Unix/Linux, text mode and binary mode are identical — there is no newline translation. On Windows, text mode translates \n to \r\n on write and back on read. Use binary mode ("rb", "wb") when reading binary files to ensure no translation occurs on any platform.

fclose — closing a file

Always close files when you are done:

int fclose(FILE *fp);

fclose flushes any buffered output to disk and releases the file descriptor. Returns 0 on success, EOF on error (the error is rare but possible if the final flush fails — e.g., disk full).

Failing to close a file has two consequences:

  1. Buffered output may not be written. The C library buffers writes for efficiency. If the program exits without flushing the buffer, the last writes are lost.
  2. File descriptor leak. Each open file uses a file descriptor (a small integer from the OS). Most systems limit each process to ~1,024 open files. Leaking descriptors eventually prevents opening new files.

Check fclose for write errors. If writing to a file, check fclose's return value. A full disk or network error may only manifest at close time when the OS flushes buffers. Silently ignoring fclose return values is a common bug in programs that write important data.

Checking if a file exists

Open with "r" and check for NULL:

FILE *fp = fopen("config.txt", "r");
if (fp == NULL) {
    /* file does not exist or is not readable */
} else {
    fclose(fp);
    /* file exists */
}

There is no standard C function to test file existence without opening. POSIX provides access() but that has race conditions. Opening is the correct way.

Standard streams

Three pre-opened FILE * values are always available:

NameTypeDescription
stdinInput — keyboard by default
stdoutOutput — terminal by default
stderrOutput — terminal by default, unbuffered
fprintf(stdout, "Normal output\n");
fprintf(stderr, "Error: something went wrong\n");

stderr is typically unbuffered — writes appear immediately even if stdout is being redirected. Always write error messages to stderr, not stdout.

A pattern for robust file operations

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    FILE *fp = fopen("output.txt", "w");
    if (!fp) {
        perror("fopen");
        return EXIT_FAILURE;
    }

    fprintf(fp, "Hello from C\n");
    fprintf(fp, "Line 2\n");

    if (fclose(fp) != 0) {
        perror("fclose");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

EXIT_SUCCESS (0) and EXIT_FAILURE (1) from <stdlib.h> make the intent explicit.

Where to go next

Next: reading and writing text filesfscanf, fgets, fprintf, and fputs for working with line-oriented text data.

Finished reading? Mark it complete to track your progress.

On this page