Хотя детали различаются для разных платформ и форматов исполняемых файлов и соглашений о вызовах , программы обычно делятся на несколько сегментов . Вот общий макет программы для x86 (взятый из здесь ):
+------------------------+
high address | Command line arguments |
| and environment vars |
+------------------------+
| stack |
| - - - - - - - - - - - |
| | |
| V |
| |
| ^ |
| | |
| - - - - - - - - - - - |
| heap |
+------------------------+
| global and read- |
| only data |
+------------------------+
| program text |
low address | (machine code) |
+------------------------+
Сама функция сохраняется в текстовом сегменте программы, в то время как данные, над которыми работает функция, сохраняются в других сегментах. Обратите внимание, что это виртуальная структура памяти, а не физическая память.
В большинстве систем при вызове функции кадр стека выделяется из стека. Во фрейме стека будет место для аргументов функции и локальных переменных, а также адрес предыдущего фрейма стека и адрес следующей инструкции, которая будет выполнена после возврата из функции (опять же, конкретные c детали будут различаться в зависимости от соглашений о вызовах ):
+----------------+
high address: | argument N |
+----------------+
| argument N-1 |
+----------------+
...
+----------------+
| argument 1 |
+----------------+
| return addr |
+----------------+
| prv frame addr | <---- %ebp
+----------------+
| local 1 |
+----------------+
| local 2 |
+----------------+
...
+----------------+
low address: | local N | <---- %esp
+----------------+
Наряду с регистром указателя стека (%esp
на x86, %rsp
на x86_64) существует регистр base pointer (%ebp
на x86, %rsp
на x86_64). Этот регистр хранит адрес фрейма стека, а функция ссылается на локальные объекты и аргументы через смещения от этого адреса. Пример Quick-n-dirty:
int main( void )
{
int x = 1;
int y = 2;
printf( "foo(1,2) = %d\n", foo( x, y ) );
return 0;
}
Вот фрагмент скомпилированного кода, в котором мы присваиваем x
и y
(список, полученный с помощью objdump -d
в исполняемом файле):
55d: c7 45 f0 01 00 00 00 movl $0x1,-0x10(%ebp)
564: c7 45 f4 02 00 00 00 movl $0x2,-0xc(%ebp)
В этом коде мы записываем значение 1 в ячейку 16 байтов «ниже» адреса, хранящегося в %ebp
, а значение 2 в ячейку 12 байтов «ниже».