ВПЕРЁД ⇒
⇐ НАЗАД
Пустая функция main()
Код
C++
int main() {}ASM (x86-64 clang)
main:
push rbp
mov rbp, rsp
xor eax, eax
pop rbp
retONLINE COMPILER
Что происходит при запуске main()?
Общий контекст
В сгенерированном ассемблерном коде представлены стандартные пролог (заголовок) и эпилог (концевик) функции для x86-64 (System V AMD64 ABI - соглашение о вызовах в Linux/macOS). Компилятор без включённых опций оптимизации всегда генерирует пролог и эпилог, даже для пустой функции.
_start → call main
| → push rbp (сохранить rbp _start; начало пролога)
пролог main | → mov rbp, rsp (новый фрейм main)
эпилог main | → xor eax, eax (return 0; можно считать началом эпилога)
| → pop rbp (восстановить rbp _start)
| → ret (вернуться в _start)
_start → exit()
Разбор инструкций
push rbp
Сохраняет старое значение базового указателя стека (rbp) на стек. Это нужно потому, что rbp принадлежит вызывающей функции (в данном случае - стартовому коду _start из libc). По соглашению ABI, функция обязана восстановить rbp вызывающей функции перед возвратом.
Вариант №1 (Стек до push rbp):
Верхние адреса памяти
--------------------------------------------------
rsp → < адресс возврата из подпрограммы main >
--------------------------------------------------
Нижние адреса памяти
##################################################
Вариант №2 (Стек после push rbp):
Верхние адреса памяти
--------------------------------------------------
< адресс возврата из подпрограммы main >
rsp → < старый rbp >
--------------------------------------------------
Нижние адреса памяти
mov rbp, rsp
Устанавливает rbp равным текущему значению rsp (указателя стека, stack pointer, SP). Теперь rbp - это указатель фрейма (frame pointer, FP): фиксированная точка отсчёта для доступа к локальным переменным и аргументам через [rbp - N] и [rbp + N]. SP во время выполнения инструкций функции всё время будет инкрементироваться, а FP будет оставаться неизменным и указывать на стек-фрейм, в котором хранятся локальные переменные текущей выполняемой функции. В пустой функции FP никак не используется, но компилятор создаёт стек-фрейм всегда (если не передан флаг -fomit-frame-pointer).
xor eax, eax
Обнуляет регистр eax - это возвращаемое значение функции. По ABI, целочисленный результат передаётся через rax. Инструкция xor eax, eax предпочтительнее mov eax, 0 потому что:
- занимает меньше байт (2 байта против 5),
- не требует константы из памяти,
- процессор может выполнить её с нулевой задержкой.
Важное замечание
Обнуление
eaxавтоматически обнуляет и старшие 32 битаrax- это гарантия x86-64.
Именно эта инструкция соответствует return 0. Хотя в C/C++ стандарт позволяет опустить return в main, компилятор всё равно генерирует возврат нуля.
pop rbp
Восстанавливает сохранённый ранее rbp вызывающей функции. Стек возвращается в то состояние, в котором был до push rbp.
ret
Снимает с вершины стека адрес возврата (который туда положила вызывающая сторона командой call main) и передаёт туда управление. В данном случае это код инициализации libc (__libc_start_main), который после этого вызовет exit().
Схема работы стека
Вызов: call main → кладёт "ret addr" на стек
push rbp → кладёт старый rbp
mov rbp, rsp → фиксирует начало фрейма
...тело...
pop rbp → восстанавливает старый rbp
ret → снимает адресс "ret addr" и переходит по нему
Почему не просто xor eax, eax + ret?
Указатель фрейма (FP) нужен для отладки. Без него отладчик (gdb, lldb) и инструменты профилирования не смогут восстановить стек вызовов (call stack / backtrace). С флагом -O2 или -fomit-frame-pointer компилятор действительно убирает push rbp / mov rbp, rsp / pop rbp, оставляя только:
main:
xor eax, eax
retВПЕРЁД ⇒
⇐ НАЗАД
Источники
- Здесь будет список источников
Категория
Теги
- 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
- Эпилог Эпилог
- Пролог Пролог