$ clang -g -O0 12_signed_overflow.c -o bin/12_signed_overflow
$ ./bin/12_signed_overflow ; echo "exit=$?"
y = -2147483648
exit=0
^ No crash, no warning at runtime. The wrong answer is the only symptom.$ clang -g -O0 12_signed_overflow.c -o bin/12_signed_overflow
$ ./bin/12_signed_overflow ; echo "exit=$?"
y = -2147483648
exit=0
^ No crash, no warning at runtime. The wrong answer is the only symptom.gdb runs to a clean exit; `print y` would just show -2147483648. There is no signal and no invalid memory, so gdb has nothing to catch. Arithmetic UB is a job for a sanitizer, not a debugger.
$ clang -g -O0 -fsanitize=undefined 12_signed_overflow.c -o bin/12_signed_overflow.ubsan
$ ./bin/12_signed_overflow.ubsan ; echo "exit=$?"
12_signed_overflow.c:75:15: runtime error: signed integer overflow:
2147483647 + 1 cannot be represented in type 'int'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior 12_signed_overflow.c:75:15
y = -2147483648
exit=0
How to read it:
"signed integer overflow: 2147483647 + 1 cannot be represented in type
'int'" -> the exact operation and operands that overflowed, at line
75 column 15. UBSan instruments arithmetic and tells you in plain
English. By default it PRINTS and CONTINUES (so you still see y and
exit 0); add -fno-sanitize-recover=undefined to make it abort instead.
And the contrast that proves the point:$ valgrind -q ./bin/12_signed_overflow
y = -2147483648
(valgrind says NOTHING.)
META-LESSON: valgrind/memcheck only sees MEMORY errors. Signed overflow is
a pure arithmetic/UB issue with no bad memory access, so memcheck is blind
to it. Sanitizers and valgrind are COMPLEMENTARY, not interchangeable —
pick by the SYMPTOM (see the README cheat-sheet).
ROOT CAUSE vs CRASH SITE:
There is no crash. The "site" and the cause are the same expression, but
the deeper cause is trusting that `INT_MAX + 1` wraps. It is UB; treat it
as a logic error to be prevented (check before adding), not a wrap to
exploit.UB / arithmetic
2 ------------------------------------------------------------
`x = INT_MAX` then `x + argc`. Since argc >= 1, this is INT_MAX + 1, which overflows a signed int. Signed overflow is Undefined Behavior in C (unlike UNSIGNED, which wraps by definition). Buggy line: `int y = x + argc;`. (argc is used so the compiler can't constant-fold it away.)
SILENT. On this machine it "wraps" to INT_MIN and prints -2147483648, exit 0 — it LOOKS like it works. But it is UB: the compiler is entitled to assume signed overflow never happens and optimize accordingly, so at -O2 the same code can do something entirely different. Never rely on the wrap.
Check before the operation, or use a wider/unsigned type deliberately: if (x > INT_MAX - argc) { ...handle the overflow... } long y = (long)x + argc; // compute in a wider type
Rebuild at -O2 with `-fsanitize=undefined` and also try `-fno-sanitize-recover=undefined` so UBSan ABORTS (exit != 0) on the overflow — exactly what you want in CI to fail the build on UB. ============================================================
#include <limits.h>
#include <stdio.h>
int main(int argc, char **argv) {
int x = INT_MAX;
int y = x + argc; /* <-- THE BUG: INT_MAX + (argc>=1) overflows */
printf("y = %d\n", y);
(void)argv;
return 0;
}A lab is done when you can say what it showed on your machine — not when you have read it.
It decides which problems come first, which drills open, which labs you see and how the Learn surface is organised. Change it any time from the switch in the top bar, or press Escape to look around first.