ВПЕРЁД ⇒
⇐ НАЗАД
Локальные переменные
Код
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
retONLINE COMPILER
Локальная переменная на стеке
Что изменилось по сравнению с 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: если выброс кода не меняет наблюдаемое поведение программы - выбрасывать можно.
ВПЕРЁД ⇒
⇐ НАЗАД
Источники
- Здесь будет список источников
Категория
Теги
- Cpp Cpp
- Asm Asm
- Assembly Assembly
- Intel-asm-syntax Intel-asm-syntax
- x86-64 x86-64
- Стек Стек
- Stack Stack
- Стек-вызовов Стек-вызовов
- Call-stack Call-stack
- Стек-фрейм Стек-фрейм
- Stack-frame Stack-frame
- Указатель-фрейма Указатель-фрейма
- Frame-pointer Frame-pointer
- Указатель-стека Указатель-стека
- Stack-pointer Stack-pointer
- clang clang
- clang-fomit-frame-pointer clang-fomit-frame-pointer
- clang-O2 clang-O2