C Mastery / UB Catalog: The Important Cases
Part 2 — The Object Model and Undefined Behavior

UB Catalog: The Important Cases

This chapter catalogs the most important cases of undefined behavior in C. For each, it shows bad code, why it is UB, what the standard says, a safe version, a common misconception, and the compiler consequence.

Why This Matters

This is the reference chapter you return to when debugging "it works in debug but not release" bugs. Knowing the catalog — and the safe alternative for each case — is a core skill for systems and security work.

Prerequisites

How to Read the Catalog

Each entry uses this structure:


1. Out-of-bounds array access

BAD CODE:

int a[5];
int x = a[5];   /* one past the last valid index 4 */

WHY: The index is outside the array object.

WHAT THE STANDARD SAYS: Array subscripting outside the bounds of the array object is undefined behavior.

SAFE VERSION:

if (i >= 0 && i < 5) { int x = a[i]; }

COMMON MISCONCEPTION: "Reading past the end is fine as long as I don't write." No, reading is also UB.

COMPILER CONSEQUENCE: The compiler may assume i is always in bounds and remove your bounds check, or vectorize the loop past the array.


2. Use-after-free / dangling pointer

BAD CODE:

int *p = malloc(sizeof *p);
free(p);
*p = 5;   /* use after free */

WHY: The object's lifetime ended at free.

SAFE VERSION: Do not use p after free; set it to NULL if helpful, and manage ownership (c.mem.ownership).

COMMON MISCONCEPTION: "The value is still there, so it works." The allocator may have reused the memory.

COMPILER CONSEQUENCE: The optimizer may reorder the write to *p with the free or assume the pointer is not used, causing corruption.


3. Double free

BAD CODE:

free(p);
free(p);   /* double free */

SAFE VERSION: Track ownership; free exactly once. Set p = NULL after free to catch accidental double-free (though this does not fix all cases).

COMMON MISCONCEPTION: "Freeing NULL is fine, so freeing twice must also be fine." free(NULL) is defined; double-free is not.


4. Null pointer dereference

BAD CODE:

int *p = NULL;
int x = *p;

SAFE VERSION: Check if (p != NULL) before dereferencing.

COMPILER CONSEQUENCE: The compiler may assume p is non-null after a check and remove a later null check.


5. Signed integer overflow

BAD CODE:

int x = INT_MAX + 1;

SAFE VERSION: Use unsigned (wraps), or check before adding.

COMMON MISCONCEPTION: "It wraps around." Unsigned wraps; signed overflow is UB.


6. Invalid shift

BAD CODE:

int x = 1 << 32;   /* shift count >= width of int */

SAFE VERSION: Ensure 0 <= n < width; use unsigned types.


7. Division by zero (and INT_MIN / -1)

BAD CODE:

int x = a / 0;
int y = INT_MIN / -1;

SAFE VERSION: Check the divisor before dividing.


8. Uninitialized read (indeterminate value)

BAD CODE:

int x;
printf("%d\n", x);

SAFE VERSION: Initialize before use.


9. Invalid pointer arithmetic

BAD CODE:

int a[5];
int *p = a + 6;   /* beyond one-past-the-end */

SAFE VERSION: Stay within [a, a + 5].


10. Strict aliasing violation

BAD CODE:

float f = 1.0f;
int *p = (int *)&f;
int x = *p;

SAFE VERSION: Use memcpy to reinterpret representation.


11. Unsequenced side effects

BAD CODE:

int i = 0;
i = i++;

SAFE VERSION: Sequence the operations; never modify the same object twice without a sequence point.


12. Data race

BAD CODE:

/* two threads write the same non-atomic int */

SAFE VERSION: Use _Atomic, mutexes, or proper synchronization (c.conc.5).


13. Invalid format string

BAD CODE:

printf("%d", 3.14);   /* wrong specifier */

SAFE VERSION: Match format specifiers to argument types.


14. Invalid library usage

BAD CODE:

memcpy(dst, src, n);   /* overlapping buffers */

SAFE VERSION: Use memmove for overlapping buffers.


What You Should Now Know

happens.

References

cited in each entry.

Verification

by zero, aliasing violation, data race, unsequenced side effects, and format string mismatch are all UB. VERIFIED

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.ub.catalog07