$ clang -g -O0 09_uninit_value.c -o bin/09_uninit_value
$ ./bin/09_uninit_value ; echo "exit=$?"
exit=0
^ Here x happened to be 0 (zeroed stack page): nothing printed, exit 0.
On a dirtied stack it may print "x is big" and exit 1. Silent UB.$ clang -g -O0 09_uninit_value.c -o bin/09_uninit_value
$ ./bin/09_uninit_value ; echo "exit=$?"
exit=0
^ Here x happened to be 0 (zeroed stack page): nothing printed, exit 0.
On a dirtied stack it may print "x is big" and exit 1. Silent UB.gdb runs it to a clean exit — there is no signal to catch, and `print x` just shows you *a* value (gdb won't tell you it's uninitialized). For "is this value defined?", the tool is valgrind/memcheck (or clang MSan).
$ valgrind -q --track-origins=yes ./bin/09_uninit_value
==61846== Conditional jump or move depends on uninitialised value(s)
==61846== at 0x400483: main (09_uninit_value.c:68)
==61846== Uninitialised value was created by a stack allocation
==61846== at 0x400470: main (09_uninit_value.c:66)
==61846== Syscall param exit_group(status) contains uninitialised byte(s)
==61846== at 0x4934048: _Exit (_exit.c:30)
...
How to read it:
"Conditional jump ... depends on uninitialised value(s)" -> the `if`
branched on a value memcheck never saw written. The address (line
68) is WHERE it was used.
"Uninitialised value was created by a stack allocation ... (line 66)"
-> --track-origins traced it to x's stack slot. memcheck pins stack
allocations to the function entry (line 66, where x's frame is set
up) rather than the exact `int x;` line. Without --track-origins you
only learn THAT it's tainted, not where it was born.
"Syscall param exit_group(status) contains uninitialised byte(s)" -> the
`return x & 1` fed undefined bits to the exit syscall: a second,
independent symptom of the same root cause.ROOT CAUSE vs CRASH SITE: No crash. The USE sites are line 68 (the branch) and the return; the ROOT CAUSE is that `int x;` is never initialized (its frame slot, which memcheck reports at line 66, holds garbage). --track-origins gives you that backward link — birthplace, not just use.
UB / uninitialized read
2 ------------------------------------------------------------
`x` is declared but never assigned, then used in `if (x>42)` and `return x & 1`. Reading an uninitialized automatic variable is UB. Buggy line: `if (x > 42)` (and `return x&1`).
NONDETERMINISTIC and usually SILENT. `x` is whatever garbage is on the stack, so whether "x is big" prints — and the exit code — can change run to run. No crash. (Compare bug #02, which was an uninitialized POINTER; this is the scalar case.)
Initialize at declaration: `int x = 0;` (or to a meaningful value).
Build with `-fsanitize=memory` (clang MemorySanitizer) if your toolchain ships it: MSan is purpose-built for uninitialized reads and is far faster than valgrind. Also try `clang -O0 -Wall -Wuninitialized` — sometimes the compiler warns at compile time (depends on how the value flows). ============================================================
#include <stdio.h>
int main(void) {
int x; /* ROOT CAUSE: never initialized */
if (x > 42) /* <-- THE BUG: branch on uninitialized x */
printf("x is big\n");
return x & 1; /* also feeds undefined bits to exit() */
}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.