在325页的Assembly Language, Seventh Edition for x86 Processors by Kip Irvine中,它在 8.2.4 32位调用约定下显示,
C调用约定 ... C调用约定以一种简单的方式解决了清理运行时堆栈的问题:程序调用子例程时,它遵循
CALL
指令的语句,该语句向堆栈指针(ESP
)添加等于子例程参数组合大小的值。这是一个示例,其中在执行CALL
指令之前将两个参数(5和6)压入堆栈,Example1 PROC push 6 push 5 call AddTwo add esp, 8 ret Example1 ENDP
因此,用C / C ++编写的程序总是在子程序返回后从调用程序的堆栈中删除参数。
接着说
STDCALL调用约定从堆栈中删除参数的另一种常用方法是使用名为
STDCALL
的约定。在下面的AddTwo
过程中,我们为RET
指令提供了一个 interger参数,该参数在返回调用过程后又将ESP加8。整数必须等于该过程的参数消耗的堆栈空间的字节数:AddTwo PROC push ebp mov ebp,esp mov eax,[ebp+12] add eax,[ebp+8] pop ebp ret 8 AddTwo ENDP
应该指出,
STDCALL
与C一样,将参数以相反的顺序推入堆栈。通过在RET
指令中包含一个参数,STDCALL
减少了为子例程调用生成的代码量(由一条指令),并确保调用程序永远不会忘记清理堆栈。另一方面,C调用约定允许子例程声明可变数量的参数。调用者可以决定要传递多少个参数。
答案 0 :(得分:-2)
该代码有点令人困惑,因为一个显示调用,而另一个显示函数。为了简单起见,它们都应同时显示两者。为了调用约定,对堆栈的修改有两个阶段,
这两个约定之间的区别不是“一条指令” ,也不是每个RET
都与之有关,而是与清理发生的地方有关。参数应放在调用之前的堆栈上,
作为一个导入说明,第一个选项有很多优点,即您声明一个带有可变数量参数的函数。
整个RET
片段似乎让人分心,因为关于x86的调用约定没有什么。实际上,Windows 10在doesn't even support RET
的ARM上运行。此外,在第一个使用cdecl
的示例中,编译器可以编写
ret 8
而不是
add esp,8 ret
它会产生相同的效果。实际上,它会保存一条指令