There’s no shortage of material that explains variables in C should be initialized… but I’ve often found the explanation of why to be somewhat lacking.
Where do the initial values of a variable come from if you don’t explicitly initialize?
In C, using the value of an uninitialized local variable is undefined behaviour.
The demo below shows one common manifestation of that: the variable happens to reuse the same stack slot as a previous function, so it prints the old value that was left behind.
| |
When you compile and run the code above, both functions show the same value of the variable initialized in stack_var_a().
% clang -O0 -g -o initvars initvars.c
% ./initvars
The int is... 69
The int is... 69
Digging in with the debugger:
$ lldb ./initvars
(lldb) target create "./initvars"
Current executable set to 'initvars' (x86_64).
(lldb) b stack_var_a
Breakpoint 1: where = initvars`stack_var_a + 8 at initvars.c:5:6, address = 0x0000000000201658
(lldb) b stack_var_b
Breakpoint 2: where = initvars`stack_var_b + 8 at initvars.c:14:32, address = 0x0000000000201688
(lldb) run
Process 14277 launched: initvars' (x86_64)
Process 14277 stopped
* thread #1, name = 'initvars', stop reason = breakpoint 1.1
frame #0: 0x0000000000201658 initvars`stack_var_a at initvars.c:5:6
2
3 void stack_var_a (void)
4 {
-> 5 int my_int = 69;
6
7 printf ("The int is... %d\n", my_int);
8 }
(lldb) var -L
0x000000082104612c: (int) my_int = 8998
(lldb) step
Process 14277 stopped
* thread #1, name = 'initvars', stop reason = step in
frame #0: 0x000000000020165f initvars`stack_var_a at initvars.c:7:32
4 {
5 int my_int = 69;
6
-> 7 printf ("The int is... %d\n", my_int);
8 }
9
10 void stack_var_b (void)
(lldb) var -L
0x000000082104612c: (int) my_int = 69
(lldb) continue
Process 14277 resuming
The int is... 69
Process 14277 stopped
* thread #1, name = 'initvars', stop reason = breakpoint 2.1
frame #0: 0x0000000000201688 initvars`stack_var_b at initvars.c:14:32
11 {
12 int your_int;
13
-> 14 printf ("The int is... %d\n", your_int);
15 }
16
17 int main (int argc, char **argv)
(lldb) var -L
0x000000082104612c: (int) your_int = 69
(lldb)
This happens because the function prologue and epilogue for both
functions allocate and free the same amount of stack space. That
means the same stack slot (rbp - 0x4) is reused for each function’s
local int. Since the memory isn’t cleared, whatever stack_var_a
left behind becomes your_int in stack_var_b.
(lldb) disassemble -b
initvars`stack_var_b:
0x201680 <+0>: 55 push rbp
0x201681 <+1>: 48 89 e5 mov rbp, rsp
0x201684 <+4>: 48 83 ec 10 sub rsp, 0x10
-> 0x201688 <+8>: 8b 75 fc mov esi, dword ptr [rbp - 0x4]
0x20168b <+11>: 48 bf 91 04 20 00 00 00 00 00 movabs rdi, 0x200491
0x201695 <+21>: b0 00 mov al, 0x0
0x201697 <+23>: e8 d4 00 00 00 call 0x201770 ; symbol stub for: printf
0x20169c <+28>: 48 83 c4 10 add rsp, 0x10
0x2016a0 <+32>: 5d pop rbp
0x2016a1 <+33>: c3 ret
(lldb) register read rsp
rsp = 0x0000000821046120
Allocating and deallocating stack memory is done by incrementing
and decrementing the rsp register. Values in memory are not cleared
by these operations so whatever was left in memory remains when the
next function re-uses the same memory space.