ВПЕРЁД

НАЗАД



Локальные переменные

Код

C++

int main() {
	int var{1234};
	return 0;
}

ASM (x86-64 clang)

main:
	push rbp
	mov rbp, rsp
	mov dword ptr [rbp - 4], 0
	mov dword ptr [rbp - 8], 1234
	xor eax, eax
	pop rbp
	ret

ONLINE COMPILER

Godbolt

Локальная переменная на стеке

Что изменилось по сравнению с return 0?

Появилась одна новая инструкция mov dword ptr [rbp - 8], 1234. Теперь на стеке живут две ячейки вместо одной.

Разбор инструкций

mov dword ptr [rbp - 4], 0

Это явный return 0 - точно такая же инструкция, как в предыдущем примере. Адрес не изменился - [rbp - 4] всё так же первый слот стек-фрейма.

mov dword ptr [rbp - 8], 1234

Это инициализация int var{1234}. Компилятор выделил следующие 4 байта по адресу rbp - 8 и записал туда значение 1234 (0x000004D2 в hex).

Обратите внимание: никакого отдельного определения переменной var не существует на уровне машинного кода - переменная это просто именованный адрес на стеке, а её создание это просто запись значения по этому адресу.

Содержимое стека

Верхние адреса памяти
---------------------------------------------------------       
                            < ret addr   >
    rsp → (новый rbp    ) → < старый rbp >
          (новый rbp - 4) → < 0x00000000 > ← return value
          (новый rbp - 8) → < 0x000004D2 > ← var (1234)
---------------------------------------------------------
Нижние адреса памяти

Порядок выполнения инструкций

Обратите внимание: сначала записывается return 0 в [rbp - 4], и лишь потом var в [rbp - 8]. Это может показаться странным - переменная определена раньше return. Причина в том, что на уровне -O0 (без оптимизации) компилятор (clang в данном случае) распределяет слоты стека в определённом внутреннем порядке, не обязанном совпадать с порядком строк исходного кода. Cтандарт C++ не регламентирует порядок размещения переменных на стеке.

Почему xor eax, eax снова вместо mov eax, [rbp - 4]?

По той же причине, что и раньше: компилятор знает, что в [rbp - 4] лежит 0, поэтому незачем читать из памяти - проще и быстрее занулить регистр напрямую.

Что будет при -O2?

main:
    xor eax, eax
    ret

Обе записи на стек исчезают. var никогда не читается - переменная “мертва” с точки зрения анализа потока данных, поэтому компилятор удаляет её целиком. return 0 схлопывается в xor eax, eax как обычно. Правило as-if: если выброс кода не меняет наблюдаемое поведение программы - выбрасывать можно.

Godbolt



ВПЕРЁД

НАЗАД