STDCALL与CDECL:`ret`与`sub esp`与调用约定有什么关系?

时间:2018-10-09 18:38:27

标签: x86 x86-64 calling-convention stdcall cdecl

在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调用约定允许子例程声明可变数量的参数。调用者可以决定要传递多少个参数。

1 个答案:

答案 0 :(得分:-2)

该代码有点令人困惑,因为一个显示调用,而另一个显示函数。为了简单起见,它们都应同时显示两者。为了调用约定,对堆栈的修改有两个阶段,

  • 为调用做准备,将参数并推入堆栈。
  • 在被调用的函数中,本地变量在堆栈中分配。

这两个约定之间的区别不是“一条指令” ,也不是每个RET都与之有关,而是与清理发生的地方有关。参数应放在调用之前的堆栈上,

  1. 该函数清除自身(本地对象)时要清除。
  2. 函数返回后要清理。

作为一个导入说明,第一个选项有很多优点,即您声明一个带有可变数量参数的函数。

整个RET片段似乎让人分心,因为关于x86的调用约定没有什么。实际上,Windows 10在doesn't even support RET的ARM上运行。此外,在第一个使用cdecl的示例中,编译器可以编写

ret 8

而不是

 add esp,8
 ret

它会产生相同的效果。实际上,它会保存一条指令