Skip to main content
๐Ÿ“œ WAYPOINT LESSON

Invariants: Assertions That Catch Bugs Early

โญโญโญ advancedโณ 16 min read๐Ÿ“ Lesson 196 of 225

The proven debugging method when no debugger exists: encode what must be true, and let the violation point at the culprit.

Debugging without symbols

Professional C work happens in environments where you cannot step through code: production, CI, embedded targets, sandboxes. The durable method is invariant-driven debugging: for each data structure and loop, write down the property that must always hold, then check it aggressively in debug builds.

static void ds_invariant(const int *heap, size_t n) {
    for (size_t i = 1; i < n; i++) {
        size_t parent = (i - 1) / 2;
        if (heap[parent] < heap[i]) {
            fprintf(stderr, "heap invariant broken at %zu\nn", i);
            abort();
        }
    }
}

This is what -DNDEBUG toggles: with NDEBUG the compiler drops assert(); your own _invariant functions can stay compiled-in for tests and compiled-out for production.

The forensic sequence

  1. Reproduce โ€” a failing input you can paste, not a story.
  2. Localize โ€” bisect: which half of the program corrupts the state?
  3. Hypothesize โ€” name the invariant you believe is broken.
  4. Prove โ€” an assertion that fires earlier than the crash.
  5. Fix the cause, not the symptom โ€” then keep the assertion.

Crashes are late. Invariants are early. A corruption found at free() was caused lines earlier; the assertion lattice shrinks that distance to near zero.

โšก Now practice

Ready to Code
Forensics DrillsInvariant lattices, binary-search diagnosis, and repro discipline โ€” as testable functions.
4 challenges ยท ยท ~20 min