The reason why the address j never changes is because the compiler allocates memory for j on the stack when the function is introduced, and not when j enters the scope.
As always, looking at the assembly code can help explain the concept. Take the following function: -
int foo(void) { int i=3; i++; { int j=2; i=j; } return i; }
gcc converts it to the following x86 build code: -
foo: pushl %ebp ; save stack base pointer movl %esp, %ebp ; set base pointer to old top of stack subl $8, %esp ; allocate memory for local variables movl $3, -4(%ebp) ; initialize i leal -4(%ebp), %eax ; move address of i into eax incl (%eax) ; increment i by 1 movl $2, -8(%ebp) ; initialize j movl -8(%ebp), %eax ; move j into accumulator movl %eax, -4(%ebp) ; set i to j movl -4(%ebp), %eax ; set the value of i as the function return value leave ; restore stack pointers ret ; return to caller
Skip this build code. The first line saves the current pointer to the base pointer so that it can be restored when the function ends, the second line sets the current top of the stack as the new base stack pointer for this function.
The third line is the one that allocates memory on the stack for all local variables. The subl $8, %esp instruction subtracts 8 from the current top of the stack pointer, esp register. Stacks grow in memory, so this line of code actually increases memory on the stack by 8 bytes. There are two integers in this function: i and j , each of which requires 4 bytes, so it allocates 8 bytes.
Line 4 initializes i to 3 by writing directly to the address on the stack. Lines 5 and 6 are then loaded and incremented i . Line 7 initializes j by writing the value 2 to the memory allocated for j on the stack. Note that when j fell into scope on line 7, the assembly code did not configure the stack to allocate memory for it that had already been taken care of.
I'm sure this is obvious, but the reason the compiler allocates memory for all local variables at the beginning of the function is because it is more efficient for this. Setting the stack every time a local variable is turned on or out of scope would lead to many unnecessary manipulations with the stack pointer without amplification.
I'm sure you can decide what the rest of the assembly code does if you don't leave a comment and I will guide you through it.