Skip to main content
๐Ÿ“œ WAYPOINT LESSON

Defensive C

โญโญ intermediateโณ 16 min read๐Ÿ“ Lesson 139 of 225

The reflexes that keep code on the defined side: pre-checks, bounded APIs, initialization, and the sanitize-the-input posture.

Pre-check instead of post-detect

Every UB trap has a defined-side pre-check:

/* overflow */   if (a > 0 && b > INT_MAX - a) ...
/* shift */      if (k < 32) x = (unsigned)x << k;
/* bounds */     if (i < n) use(a[i]);
/* null */       if (p && p->next) ...

The check and the use must be made of different operations โ€” checking a + b < 0 before computing a + b is checking with the UB itself.

Bounded by default

Every buffer-carrying call names its capacity. snprintf over sprintf; memcpy(dst, src, n) where n is computed from the destination's capacity, not the source's promise. The bounded call is never slower by an amount that matters and never the bug's origin.

Initialize at the declaration

int x = 0;                     /* not "int x;" */
TNode *n = calloc(1, sizeof *n);   /* zeroed: fields have defined values */
char buf[64] = {0};

calloc for structs whose fields must start defined; explicit initializers for stack arrays. The cost is unmeasurable; the UB class it deletes is real.

The honest test posture

Because UB cannot be relied on to fail (it may work on your machine today), tests assert the defined replacement's behavior: sat_add returns INT_MAX at the boundary, parse rejects the overflow input, the shift helper clamps. The W-solution pattern in this course is exactly that: Ws are defined but wrong โ€” they misparse, drop, or corrupt in ways tests deterministically catch. Graded code never depends on UB failing; it depends on correct behavior being verifiable.

โšก Now practice

Ready to Code
Defensive GymBounded APIs that refuse to trust their callers.
1 challenge ยท ยท ~26 min