Какой эффект эти две инструкции вызывают в ассемблерном коде, сгенерированном gcc для x86 машин:
push %ebp
movl %esp, %ebp
push %ebp
При этом 32-битный (расширенный) регистр базового указателя помещается в стек, т.е. указатель стека (% esp) вычитается на четыре, затем значение % ebp копируется в место, на которое указывает указатель стека.
movl %esp, %ebp
Это копирует регистр указателя стека в регистр указателя базы.
Целью копирования указателя стека в базовый указатель является создание кадра стека, то есть области в стеке, где подпрограмма может хранить локальные данные. Код подпрограммы будет использовать базовый указатель для ссылки на данные.
Объяснение размотки является буквальной истиной (несмотря на одну небольшую ошибку направления), но не объясняет почему.
% ebp
- это «базовый указатель» для вашего фрейма стека. Это указатель, используемый средой выполнения C для доступа к локальным переменным и параметрам в стеке. Вот типичный код пролога функции, сгенерированный GCC (точнее, g ++). Сначала исходный код C ++.
// junk.c++
int addtwo(int a)
{
int x = 2;
return a + x;
}
Это генерирует следующий ассемблер.
.file "junk.c++"
.text
.globl _Z6addtwoi
.type _Z6addtwoi, @function
_Z6addtwoi:
.LFB2:
pushl %ebp
.LCFI0:
movl %esp, %ebp
.LCFI1:
subl $16, %esp
.LCFI2:
movl $2, -4(%ebp)
movl -4(%ebp), %edx
movl 8(%ebp), %eax
addl %edx, %eax
leave
ret
.LFE2:
.size _Z6addtwoi, .-_Z6addtwoi
.ident "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3"
.section .note.GNU-stack,"",@progbits
Теперь, чтобы объяснить этот код пролога (все, что было до .LCFI2:
), сначала:
pushl% ebp
хранит стековый фрейм вызывающей функции . в стеке. movl% esp,% ebp
принимает текущий указатель стека и использует его в качестве кадра для , вызываемой функции . subl $ 16,% esp
оставляет место для локальных переменных. Теперь ваша функция готова к работе. Любые ссылки с отрицательным смещением из регистра % ebp%
являются вашими локальными переменными ( x
в этом примере). Любые ссылки с положительным смещением из регистра % ebp%
являются вашими параметрами, передаваемыми в него.
Последний интересный момент - это инструкция leave
, которая является инструкцией ассемблера x86, которая выполняет работа по восстановлению кадра стека вызывающей функции. Обычно это оптимизируется до более быстрой последовательности move% ebp% esp
и pop% ebp%
в коде C. Однако в иллюстративных целях я вообще не компилировал никаких оптимизаций.
Это часть того, что известно как пролог функции .
Он сохраняет текущий базовый указатель, который будет извлечен при окончании функции, и устанавливает новый ebp в начало нового кадра.
Это типичный код, который вы видите в начале функции.
Он сохраняет содержимое регистра EBP в стеке, а затем сохраняет содержимое текущего указателя стека в EBP.
Стек используется во время вызова функции для хранения локальных аргументов. Но в функции указатель стека может измениться, потому что значения хранятся в стеке.
Если вы сохраняете исходное значение стека, вы можете обращаться к сохраненным аргументам через регистр EBP, в то время как вы все еще можете использовать (добавлять значения) стек.
В конце функции вы, вероятно, увидите команду
pop %ebp ; restore original value
ret ; return