Кодирование режима адресации с использованием [ebp + disp8]
на один байт короче [esp+disp8]
, поскольку для использования ESP в качестве базового регистра требуется байт SIB.Подробнее см. rbp в качестве базы SIB? .(Заголовок этого вопроса спрашивает о том факте, что [ebp]
должен быть закодирован как [ebp+0]
.)
В первый раз [esp + disp8]
используется после нажатия или нажатия или после call
,потребует стековой синхронизации на процессорах Intel.( Что такое механизм стека в микроархитектуре Sandybridge? ).Конечно, mov ebp, esp
для создания кадра стека в первую очередь также запускает стек-синхронизацию: любая явная ссылка на ESP в ядре не по порядку (не только в режимах адресации) вызывает стек-синхронизацию, еслиМеханизм стека может иметь смещение, о котором не знает неработающий сервер.
Традиционная установка фрейма стека с помощью ebp
создает связанный список стековых фреймов (каждыйсохраненный EBP, указывающий на сохраненный EBP родителя (прямо под адресом возврата), удобный для профилирования и иногда отладки, если ваш код не имеет альтернативных метаданных, которые позволяют вашему отладчику раскручивать стек, чтобы показать обратные следы стека.
Но несмотря на эти недостатки использования ESP, часто не лучше (для производительности) использовать EBP в качестве указателя кадра, поскольку он использует дополнительный один из 8 регистров GP для стекаоставляя вам 6 вместо 7, вы можете использовать его для других вещей, кроме стека. Современные компиляторы по умолчанию имеют значение -fomit-frame-pointer
, когда оптимизация выполняется
Компиляторам легко отслеживать, сколько ESP сместилось относительно того, где они что-то хранили, потому что они знают, насколько sub esp,28
перемещает указатель стека.Даже после push
аргумента функции они все еще знают правильное смещение ESP относительно всего, что они сохранили в стеке ранее в функции.
Люди тоже могут это делать, но легко сделатьошибка, когда вы модифицируете функцию, чтобы зарезервировать дополнительное пространство и забыть обновить все смещения от ESP до ваших локальных и стековых аргументов, если таковые имеются.(Однако обычно не стоит писать большие функции, которые не могут хранить большинство своих переменных в регистрах. Оставьте это компилятору и тратите свое время только на написание горячих циклов в asm, если вообще).
Исключение составляют случаи, когда ваша функция выделяет переменное количество стекового пространства (например, массивы переменной длины C alloca
или C99, например int arr[n]
) ;в этом случае компиляторы создадут традиционный фрейм стека с EBP.Или в рукописном asm, если вы push
в цикле используете стек вызовов в качестве структуры данных стека.
Например, x86 MSVC 19.14 компилирует этот C
int foo() {
volatile int i = 0; // force it to be stored to memory
return i;
}
В этот асм MASM.( Посмотрите сами в проводнике компилятора Godbolt )
;;; MSVC -O2
_i$ = -4 ; size = 4
int foo(void) PROC ; foo, COMDAT
push ecx
mov DWORD PTR _i$[esp+4], 0 ; note this is actually [esp+0] ; _i$ = -4
mov eax, DWORD PTR _i$[esp+4]
pop ecx
ret 0
int foo(void) ENDP ; foo
Обратите внимание, что он резервирует место для i
с push
вместо sub esp, 4
потому что это экономит размер кода и обычно примерно одинаковой производительности.Это то же количество мопов для внешнего интерфейса, без дополнительных стековых синхронизаций, потому что push
перед любой явной ссылкой на esp
, а pop
- после последней.
(Если бы он резервировал более 4 байтов, я думаю, что он просто использовал бы обычный sub esp, 8
или что-то еще.)
Здесь явно пропущена оптимизация;push 0
будет хранить значение, которое он на самом деле хочет, вместо того, чтобы мусор был в ECX.( Какой компилятор C / C ++ может использовать команды push pop для создания локальных переменных вместо простого увеличения esp один раз? ).И pop eax
будет очищать стек и load i
в качестве возвращаемого значения.
против.это с отключенной оптимизацией. Обратите внимание, что _i$ = -4
- это то же смещение от "стекового фрейма", но оптимизированный код использовал esp+4
в качестве базы, тогда как при этом используется ebp
.Это в основном просто забавный факт внутренних компонентов MSVC, который, кажется, думает с точки зрения того, где был бы EBP, если бы он не оптимизировал создание указателя кадра.Выбор опорной точки имеет смысл, и выстраиваются в очередь с его каркасно-указатель включен выбор является очевидным выбором.
;;; MSVC -O0
_i$ = -4 ; size = 4
int foo(void) PROC ; foo
push ebp
mov ebp, esp ; make a stack frame
push ecx
mov DWORD PTR _i$[ebp], 0
mov eax, DWORD PTR _i$[ebp]
mov esp, ebp
pop ebp
ret 0
int foo(void) ENDP ; foo
Интересно, что до сих пор использует пуш / поп резервировать 4 байта стека.На этот раз это вызывает одну дополнительную синхронизацию стека на процессорах Intel, потому что push ecx
после mov ebp,esp
перезагружает механизм стека до mov esp, ebp
.Но это довольно тривиально.