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
c.stdlib.1— library overview.
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
- Using
assertfor runtime/user-input errors (it disappears withNDEBUG). - Forgetting to set
errno = 0before a call when checking for "no error." - Not checking return values.
- Swallowing errors or returning success after a failure.
Undefined Behavior
- None inherent to
errno/assertthemselves, but incorrect use (e.g., using
assert with side effects) can change program behavior when NDEBUG is defined.
Portability
errno,perror,strerror,assertare standard.strerroris not required to be thread-safe; POSIX providesstrerror_r.
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
- Check every fallible call's return value.
- Use
assertfor invariants, not user-facing errors. - Use
errnoonly where the API documents it; check it immediately. - Design APIs with clear return codes and ownership rules.
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.
Related Concepts
c.mem.ownership— ownership and cleanup.c.core.16— goto cleanup.c.stdlib.2— perror/strerror with stdio.
References
- ISO/IEC 9899:2018 §7.5 (errno.h), §7.2 (assert.h).
Verification
errnosemantics and error codes are standard.VERIFIEDassertis disabled byNDEBUG.VERIFIED- No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] errno and error codes
- [ ] perror/strerror
- [ ] assert and NDEBUG
- [ ] Return-code patterns
- [ ] goto cleanup
- [ ] API error design
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.lib.errno | 0 | 6 |