Причина, по которой адрес j
никогда не меняется, заключается в том, что компилятор выделяет память для j
в стеке при вводе функции, а не когда j
входит в область действия.
Как всегда, взгляд на некоторый ассемблерный код может помочь объяснить концепцию. Возьмите следующую функцию: -
int foo(void)
{
int i=3;
i++;
{
int j=2;
i=j;
}
return i;
}
gcc преобразует это в следующий код сборки x86: -
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
Давайте пройдемся по этому ассемблерному коду. Первая строка сохраняет текущий базовый указатель стека, чтобы его можно было восстановить при выходе из функции, вторая строка устанавливает текущую вершину стека в качестве нового базового указателя стека для этой функции.
Третья строка - это та, которая выделяет память в стеке для всех локальных переменных. Инструкция subl $8, %esp
вычитает 8 из текущей вершины указателя стека, регистра esp
. Стеки растут в памяти, поэтому эта строка кода увеличивает объем памяти в стеке на 8 байт. В этой функции у нас есть два целых числа, i
и j
, каждому из которых требуется 4 байта, поэтому он выделяет 8 байтов.
Строка 4 инициализирует i
до 3 путем прямой записи по адресу в стеке. Строки 5 и 6 затем загружают и увеличивают i
. Строка 7 инициализирует j
, записывая значение 2 в память, выделенную для j
в стеке. Обратите внимание, что когда j
вошло в область видимости в строке 7, код сборки не изменил стек для выделения памяти для него, о чем уже позаботились ранее.
Я уверен, что это очевидно, но причина, по которой компилятор выделяет память для всех локальных переменных в начале функции, заключается в том, что это намного эффективнее. Регулировка стека каждый раз, когда локальная переменная входит или выходит из области видимости, приведет к большому количеству ненужных манипуляций с указателем стека без какой-либо выгоды.
Я уверен, что вы сами сможете понять, что делает остальная часть кода сборки, если не оставите комментарий, и я проведу вас через него.