Assertions (assert.h)
The <assert.h> header provides a single macro, assert(condition), used to catch programmer errors — bugs that should be logically impossible if the code is correct — as early and loudly as possible during development.
How assert() Works
If condition evaluates to false (zero), assert prints a diagnostic message (including the failing expression, file name, and line number) to stderr and calls abort(), immediately terminating the program. If condition is true, assert does nothing at all — no overhead beyond the check itself.
#include <stdio.h>
#include <assert.h>
double divide(double a, double b) {
assert(b != 0.0); /* the caller must never pass zero -- that's a bug, not a runtime error */
return a / b;
}
int main(void) {
printf("10 / 2 = %f\n", divide(10.0, 2.0));
/* This next call violates the function's contract and will
trigger the assertion, aborting the program with a diagnostic:
"Assertion failed: b != 0.0, file example.c, line 6" */
printf("10 / 0 = %f\n", divide(10.0, 0.0));
return 0;
}Assertions Are Compiled Out with NDEBUG
Defining the macro NDEBUG before including <assert.h> (or passing -DNDEBUG to the compiler) makes every assert(...) expand to nothing — the checks, and any side effects inside them, simply vanish from the compiled program. This is the standard way to strip assertion overhead from release builds while keeping the checks active during development and testing.
# Debug build: assertions are active gcc -g program.c -o program_debug # Release build: assertions are compiled away entirely gcc -O2 -DNDEBUG program.c -o program_release
`assert(list_remove(list, item) == 0);` — in a release build, `list_remove` never runs at all, silently changing your program's behavior.
Assertions vs Proper Error Handling
Assertions and error handling solve different problems and should not be used interchangeably. Assertions are for conditions that indicate a bug in the program itself — situations that should be logically impossible if all the code is correct. Proper error handling (return codes, errno, etc.) is for conditions that can legitimately happen at runtime even in a perfectly correct program — a missing file, a failed network connection, invalid user input.
Scenario | Use assert()? | Reasoning |
|---|---|---|
A function argument violates its documented precondition | Yes | Calling code has a bug |
A file the user asked to open does not exist | No | Expected, recoverable runtime condition |
An internal invariant should always hold (e.g. array index in bounds after your own math) | Yes | If it fails, the logic itself is wrong |
malloc() returns NULL | No | A real possible runtime failure — handle it, do not assert it away |
User typed invalid input on stdin | No | Expected user error, not a programmer bug |
A Practical Example
#include <stdio.h>
#include <assert.h>
/* Precondition: index must be within [0, size). This is a contract
the CALLER is responsible for upholding -- violating it is a bug. */
int getElement(const int *arr, int size, int index) {
assert(index >= 0 && index < size);
return arr[index];
}
int main(void) {
int data[5] = { 10, 20, 30, 40, 50 };
printf("%d\n", getElement(data, 5, 2)); /* fine */
/* Uncommenting the next line triggers the assertion because
index 10 is out of bounds -- a programmer error, caught early. */
/* printf("%d\n", getElement(data, 5, 10)); */
return 0;
}