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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

void stack_var_a (void)
{
	int my_int = 69;

	printf ("The int is... %d\n", my_int);
}

void stack_var_b (void)
{
	int your_int;

	printf ("The int is... %d\n", your_int);
}

int main (int argc, char **argv)
{
	stack_var_a();
	stack_var_b();

	return (0);
}

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.