mov
оставляет данные в стеке, pop
удаляет их, поэтому вы можете прочитать их только один раз и только по порядку.Данные ниже ESP должны считаться «потерянными», если только вы не используете соглашение о вызовах / ABI, включающее красную зону под указателем стека.
Данные , обычно , все еще там, нижеESP, но асинхронные вещи, такие как обработчики сигналов или отладчик, оценивающий call fflush(0)
в контексте вашего процесса, могут наступить на него.
Кроме того, pop
изменяет ESP, поэтому каждый pop
требует стека-unwind метаданные 1 в другом разделе исполняемого файла / библиотеки, чтобы он был полностью ABI-совместимым с SEH в Windows или i386 / x86-64 System V ABI в других ОС (который указывает, что все функции должныраскрутить метаданные, даже если они не являются функциями C ++, которые на самом деле поддерживают распространение исключений).
Но если вы читаете данные в последний раз и вам все это нужно, тогдада, pop - это эффективный способ чтения на современных процессорах (например, Pentium-M и более поздних, с механизмом стека для обработки обновлений ESP без отдельной операции.)
На старых процессорах, таких как Pentium III, pop
был на самом деле медленнее, чем 3x mov
+ add esp,12
, и компиляторы генерировали код, как показывает ответ Брендана.
void foo() {
asm("" ::: "ebx", "esi", "edi");
}
Эта функция заставляет компиляторсохранить / восстановить 3 сохраненных вызовом регистра (объявив на них клобберы.) На самом деле на самом деле их не трогает;встроенная строка asm пуста.Но это позволяет легко увидеть, что компиляторы будут делать для сохранения / восстановления.(Это единственный раз, когда они будут использовать pop
в обычном режиме.)
GCC по умолчанию (tune = generic) code-gen , или, например, с -march=skylake
, выглядит следующим образом( из проводника компилятора Godbolt )
foo: # gcc8.3 -O3 -m32
push edi
push esi
push ebx
pop ebx
pop esi
pop edi
ret
Но указание настроить старый ЦП без стекового движка делает это следующим образом:
foo: # gcc8.3 -march=pentium3 -O3 -m32
sub esp, 12
mov DWORD PTR [esp], ebx
mov DWORD PTR [esp+4], esi
mov DWORD PTR [esp+8], edi
mov ebx, DWORD PTR [esp]
mov esi, DWORD PTR [esp+4]
mov edi, DWORD PTR [esp+8]
add esp, 12
ret
gcc считает, что -march=pentium-m
не имеет стекового движка или, по крайней мере, решает не использовать push/pop
там.Я думаю, что это ошибка, потому что микроарх Agner Fog pdf определенно описывает движок стека, присутствующий в Pentium-M.
На PM и более поздних версиях push / pop - это однопроцессные инструкции,с обновлением ESP, обрабатываемым вне внешнего сервера, и для push микросхемы адреса хранения + данных хранилища микроплавленны.
В Pentium 3 по 2 или 3 монеты в каждом.(Опять же, смотрите таблицы инструкций Agner Fog.)
На P5 Pentium в порядке, push
и pop
на самом деле в порядке.(Но инструкций назначения памяти, таких как add [mem], reg
, обычно избегали, потому что P5 не разбивал их на мопы, чтобы лучше транслировать.)
Смешивание pop
с прямыми ссылками на [esp]
будет фактическибыть потенциально медленнее, чем один или другой, на современных процессорах Intel, потому что это требует дополнительных операций синхронизации стека.
Очевидно, что запись EAX 3 раза подряд означает, что первые 2 загрузки бесполезны в обоих случаях.Последовательности.
См. Extreme Fibonacci для примера, как pop (1 моп или как 1.1 моп с амортизированной синхронизацией стека) более эффективен, чем lodsd (2 мопа на Skylake) для чтениячерез массив.(В злом коде, который предполагает большую красную зону, потому что он не устанавливает обработчики сигналов. На самом деле не делайте этого, если вы точно не знаете, что делаете, и когда он сломается; это скорее глупые компьютерные уловки /экстремальная оптимизация для кода-гольфа, чем все, что практически полезно.)
Сноска 1: Проводник компилятора Godbolt обычно отфильтровывает дополнительные директивы ассемблера, но если вы снимите этот флажок, вы можетесм. функцию gcc, которая использует push / pop, имеет .cfi_def_cfa_offset 12
после каждого push / pop.
pop ebx
.cfi_restore 3
.cfi_def_cfa_offset 12
pop esi
.cfi_restore 6
.cfi_def_cfa_offset 8
pop edi
.cfi_restore 7
.cfi_def_cfa_offset 4
Директивы метаданных .cfi_restore 7
должны присутствовать независимо от push / pop и mov, потому что это позволяет стекуразматывать восстановить сохраненные вызовы регистры по мере раскрутки.(7
- номер регистра).
Но для других применений push / pop внутри функции (например, передача аргументов в вызов функции или фиктивная pop
для ее удаления из стека) у вас не будет .cfi_restore
, только метаданные дляуказатель стека изменяется относительно стекового фрейма.
Обычно вы не беспокоитесь об этом в рукописном асме, но компиляторы должны сделать это правильно, поэтому есть небольшая дополнительная плата за использование push/pop
с точки зрения общего размера исполняемого файла. Но только в тех частях файла, которые обычно не отображаются в память и не смешиваются с кодом.