Home / Free Tutorials

Practical Learning Guide

Reading and Writing Files in C

Practice stream creation, checked writes, bounded reads and cleanup with a complete C example

This self-study guide explains the topic and provides a practical starting point. For trainer-led learning, review the related course and request the current syllabus and batch details.

Read the Study Plan

Follow the stream lifecycle

Plan the program as create or open, check, write, reposition or reopen for reading, read, inspect the result and close. The example uses tmpfile so the exercise does not overwrite a named document. For a persistent file, choose an explicit path and review the fopen mode first; opening an existing file with w can truncate it.

Make the first run reproducible

Save the example as file_io.c and compile with gcc -std=c11 -Wall -Wextra -Werror file_io.c -o file_io. Run ./file_io and compare the output below. The supplied records are intentionally shorter than the 64-byte buffer. Keep this first run unchanged so unexpected output can be separated from changes you introduce during practice.

Test an empty stream deliberately

Run ./file_io empty. The program creates the stream but skips writing records, so the documented result is chunks read: 0. This is a normal end-of-input case, not a read failure. Compare that outcome with tmpfile returning NULL or ferror reporting a read problem, both of which make the program return a failure status.

Understand bounded reading

fgets reads into the buffer within the supplied capacity and terminates a successfully read string. A newline that is read stays in the string. One successful call does not necessarily represent a whole logical line: a longer record may need several calls. The counter therefore says chunks rather than lines. Do not claim this small demonstration parses arbitrary CSV or binary files.

Check state changes and cleanup

fseek returns the update stream to its beginning before reading; its result is checked. The example also checks writing, reading and closing outcomes. A single cleanup label ensures that the stream is closed after an error once it has been created. The temporary file is removed when closed; this example is for practice, not persistent storage.

Extend the exercise in controlled steps

First change one record and confirm both content and chunk count. Next make a record longer than the buffer and predict how many fgets calls it needs, including any remaining newline. Then build a separate named-file version in a disposable folder, using r to read existing content or a to append. Never point an introductory w-mode exercise at files you need to keep.

Explain what the example does not test

The empty option is tested, but it does not simulate a storage failure. To investigate actual I/O failures, use an isolated test environment and describe the failing operation and return value. Do not assume a NULL read result always means normal end-of-file, and do not continue using a FILE pointer after fclose.

Complete file_io.c example

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

int main(int argc, char **argv)
{
    int empty = argc == 2 && strcmp(argv[1], "empty") == 0;
    if (argc > 2 || (argc == 2 && !empty)) {
        fputs("usage: file_io [empty]\n", stderr);
        return 2;
    }

    FILE *stream = tmpfile();
    if (stream == NULL) {
        perror("tmpfile");
        return 1;
    }

    int status = 0;
    if (!empty && fputs("Java practice\nC file handling\n", stream) == EOF) {
        perror("write");
        status = 1;
        goto cleanup;
    }
    if (fseek(stream, 0L, SEEK_SET) != 0) {
        perror("seek");
        status = 1;
        goto cleanup;
    }

    char buffer[64];
    unsigned int chunks = 0;
    while (fgets(buffer, sizeof buffer, stream) != NULL) {
        if (fputs(buffer, stdout) == EOF) {
            status = 1;
            goto cleanup;
        }
        ++chunks;
    }
    if (ferror(stream)) {
        fputs("read failed\n", stderr);
        status = 1;
        goto cleanup;
    }
    if (printf("chunks read: %u\n", chunks) < 0) {
        status = 1;
    }

cleanup:
    if (fclose(stream) == EOF) {
        perror("close");
        status = 1;
    }
    return status;
}

Expected result

Normal run:
Java practice
C file handling
chunks read: 2

Run with empty argument:
chunks read: 0

Further reading

Check your progress

Keep your working files, test cases and notes together. Explain one design decision, demonstrate a failure case and list what you would improve next. These concrete outputs make it easier to identify the next skill to practice.