代码之家  ›  专栏  ›  技术社区  ›  Evan Carroll

stdcall与cdecl:`ret`vs`sub esp`与调用约定有什么关系?

  •  -1
  • Evan Carroll  · 技术社区  · 6 年前

    Assembly Language, Seventh Edition for x86 Processors by Kip Irvine 在第325页,上面写着 8.2.4 32位调用约定 ,

    C呼叫约定 …C调用约定以一种简单的方式解决了清理运行时堆栈的问题:当程序调用子例程时,它遵循 CALL 带有向堆栈指针添加值的语句的指令( ESP )等于子程序参数的组合大小。下面是一个示例,其中两个参数(5和6)在执行 呼叫 说明,

    Example1 PROC
      push 6
      push 5
      call AddTwo
      add esp, 8
      ret
    Example1 ENDP
    

    因此,在子程序返回后,用C/C++编写的程序总是从调用程序中的堆栈中删除参数。

    接着说

    stdcall调用约定 从堆栈中删除参数的另一种常见方法是使用名为 STDCALL . 在下面 AddTwo 程序,我们提供 的整数参数 RET 指令,在返回调用过程后,该指令又向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一样,将参数按相反的顺序推送到堆栈上。把婴儿车放在 雷特 说明, stdcall 减少为子程序调用生成的代码量(一条指令),并确保调用程序永远不会忘记清理堆栈。另一方面,C调用约定允许子例程声明可变数量的参数。调用方可以决定它将传递多少参数。

    1 回复  |  直到 6 年前
        1
  •  -2
  •   Evan Carroll    6 年前

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

    • 在准备调用时,将参数推到堆栈上。
    • 在被调用函数中,在堆栈上分配局部变量。

    这两个惯例的区别不是 “一条指令” 也与此无关 RET 比如说,但在清理的地方。参数在调用之前放置在堆栈上,因此它们应该

    1. 当函数自身(局部变量)清除时清除。
    2. 函数返回后清除。

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

    整个 雷特 这篇文章似乎分散了人们的注意力,因为没有任何关于x86特有的呼叫约定。实际上,Windows10运行在 doesn't even support RET 此外,在第一个示例中, cdecl 编译器本可以编写,

    ret 8
    

    而不是

     add esp,8
     ret
    

    它也会有同样的效果。实际上,它可以保存一条指令