Bru*_*uce 4 x86 assembly calling-convention
IA-32的共同呼叫惯例是:
• Callee-save registers
%ebx, %esi, %edi, %ebp, %esp
Callee must not change these. (Or restore the caller's values before returning.)
• Caller-save registers
%eax, %edx, %ecx, condition flags
Caller saves these if it wants to preserve them. Callee can freely clobber.
Run Code Online (Sandbox Code Playgroud)
为什么存在这种奇怪的约定?为什么不在调用另一个函数之前保存所有寄存器?或者让被叫方用pusha/ 保存并恢复所有内容popa?
为什么要编写代码来保存您可能不需要的每个函数中的寄存器?这将为每个函数调用添加额外的代码和额外的内存写入.它现在看起来可能并不重要,但是在80年代创建这个大会时它可能确实很重要.
请注意,ia-32没有固定的调用约定 - 你列出的只是一个外部约定 - ia-32不会强制执行它.如果您正在编写自己的代码,则可以根据需要使用寄存器.
另请参阅Old New Thing Blog上的调用约定历史.
在决定调用约定应保留哪些寄存器时,需要平衡调用者的需求与被调用者的需求.调用者希望保留所有寄存器,因为这样就不需要调用者担心在调用中保存/恢复值.被调用者宁愿不保留寄存器,因为这样就不需要在输入时保存值并在退出时恢复它.
如果您需要保留的寄存器太少,则调用者将填充注册保存/恢复代码.但是如果你需要保留太多的寄存器,那么被调用者就有义务保存和恢复调用者可能没有真正关心的寄存器.这对于叶子函数(不调用任何其他函数的函数)尤为重要.
一个猜测:
如果调用者保存了函数调用后仍需要的所有寄存器,则会浪费被调用函数不修改所有寄存器的时间.
如果被调用者保存了它所改变的所有寄存器,那么当调用者不再需要这些寄存器中的值时,会浪费时间.
当一些寄存器由调用者保存时,一些寄存器由被调用者保存,编译器(或汇编程序员)可以根据在下一个函数调用后是否需要该值来选择使用哪种寄存器.
如果您更深入地研究所使用的寄存器,您就会明白为什么它们不会被被调用者保留:
EAX: 用于函数返回,所以很明显它不能被保留。EDX:EAX:用于64位函数返回,同EAX。ECX:这是计数寄存器,在 x86 的旧时代,当它LOOPcc“很酷”时,这个寄存器会被疯狂地敲打,即使在今天,仍然有相当多的指令用作ECX计数器(如REP前缀指令)。__thiscall然而,由于和的出现__fastcall的出现,它习惯于传入参数,这意味着它很可能会改变,所以保留它几乎没有意义。ESP:这是一个小异常,因为它没有真正保留,它根据堆栈更改而更改。尽管可以保留它以防止堆栈指针损坏/安全或由于内联汇编(通过堆栈帧)而导致不平衡。现在它实际上变得直观了:)