Общая последовательность: вход и выход из функции
Большинство функций Windows и в пользовательских программах выполняют вход и выход в одной и той же манере. Для входа в функцию устанавливается пролог, а для выхода — эпилог (компилятор генерирует их автоматически). При установке пролога код получает доступ к локальным переменным и параметрам функции. Объекты доступа называются кадром стека (stack frame). Хотя CPU x86 явно не определяет никакой схемы стекового кадра, операционным системам легче всего использовать для хранения указателя на этот кадр регистр ЕВР (этому способствует конструкция CPU и формат некоторых его инструкций).
_asm
{
// Установка стандартного пролога.
PUSH EBP // Сохранить содержимое регистра стекового кадра.
MOV EBP , ESP // Установить в ЕВР адрес стекового кадра локальной
// функции.
SUB ESP , 20h // Отвести в стеке 0x20 байт для локальных
// переменных. Инструкция SUB появляется, только
// если функция имеет локальные переменные.
}
Эта последовательность является общей как для отладочных, так и для выпускных построений (финальных версий). Однако в некоторых функциях выпускных построений можно увидеть группу инструкций, помещенных между PUSH и MOV. CPU с множественными конвейерами (например, из семейства Pentium) могут расшифровывать сразу несколько инструкций одновременно, и чтобы воспользоваться этим преимуществом, оптимизатор попытается установить поток инструкций.
В зависимости от режима оптимизации, выбранного при компиляции кода, можно также иметь функции, которые не используют регистр ЕВР в качестве указателя кадра стека. Эти процедуры обладают тем, что называют FPO1-данными. В окне дизассемблера код такой функции выглядит так, как будто она только что начала манипулировать данными. Как можно идентифицировать одну из таких функций, будет показано ниже, в разделе "Доступ к параметрам, локальным и глобальным переменным" этой главы.
Следующий общий (для всех функций) эпилог отменяет действия пролога. Такой эпилог можно увидеть в большинстве в отладочных конструкций.
Этот эпилог соответствует указанному выше прологу.
_asm
{
// Стандартный демонтаж эпилога
MOV ESP , ЕВР // Восстановить стековое значение.
POP EBP // Восстановить сохраненное значение регистра -.
// стекового кадра.
}
В выпускных построениях инструкция LEAVE выполняется быстрее, чем последовательности MOV/POP, так что в их эпилогах можно найти только инструкцию LEAVE. Она идентична последовательности
MOV/POP. В отладочных построениях компиляторы по умолчанию используют последовательности MOV/POP. Интересно, что CPU х86 для установки пролога имеют соответствующую инструкцию — ENTER, но она медленнее, чем последовательность PUSH/MOV/ADD, так что компиляторы ее не применяют.
Выбор компиляторами способа генерации кода во многом зависит от того, как оптимизирована программа — по скорости или по размеру. Если установлена оптимизация по размеру, как было настоятельно рекомендовано в главе 2, большинство функций преимущественно используют стандартные стековые кадры. Оптимизация по скорости приводит к более сложной FPO-генерации.
FPO (Frame Pointer Omission — пропуск указателя кадра). — Пер.