WHY does UB even exist? So compilers can generate fast code without inserting defensive checks everywhere. The standard says "if the programmer writes X, we assume they never trigger the bad case, so we're free to optimize." That freedom is great for speed — and a nightmare for correctness when you do trigger it.
clang -fsanitize=undefined -fno-sanitize-recover=all -g file.c -o prog./prog# On the first UB: prints e.g.# file.c:12:9: runtime error: signed integer overflow:# 2147483647 + 1 cannot be represented in type 'int'
-g → keep debug info so it prints line numbers.
-fno-sanitize-recover=all → abort on first error (otherwise UBSan reports and continues, which can hide the "first" cause).
Often combined: -fsanitize=undefined,address for UB + memory errors.
Take signed integer overflow for a 32-bit int. Representable range is
[−231,231−1].
WHY these bounds? Two's complement with n=32 bits: the most significant bit has weight −2n−1, the rest add positive weights up to 2n−1−1. So:
INT_MIN=−231,INT_MAX=231−1.
Deriving the addition-overflow condition. Compute the true mathematical sum s=a+b (in unbounded integers). Overflow = "s is not representable":
overflow(a,b)⟺s<−231∨s>231−1.
Why this step? UBSan can't compute in unbounded integers cheaply, so it uses a hardware fact: adding two same-sign numbers can only overflow toward that same sign. Formally, overflow occurs iff a and b have the same sign and the result's sign differs:
Why add -fno-sanitize-recover=all? ⇒ abort on first error so you see the true root cause
Why add -g? ⇒ so reports show file:line
Is signed overflow UB? Is unsigned overflow UB? ⇒ signed = UB; unsigned = well-defined (wraps mod 232)
How can n << 32 "work" yet be UB? ⇒ x86 masks the shift to & 31, giving a wrong-but-non-crashing result
Recall Feynman: explain to a 12-year-old
Imagine the language has some "forbidden moves" — like counting past the biggest number your calculator can show. If you make a forbidden move, the rulebook says "anything can happen" and the machine might give a weird answer, or crash, or seem fine and betray you later. UBSan is like a referee who watches the game and blows a whistle the instant you make a forbidden move, pointing at the exact line: "Right here! You tried to add past the maximum!" So instead of a spooky bug that shows up randomly next month, you get caught immediately.
Dekho, C/C++ mein kuch operations "Undefined Behavior" (UB) hote hain — matlab standard kehta hai "iska koi rule nahi, agar tum ye karoge toh kuch bhi ho sakta hai." Jaise signed integer ko INT_MAX se aage badhana, array ke bahar access karna, ya null pointer ko dereference karna. Problem ye hai ki UB kabhi-kabhi "chal jaata hai" — program sahi answer de deta hai, aur tumhe lagta hai sab theek hai. Par jaise hi optimizer badalta hai ya doosra CPU aata hai, wahi code galat answer de sakta hai ya crash kar sakta hai. Ye silent bugs sabse khatarnaak hote hain.
UBSan (Undefined Behavior Sanitizer) ek referee ki tarah hai. Tum -fsanitize=undefined -g flag ke saath compile karte ho, aur compiler har risky operation ke aas-paas chhota sa check daal deta hai. Jaise hi UB hota hai, wo turant bata deta hai: "file.c line 12: signed integer overflow, 2147483647 + 1 fit nahi ho raha int mein." Ab spooky bug ek clear error message ban gaya, exact line ke saath. -fno-sanitize-recover=all add karo taaki pehli galti pe hi ruk jaaye — warna wo report karke aage badh jaata hai aur asli root cause chhup jaata hai.
Intuition ye samjho: 32-bit int ka range hai −231 se 231−1. Two's complement mein overflow tabhi hota hai jab dono numbers ka sign same ho aur result ka sign flip ho jaaye — yehi CPU ka overflow flag ek hi instruction mein bata deta hai, isliye UBSan sasta hai (sirf 1.2x–2x slow). Yaad rakhna: unsigned overflow UB nahi hai (wo mod 232 wrap karta hai), sirf signed overflow UB hai. Aur ek zaroori baat — UBSan clean matlab bug-free nahi; ye sirf jo code paths tumne run kiye unhe check karta hai. Isliye ASan/MSan/TSan ke saath combine karo aur tests ki coverage badhao.