В соглашении о вызовах по умолчанию для C
вызывающая сторона освобождает аргумент функции после возврата из функции. Но сама функция управляет своими переменными в стеке. Например, вот ваш код в сборке без какой-либо оптимизации:
my_func:
push ebp // +
mov ebp, esp // These 2 lines prepare function stack
sub esp, 16 // reserve memory for local variables
mov DWORD PTR [ebp-4], 0
mov DWORD PTR [ebp-8], 0
mov edx, DWORD PTR [ebp-8]
mov eax, DWORD PTR [ebp-4]
add eax, edx // <--return value in eax
leave // return esp to what it was at start of function
ret // return to caller
main:
push ebp
mov ebp, esp
push 3
push 2
push 1
call my_func
add esp, 12 // <- return esp to what it was before pushing arguments
mov eax, 0
leave
ret
Как видите, в main
есть add esp, 12
для возврата esp
, как это было до нажатия аргументов. В my_func
есть такая пара:
push ebp
mov ebp, esp
sub esp, 16 // <--- size of stack
...
leave
ret
Этот набор пар используется для резервирования некоторой памяти в виде стека. leave
отменяет эффект push ebp/move ebp,esp
. И функция использовала ebp
для доступа к своим аргументам и выделенным в стеке переменным. Возвращаемое значение всегда в eax
.
Примечание о быстром выделении стека:
Как видите, в функции есть инструкция add esp, 16
, даже если в стеке хранится только 2 переменные типа int
, общий размер которых составляет 8 байт. Это связано с тем, что размер стека выравнивается по определенным границам (по крайней мере, с параметрами компиляции по умолчанию). Если вы добавите еще 2 int
переменных к my_func
, эта инструкция все равно будет add esp, 16
, потому что общий стек все еще находится в 16-байтовом выравнивании. Но если вы добавите третью переменную int
, эта инструкция станет add esp, 32
. Это выравнивание можно настроить с помощью опции -mpreferred-stack-boundary
в GCC
.
Кстати, все это для 32-битной компиляции кода. Напротив, вы обычно никогда не передаете аргумент через стек, передаваемый в 64-битном режиме, и передаете их через регистры. Как упомянуто в комментарии, в 64-битных аргументах только через стек передается 5-й аргумент (в соглашении о вызовах Microsoft x64 ).
Обновление:
С соглашение о вызовах по умолчанию , в среднем cdecl
, которое обычно используется при компиляции кода для x86 без каких-либо опций компилятора или определенных атрибутов функции. Если вы измените вызов функции на stdcall
в качестве примера, все это изменится.