Использование стека для 64-разрядных систем

Весь объем памяти за пределами текущего адреса RSP считается переменным: ОС или отладчик может перезаписать эту память во время сеанса отладки пользователя или обработчик прерываний. Таким образом, RSP необходимо всегда задавать перед попыткой чтения или записи значений в кадр стека.

В этом разделе рассматривается выделение пространства стека для локальных переменных и использование встроенной функции alloca.

Выделение памяти в стеке

Пролог функции отвечает за выделение пространства стека для локальных переменных, сохраненных регистров, параметров стека и параметров регистра.

Область параметров всегда находится в нижней части стека (даже при использовании alloca), поэтому при любом вызове функции она всегда будет находиться рядом с адресом возврата. Она содержит как минимум четыре записи, но в ней всегда достаточно места для хранения всех параметров, необходимых любой вызываемой функции. Обратите внимание, что пространство всегда выделяется для параметров регистра, даже если сами параметры никогда не попадают в стек. Вызываемому объекту гарантируется, что пространство было выделено для всех его параметров. Начальные адреса необходимы для аргументов регистра, поэтому смежная область должна быть доступна в случае, если вызываемой функции требуется адрес списка аргументов (va_list) или отдельного аргумента. В эту область также удобно сохранять аргументы регистра во время выполнения преобразователя. Кроме того, она используется при отладке (например, позволяет упростить поиск аргументов во время отладки, если они хранятся по своим начальным адресам в коде пролога). Даже если вызываемая функция имеет менее 4 параметров, эти 4 расположения в стеке фактически принадлежат вызываемой функции и могут использоваться ею для других целей (помимо сохранения значений параметров регистра). Таким образом, вызывающий объект не может сохранять информацию в эту область стека в рамках вызова функции.

Если в функции пространство выделяется динамически (alloca), то в качестве указателя кадра для пометки основы фиксированной части стека должен использоваться неизменяемый регистр, и этот регистр должен быть сохранен и инициализирован в прологе. Обратите внимание, что при использовании alloca вызовы одного и того же вызываемого объекта из одного и того же вызывающего объекта могут иметь разные начальные адреса для параметров регистра.

Стек всегда будет выравниваться по 16-байтной границе, но только не в прологе (например, после отправки возвращаемого адреса) и за исключением случаев, указанных в разделе Типы функций для определенного класса функций кадра.

Ниже приведен пример компоновки стека, где функция А вызывает неконечную функцию Б. Пролог функции А уже выделил место для всех параметров регистра и стека, необходимых функции Б, в нижней части стека. Вызов отправляет адрес возврата, а пролог Б выделяет пространство для локальных переменных, неизменяемых регистров, а также пространство, необходимое для вызова функций. Если Б использует alloca, пространство выделяется между областью сохранения локальной переменной и неизменяемого регистра и областью стека параметров.

Diagram of the stack layout for the x64 conversion example.

Когда функция Б вызывает другую функцию, адрес возврата отправляется сразу под начальным адресом RCX.

Динамическое создание области стека параметров

Если используется указатель кадра, можно динамически создать область стека параметров. Сейчас эта возможность отсутствует в компиляторе x64.

Типы функций

Существует два типа функций. Функция, которой требуется кадр стека, называется функцией кадра. Функция, которой не требуется кадр стека, называется конечной функцией.

Функция кадра — это функция, которая выделяет пространство в стеке, вызывает другие функции, сохраняет неизменяемые регистры или использует обработку исключений. Для нее также требуется запись в таблице функций. Для функции кадра требуются пролог и эпилог. Функция кадра может динамически выделять пространство стека и использовать указатель кадра. Функция кадра обладает всеми возможностями этого вызывающего стандарта.

Если функция кадра не вызывает другую функцию, выделение памяти в стеке не требуется (см. раздел Выделение памяти в стеке).

Для конечной функции запись в таблице функций не нужна. Она не может вносить изменения в неизменяемые регистры, включая RSP. Это значит, что она не может вызвать функции или выделять пространство в стеке. Во время выполнения стек можно оставить без выравнивания.

Выравнивание с помощью функции malloc

Функция malloc гарантированно возвращает память, которая соответствующим образом выровнена для хранения любого объекта с фундаментальным выравниванием и размером, подходящим для выделенного объема памяти. Фундаментальное выравнивание — это выравнивание, которое меньше или равно значению наибольшего выравнивания, поддерживаемого реализацией без спецификации выравнивания. (В Visual C++это выравнивание, необходимое для double8 байтов. В коде, предназначенном для 64-разрядных платформ, это 16 байт.) Например, выделение четырехбайтов будет выровнено по границе, поддерживающей любой четырехбайтовый или меньший объект.

В Visual C++ допускаются типы с расширенным выравниванием, которые также называются типами с избыточным выравниванием. Например, типы SSE __m128 и __m256, а также типы, объявленные с помощью __declspec(align( n )), где n больше 8, имеют расширенное выравнивание. malloc не гарантирует выравнивание памяти по границе, подходящей для объекта, требующего расширенного выравнивания. Чтобы выделить память для типов с избыточным выравниванием, следует использовать функцию _aligned_malloc и другие связанные функции.

alloca

Функция _alloca должна быть выровнена по 16-байтовой границе и дополнительно использовать указатель кадра.

Выделяемый стек должен содержать место для параметров впоследствии вызываемых функций, как описано в разделе Выделение памяти в стеке.

См. также

Программные соглашения для 64-разрядных систем
align
__declspec