C Mastery / errno.h, assert.h, and Error-Handling Patterns
Part 3 — The Standard Library

errno.h, assert.h, and Error-Handling Patterns

This chapter covers <errno.h>, <assert.h>, and the broader discipline of error handling in C: return codes, error propagation, cleanup, and API design.

Why This Matters

C has no exceptions. Error handling is entirely a matter of convention and discipline. The errno mechanism, assertions, and structured cleanup patterns are how robust C programs report and recover from failures.

Prerequisites

Core Concept

errno

errno is a modifiable lvalue (usually a macro) set by library functions to indicate an error. It is declared in <errno.h>, along with error-code macros (EDOM, ERANGE, EINVAL, etc.).

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

int main(void)
{
    FILE *f = fopen("missing.txt", "r");
    if (f == NULL) {
        perror("fopen");               /* prints message + errno text */
        fprintf(stderr, "errno=%d (%s)\n", errno, strerror(errno));
        return 1;
    }
    fclose(f);
    return 0;
}

Set errno = 0 before a call if you need to distinguish "no error" from a stale value; library functions may leave errno unchanged on success.

assert

assert(expr) (from <assert.h>) aborts the program if expr is false. It is disabled when NDEBUG is defined, so it must never be used for runtime error handling — only for programmer errors (invariants).

#include <assert.h>

int main(void)
{
    int *p = NULL;
    assert(p != NULL);   /* aborts if NDEBUG is not defined */
    return 0;
}

Error-Handling Patterns

Return codes

Functions return a status (0 for success, negative or nonzero for error) and communicate results through output parameters.

int parse(const char *s, int *out);

Error enums and structs

Define an error enum for richer status:

enum Error { OK = 0, ERR_IO, ERR_PARSE, ERR_RANGE };

enum Error do_work(void);

Error propagation

Return errors up the call stack, translating and annotating as needed. Do not swallow errors silently.

Cleanup with goto

Use the goto cleanup pattern to release resources in reverse order on error (see c.core.16).

Ownership

Every resource must have a clear owner; the owner frees it exactly once (c.mem.ownership).

Examples

Robust error handling with cleanup

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

int process(void)
{
    FILE *f = fopen("in.txt", "r");
    if (f == NULL) return -1;

    char *buf = malloc(1024);
    if (buf == NULL) goto cleanup_file;

    if (fread(buf, 1, 1024, f) == 0) goto cleanup_buf;

    free(buf);
cleanup_file:
    fclose(f);
    return 0;

cleanup_buf:
    free(buf);
    fclose(f);
    return -1;
}

Common Mistakes

Undefined Behavior

assert with side effects) can change program behavior when NDEBUG is defined.

Portability

Under the Hood

errno is typically a thread-local variable (in threaded implementations), so each thread has its own error state. assert compiles to a check that calls abort if the expression is false, unless NDEBUG removes it.

Practical Usage

Exercises

1. Write a function that uses strtol and reports errors via errno and a return code. 2. Demonstrate assert and the effect of defining NDEBUG. 3. Write a multi-resource function using goto cleanup. 4. Design a small API with an error enum and output parameters.

Deep Challenge

Design an error-handling policy for a library: define an error enum, an error reporting convention, and an ownership contract, then implement a function that demonstrates error propagation, cleanup, and errno usage. Justify each decision.

References

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.lib.errno06