Skip to content
Home
C File I/O: Reading and Writing Files

C File I/O: Reading and Writing Files

C/C++ C/C++ 9 min read 1780 words Intermediate ExcellentWiki Editorial Team

File input/output is fundamental to C programming. Unlike higher-level languages, C exposes the raw operating system interface for file operations. You manage buffers, check return codes, and handle errors explicitly. This gives you complete control but demands careful attention to detail.

This guide covers text and binary file I/O, formatted reading and writing, error handling, and practical patterns for real-world file processing.

Opening and Closing Files

The fopen function opens a file and returns a FILE* pointer:

#include <stdio.h>

FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
    perror("Failed to open file");
    return 1;
---

// File operations here

fclose(fp);

File Modes

ModeDescription
"r"Read (file must exist)
"w"Write (creates or truncates)
"a"Append (creates if missing)
"r+"Read and write (file must exist)
"w+"Read and write (creates or truncates)
"a+"Read and append (creates if missing)
"rb"Binary read
"wb"Binary write
"ab"Binary append

Always check the return value of fopen. A NULL return means the file could not be opened. Use perror() to print the system error message.

Text File I/O

Reading Characters

int ch;
FILE *fp = fopen("input.txt", "r");

while ((ch = fgetc(fp)) != EOF) {
    putchar(ch);
---

fclose(fp);

fgetc returns int (not char) because EOF is a negative value that cannot be represented in unsigned char.

Reading Lines with fgets

char line[1024];

while (fgets(line, sizeof(line), fp) != NULL) {
    // Remove trailing newline
    line[strcspn(line, "\n")] = '\0';
    printf("Read: %s\n", line);
---

fgets reads up to n-1 characters or until a newline, then null-terminates the buffer. It includes the newline if there is room. Use strcspn to strip the trailing newline safely.

Formatted Reading with fscanf

// data.txt:
// Alice 42 3.14
// Bob 37 2.71

char name[64];
int age;
double value;

while (fscanf(fp, "%63s %d %lf", name, &age, &value) == 3) {
    printf("%s is %d years old, value = %.2f\n", name, age, value);
---

Check the return value of fscanf — it returns the number of successfully matched items. A return value less than expected indicates a parse error or EOF. Use width specifiers (%63s) to prevent buffer overflows.

Formatted Writing with fprintf

FILE *fp = fopen("output.txt", "w");
if (fp == NULL) {
    perror("Failed to create output file");
    return 1;
---

fprintf(fp, "Name: %s\n", "Alice");
fprintf(fp, "Score: %d / %d\n", 95, 100);
fprintf(fp, "Average: %.2f\n", 85.7);

fclose(fp);

Binary File I/O

Binary I/O reads and writes raw bytes without formatting. It is more compact and precise than text I/O:

Writing Binary Data

#include <stdio.h>
#include <stdint.h>

typedef struct {
    uint32_t id;
    char name[32];
    float salary;
--- Employee;

Employee emp = {
    .id = 1001,
    .name = "Alice Smith",
    .salary = 75000.50
---;

FILE *fp = fopen("employees.bin", "wb");
if (fp == NULL) {
    perror("Cannot open file");
    return 1;
---

size_t written = fwrite(&emp, sizeof(Employee), 1, fp);
if (written != 1) {
    perror("Write failed");
    fclose(fp);
    return 1;
---

fclose(fp);

Reading Binary Data

Employee emp;

FILE *fp = fopen("employees.bin", "rb");
if (fp == NULL) {
    perror("Cannot open file");
    return 1;
---

size_t read = fread(&emp, sizeof(Employee), 1, fp);
if (read != 1) {
    if (feof(fp)) {
        fprintf(stderr, "Unexpected end of file\n");
    } else {
        perror("Read failed");
    }
    fclose(fp);
    return 1;
---

printf("ID: %u, Name: %s, Salary: %.2f\n", emp.id, emp.name, emp.salary);
fclose(fp);

Binary Arrays

// Write an array
int data[] = {10, 20, 30, 40, 50};
size_t count = sizeof(data) / sizeof(data[0]);

fwrite(data, sizeof(int), count, fp);

// Read an array
int buffer[100];
size_t items_read = fread(buffer, sizeof(int), count, fp);

for (size_t i = 0; i < items_read; i++) {
    printf("%d ", buffer[i]);
---

Random Access

fseek and ftell let you navigate within a file:

FILE *fp = fopen("data.bin", "rb");
if (fp == NULL) return 1;

// Seek to 100 bytes from the start
fseek(fp, 100, SEEK_SET);

// Seek 50 bytes forward from current position
fseek(fp, 50, SEEK_CUR);

// Seek 20 bytes before the end
fseek(fp, -20, SEEK_END);

// Get current position
long pos = ftell(fp);

// Seek to a specific record
int record_num = 5;
fseek(fp, record_num * sizeof(Record), SEEK_SET);
fread(&record, sizeof(Record), 1, fp);

fclose(fp);

SEEK_SET, SEEK_CUR, and SEEK_END control the reference point. ftell returns the current file position or -1 on error. For files larger than 2 GB, use fseeko and ftello with off_t.

Error Handling

Checking for Errors

FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
    fprintf(stderr, "Error opening file: %s\n", strerror(errno));
    return 1;
---

int ch;
while ((ch = fgetc(fp)) != EOF) {
    putchar(ch);
---

if (ferror(fp)) {
    fprintf(stderr, "Error reading file\n");
    clearerr(fp);
---

fclose(fp);

| Function | Purpose | |

Text vs Binary File Modes

When opening files, C distinguishes between text mode and binary mode. On Unix-like systems they are identical, but on Windows text mode translates \n to \r\n on write and \r\n to \n on read. Binary mode ("rb", "wb") disables this translation, which is essential for reading non-text data like images or serialized structures. Always use binary mode when working with non-text data to avoid corruption across platforms.

File Positioning

ftell() returns the current position in the file, and fseek() moves to a specific position:

fseek(fp, 0, SEEK_END);
long size = ftell(fp);  // Get file size
rewind(fp);             // Go back to beginning

fseek(fp, 100, SEEK_SET);  // Go to byte 100 from start
fseek(fp, -50, SEEK_CUR);  // Go back 50 bytes from current
fseek(fp, 0, SEEK_END);    // Go to end

Random access is useful for database files, index structures, and record-based file formats where you need to read or update specific records without scanning the entire file.

Error Handling with errno

File operations can fail for many reasons — disk full, permission denied, file not found. Always check return values and use errno and perror() to diagnose failures:

#include <errno.h>
#include <string.h>

FILE *fp = fopen("config.dat", "r");
if (!fp) {
    fprintf(stderr, "Failed to open file: %s\n", strerror(errno));
    return 1;
---

Common error codes include ENOENT (file not found), EACCES (permission denied), ENOSPC (disk full), and EISDIR (path is a directory). Handling these specifically allows your program to give users meaningful error messages rather than a generic failure.

FAQ

What is the difference between malloc and new in C++? malloc allocates raw memory without calling constructors; new allocates memory and calls the constructor. In C++, prefer new for objects. free vs delete follows the same pattern — delete calls the destructor.

How do I prevent memory leaks in C/C++? Use RAII (Resource Acquisition Is Initialization) in C++ — smart pointers like std::unique_ptr and std::shared_ptr automatically free memory. In C, always pair every malloc with a free and use tools like Valgrind or AddressSanitizer to detect leaks.

What is undefined behavior in C/C++? Undefined behavior occurs when code performs operations that the language standard does not define — dereferencing a null pointer, buffer overflow, signed integer overflow. The compiler may generate any code, including unexpected results or crashes.

Should I learn C or C++ first? Learn C first if you want to understand low-level memory and system programming. Learn C++ first if you want object-oriented features and the STL. Both are valuable; C++ builds on C concepts.

What is the difference between a header file and a source file? Header files (.h) declare interfaces — function prototypes, class definitions, macros. Source files (.c or .cpp) implement the declarations. Headers are #included; source files are compiled separately and linked.

———-|———| | ferror(fp) | Check if error indicator is set | | feof(fp) | Check if end-of-file indicator is set | | clearerr(fp) | Clear both error and EOF indicators | | perror(msg) | Print msg: <system error> to stderr | | strerror(errno) | Get string description of error code |

Robust Read Loop

char buffer[4096];
size_t bytes_read;

while ((bytes_read = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
    size_t written = fwrite(buffer, 1, bytes_read, out_fp);
    if (written != bytes_read) {
        perror("Write failed");
        break;
    }
---

if (ferror(fp)) {
    perror("Read error");
---

Buffering

C’s file I/O is buffered by default. Three buffering modes are available:

#include <stdio.h>

// No buffering — writes go directly to disk
setvbuf(fp, NULL, _IONBF, 0);

// Line buffering — flush on newline (default for terminals)
setvbuf(fp, NULL, _IOLBF, 0);

// Full buffering — flush when buffer is full (default for files)
char buf[8192];
setvbuf(fp, buf, _IOFBF, sizeof(buf));

Set buffering before any I/O operations on the file. For performance-sensitive applications, a large custom buffer (64 KB or more) can significantly reduce system calls.

Practical Examples

CSV Parser

#include <stdio.h>
#include <string.h>

int parse_csv(const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) return -1;

    char line[4096];
    int line_num = 0;

    while (fgets(line, sizeof(line), fp)) {
        line_num++;
        line[strcspn(line, "\n")] = '\0';

        char *token = strtok(line, ",");
        int col = 0;

        while (token) {
            printf("Row %d, Col %d: %s\n", line_num, col++, token);
            token = strtok(NULL, ",");
        }
    }

    fclose(fp);
    return 0;
---

File Copy

int copy_file(const char *src, const char *dst) {
    FILE *in = fopen(src, "rb");
    if (in == NULL) {
        perror("Source open failed");
        return -1;
    }

    FILE *out = fopen(dst, "wb");
    if (out == NULL) {
        perror("Dest open failed");
        fclose(in);
        return -1;
    }

    char buffer[65536];
    size_t bytes;

    while ((bytes = fread(buffer, 1, sizeof(buffer), in)) > 0) {
        if (fwrite(buffer, 1, bytes, out) != bytes) {
            perror("Write failed");
            fclose(in);
            fclose(out);
            return -1;
        }
    }

    if (ferror(in)) {
        perror("Read failed");
        fclose(in);
        fclose(out);
        return -1;
    }

    fclose(in);
    fclose(out);
    return 0;
---

Temporary Files

#include <stdlib.h>

void process_temp(void) {
    char template[] = "/tmp/myapp_XXXXXX";
    int fd = mkstemp(template);
    if (fd == -1) {
        perror("mkstemp failed");
        return;
    }

    FILE *fp = fdopen(fd, "w+");
    if (fp == NULL) {
        perror("fdopen failed");
        close(fd);
        return;
    }

    fprintf(fp, "Temporary data\n");
    rewind(fp);

    char buf[256];
    fgets(buf, sizeof(buf), fp);
    printf("Read back: %s", buf);

    fclose(fp);
    unlink(template);  // Delete the temp file
---

Best Practices

  • Always check return values — Every I/O function can fail. Check and handle errors.
  • Close files promptly — Not closing leads to resource leaks. Use fclose in cleanup paths.
  • Use sizeof on variablesfread(buf, 1, sizeof(buf), fp) is correct even if buf type changes.
  • Prefer binary for structured data — Binary is more compact and avoids parsing ambiguity.
  • Flush before seeking between read/write — Call fflush or fseek between read and write operations on "r+" files.
  • Use fixed-width integer typesuint32_t guarantees portability in binary files.

Summary

C’s file I/O library provides complete control over reading and writing data. Text I/O with fgets, fscanf, and fprintf handles human-readable formats. Binary I/O with fread and fwrite stores structured data compactly. Error checking with ferror, feof, and return values from every function call separates robust programs from fragile ones. Buffering, random access, and temporary file functions complete the toolkit for production file operations.

For a comprehensive overview, read our article on C Memory Management.

For a comprehensive overview, read our article on C Pointers Guide.

Section: C/C++ 1780 words 9 min read Intermediate 756 articles in section Report inaccuracy Back to top