an assertion, invariants, and the contract idea
/ assert: "uh-SERT" /
Imagine a bridge with a sign: 'maximum load 10 tons'. That sign is a promise in two directions - the bridge promises to hold up to 10 tons, and you promise not to drive a heavier truck across. An assertion is the code version of putting up such a sign and a guard who stops everything the instant the rule is broken. assert(x) says 'I claim x is true right here; if it is not, the program has a bug, so stop immediately and loudly rather than limp onward'.
Here is the precise machinery. An invariant is a condition that should always hold at a certain point - for example, 'this pointer is never NULL here' or 'this list's length always equals its stored count'. A precondition is what a function requires of its caller before it will work correctly (e.g. 'the array passed in must be non-NULL and have at least n elements'). A postcondition is what the function promises to deliver in return (e.g. 'the returned list is sorted'). Together these form a contract: if the caller meets the preconditions, the function guarantees the postconditions. assert(condition) checks such a claim at runtime; if the condition is false, the standard library prints the failing expression, the file, and the line, then calls abort() to terminate the program. Crucially, assertions are compiled out when the macro NDEBUG is defined (typically in release builds), so they are a debugging-time check, not a runtime cost in production - which means you must never put code with side effects inside an assert.
Why it matters and the central misconception: assertions are for catching bugs - violations of things you believe are always true - not for handling expected errors. Whether a file exists, whether the user typed a number, whether the network is up: these are recoverable errors from the outside world and must be handled with normal error-checking code, because they can legitimately happen and your program should cope. A failed assertion means the program's own logic is broken: something you proved to yourself can never happen just did, so continuing would only corrupt more state. The honest caveat is that because assertions vanish under NDEBUG, never rely on an assertion to validate untrusted input or enforce security - that check must be real, always-on code.
Right: int *p = malloc(n * sizeof *p); assert(n > 0); /* my own code should never call this with n <= 0 - a bug if it does */. Wrong: assert(fopen(path, "r") != NULL); - a missing file is an expected error, not a bug, AND under NDEBUG the fopen() call vanishes entirely, so the file is never opened.
Assert your own invariants; handle the outside world's failures with real error code.
Assertions are compiled out when NDEBUG is defined, so (a) never put side effects inside assert(), and (b) never use them to validate untrusted input or enforce security - those checks must always run. A failed assertion signals a bug, not a recoverable error.