Exercises — Undefined Behavior Sanitizer (UBSan)
Before we start, one shared fact we will use again and again. Everything here assumes a 32-bit int. From Two's Complement Representation, the representable range is
We write for the top (most significant) bit of the machine word — it is when is negative, when is non-negative. Look at the figure below to see why one bit tells you the sign at all.

The bar shows a 32-bit signed integer. The leftmost cell (orange) is the sign bit with weight ; the other 31 cells (blue) all add positive weights. So if you flip only the top bit you swing by exactly — that jump is what "wraparound" means (see Integer Overflow & Wraparound).
Level 1 — Recognition
Recall Solution L1·Q1
- (a) UB. Signed overflow: true sum . Not representable ⇒ UB.
- (b) Defined. Unsigned overflow is not UB — it wraps modulo . Result is . (See the parent note's recall: signed = UB, unsigned = defined.)
- (c) UB.
1 << 31shifts a into the sign-bit position of a signed type. Since C++20 this is defined, but in C and pre-C++20 shifting into the sign bit is UB. UBSan flags it under theshiftcheck. - (d) Defined (implementation-defined).
-1 >> 1is a right shift of a negative value — implementation-defined, not UB. UBSan does not flag it.
Level 2 — Application
Recall Solution L2·Q2
True sum . . Since , the sum is not representable ⇒ overflow (UB). Same-sign check: both operands are non-negative (), and the wrapped result is negative (). Sign flipped despite equal input signs ⇒ overflow flag set ⇒ UBSan fires.
Recall Solution L2·Q3
The standard requires the shift amount to satisfy , i.e. . UBSan inserts, straight from that constraint:
if (k < 0 || k >= 32) __ubsan_handle_shift_out_of_bounds(...);So or both report. (On x86 the bare hardware would compute k & 31, silently giving x << 0 for k = 32 — a wrong answer with no crash, exactly why UBSan is needed.)
Level 3 — Analysis
Recall Solution L3·Q4
With and , the sum lies between the two operands: Since and , we get . The result is squeezed inside the valid range, so it always fits — no overflow possible. Geometrically (figure below), adding a negative moves you left from but never past ; adding a positive to moves right but never past . You can never escape the interval from inside it by a step that stays inside it.

Recall Solution L3·Q5
INT_MIN / -1. But , so is one past the top ⇒ not representable ⇒ UB (and on many CPUs a hardware divide trap).INT_MIN * -1— same value, same problem ⇒ UB.INT_MAX / -1, which is ⇒ representable ⇒ defined. The asymmetry is the whole story: two's complement has one more negative number than positive, so negating (or the equivalent divide/multiply) the single extreme negative has no positive twin.
Level 4 — Synthesis
Recall Solution L4·Q6
Line (1) dereferences p. Dereferencing NULL is UB, so the compiler is entitled to assume p is not NULL — because if it were, the program already had UB and the standard imposes no requirements. Under that assumption, the test p == NULL on line (2) is always false, so the optimizer may delete the entire if branch. Your safety check silently vanishes.
- UBSan (
-fsanitize=undefined) catches the null / invalid pointer dereference at line (1) with anull-pointer-usediagnostic. - ASan is the stronger tool for the general case (wild pointers, use-after-free, out-of-bounds), because it validates the actual memory address. For a plain
NULLderef both catch it; for a garbage-but-non-null pointer, ASan is the one that flags the bad load. Real fix: reorder — checkp == NULLbefore the dereference. Then the assumption never kicks in.
Recall Solution L4·Q7
clang -fsanitize=undefined,address -fno-sanitize-recover=all -g -O1 file.c -o prog-fsanitize=undefined→ UBSan (overflow, shifts).,address→ ASan for the heap-buffer-overflow (a memory error UBSan doesn't cover).-fno-sanitize-recover=all→ abort on the first violation, so the earliest (root-cause) error isn't buried by cascading later reports.-g→ embed debug info so reports showfile:line:colinstead of raw addresses.-O1(not-O0) → catches optimization-sensitive UB while keeping stack traces readable; both GCC and Clang support this combination.
Level 5 — Mastery
Recall Solution L8
No UB. In C, int8_t operands are promoted to int before +. So the addition is computed in int — no overflow (well within int range). The narrowing back to int8_t (range ): doesn't fit, so it is implementation-defined conversion (typically wraps to ), not UB. Default UBSan does not flag this; only the optional -fsanitize=implicit-conversion (implicit-integer-truncation) sublevel would warn. So the base -fsanitize=undefined build stays silent.
Recall Solution L5·Q9
- (a) UB. ⇒ underflow. Wrapped value: .
- (b) UB. , one past INT_MAX. Wrapped value: (negating INT_MIN gives INT_MIN back — the famous fixed point).
- (c) Defined. Unsigned: .
- (d) UB. . Wrapped: .
- (e) Defined (implementation-defined). Right-shifting a negative is implementation-defined, not UB. Arithmetic shift typically gives .
Recall Solution L5·Q10
Let (true math). We show: () (same sign in, flipped sign out).
- Opposite signs ( or vice versa): by L3·Q4 the sum stays in range, so never leaves range and the sign-bit can't flip past a limit (result lies between operands). Both rules say "no overflow." ✓
- Both non-negative (): . Overflow (range rule) the true sum needs bit , i.e. the wrapped machine result has (flipped). Same condition. ✓
- Both negative (): . Overflow the wrapped result's top bit becomes (flipped to non-negative). Same condition. ✓
Every case matches, so the two rules are equivalent — and the sign-bit version is what the hardware
OFflag computes in one step.
Wrap-up recall
Recall Which of these are UB? (cover the answers)
INT_MAX + 1::: UB (signed overflow)UINT_MAX + 1u::: defined (wraps to 0)INT_MIN / -1::: UB (result not representable)1 << 32::: UB (shift ≥ width)-1 >> 1::: defined (implementation-defined, not UB)- deref
pthen checkp == NULL::: the check may be deleted by the optimizer; deref of possibly-null is UB
Parent: Undefined Behavior Sanitizer (UBSan) · Hinglish: 5.3.13 Undefined Behavior Sanitizer (UBSan) (Hinglish)