Skip to content

Chapter 20 of 21

Error Handling

Design status codes, errno diagnostics, cleanup paths, and defensive APIs.

28 minutes 10 quick checksBy Subha Prasad
Lesson 20 of 21Course navigation

Lesson content

Read, practise, then check your understanding

C has no exception mechanism. Functions report failure through return values, output parameters, status enums, and occasionally errno. A reliable program checks failures where they occur and preserves enough context to diagnose them.

Return-value contracts

Choose one unambiguous convention and document it.

enum ParseResult {
    PARSE_OK,
    PARSE_INVALID,
    PARSE_OUT_OF_RANGE
};

enum ParseResult parse_port(const char *text, unsigned *port);

Separate status from output so every valid output value remains representable. Do not ignore functions whose return value reports a short write, failed close, or partial operation.

errno

Some library and system functions set errno after failure. Include <errno.h>, check the function’s documented failure result first, and then inspect errno promptly.

errno = 0;
char *end = NULL;
long value = strtol(text, &end, 10);

if (end == text) {
    /* no digits */
} else if (errno == ERANGE) {
    /* outside long range */
} else if (*end != '\0') {
    /* unexpected suffix */
}

Do not test errno after success; successful functions need not clear an old value. perror and strerror provide human-readable messages, while production logs should also retain operation and input context without leaking secrets.

Cleanup paths

When a function owns multiple resources, acquire in order and release in reverse. A forward goto cleanup keeps each failure path correct and avoids deeply nested blocks.

int transform(const char *path) {
    int status = -1;
    FILE *file = fopen(path, "rb");
    unsigned char *data = NULL;
    if (!file) goto cleanup;
    data = malloc(4096);
    if (!data) goto cleanup;
    status = process(file, data);
cleanup:
    free(data);
    if (file && fclose(file) != 0 && status == 0) status = -1;
    return status;
}

Assertions and recovery

assert documents internal conditions that indicate programmer defects. It may disappear under NDEBUG, so never use it to validate external input or perform required side effects. Recoverable failures should return a status; impossible internal states may justify an assertion or controlled termination.

Test error paths deliberately, including allocation failure, truncated input, permission errors, and cleanup after partial initialization.

Knowledge check

Answer every question correctly to complete this chapter.

When is errno meaningful?
What is a useful single-exit cleanup pattern in C?
Why separate a status return from an output parameter?
Which function prints context followed by a description of errno?
Should code test errno without first observing a documented failure result?
Why release resources in reverse acquisition order?
What should assert normally represent?
Why must required work not occur only inside assert?
What does strtol’s end pointer help detect?
Which error paths deserve deliberate tests?

0 of 10 checks passed

Your progress is saved on this device.

Error Handling | C Lesson | Subha Prasad