阅读汇编教程,我看到“功能”序言/结语包括:
push bp
mov bp, sp
---
pop bp
但我也看到了其他一些使用pusha
thenpopa
来保存暂存器的教程。那么为什么除了设定 bp 之外,函式 prologue/epilogue 不执行 pusha/popa 来保存暂存器背景关系?
uj5u.com热心网友回复:
它们不会保存所有暂存器,因为您并不总是需要保存所有暂存器。保存和恢复它们很慢。是的,这是一个很小的单一指令,看起来很节省,但它需要时间和堆栈空间。要了解保存的内容,请查看呼叫约定。
https://en.wikipedia.org/wiki/X86_calling_conventions
PUSHA/PUSHAD—压入所有通用暂存器
这些是缓慢的指令。在 Skylake 上,PUSHA 需要 19 uop 和 8 个吞吐量周期。POPA 需要 18 uop 和 8c 的吞吐量。
此外,PUSHA/PUSHAD 在 64 位中无效。当 x86 扩展到 64b 时,它们被 AMD 清除,然后被 Intel 清除。
现代编译器反其道而行之,尽可能避免保存暂存器。LLVM 执行称为收缩包装的分析,其中 prolog 被向前推以允许快速提前退出。
https://llvm.org/doxygen/ShrinkWrap_8cpp_source.html
这些是可怕的,可怕的,没有好的,非常糟糕的指示。
uj5u.com热心网友回复:
对于真正的汇编语言(您不必遵守不同语言的呼叫约定),“函式序言/结尾”一词没有意义。
对于“旨在符合某些其他语言的呼叫约定的汇编语言”;您只需要保存/恢复一些暂存器(可能没有)。
举个例子;对于 CDECL,EAX、ECX 和 EDX 的内容可以被被呼叫者丢弃,永远不需要被被呼叫者保存/恢复(如果他们关心,呼叫者需要保存它们);如果一个函式不使用任何其他暂存器,被呼叫者也不需要保存或恢复任何其他暂存器。还要注意,“EBP 作为帧指标”是过时的垃圾(它存在是因为除错器不是很好,并且在除错信息改进时变得毫无意义——例如 DWARF 除错信息等)。这些东西结合在一起意味着这样的事情对于 CDECL 来说具有可接受的序言和尾声:
myFunction:
mov eax,12345 ;eax = returned value
ret
如果确实需要保存和恢复“大量”暂存器;pusha
很慢(微编码),一系列多push
条指令也很慢(存盘的地址取决于最近修改的 ESP 中的值)。典型的方法是自己做,例如:
;Don't bother saving EAX, ECX, EDX.
sub esp,16 ;Create space to save 4 registers (but maybe more for local variables)
mov [esp],ebx
mov [esp 4],esi
mov [esp 8],edi
mov [esp 12],ebp
然而; 成本是空间。在代码大小极其有限(例如“512 字节的一部分”)的引导加载程序中,聪明的程序员将使用真正的汇编语言(其中“函式序言/结尾”没有意义),初学者可能会使用它pusha
来节省空间(没有意识到他们没有理由关心其他编程语言的呼叫约定)。
0 评论