什么阻止被调用者清理堆栈?

Tho*_* K. 4 theory assembly calling-convention

作为扩展我的课程的一部分,我正在慢慢地沿着编程抽象的阶梯下降。现在我已经很好地掌握了 C,并且正在准备编写一些汇编(特别是 ARM 汇编)。

我遇到过调用约定的主题,虽然我通常理解它们的含义,但似乎从未被问过或回答过的问题是:

为什么被调用者不能处理堆栈上的变量参数?

到处都说被调用的函数不知道传递了多少参数,但在这种情况下,为什么不能简单地将数据放入寄存器或将其推入堆栈顶部以供被调用函数调用使用?

我问这个问题是针对任何利用堆栈进行子例程通信的体系结构,而不仅仅是 ARM 或 x86。

joh*_*und 5

被调用者可以从堆栈中清除变量参数。事实上,我做过一次。但代码相当大。

无论如何,在cdecl约定中,调用者清理堆栈的主要原因是其他的。(毕竟变量参数程序很少)

在某些体系结构上(通常非常小,如旧的 8080 或 6800),没有ret n指令可以自动执行堆栈清理,并且通常它们也无法使用堆栈指针进行算术运算。

因此,被调用者必须首先从堆栈中弹出返回地址才能到达参数,然后弹出所有参数,然后推回返回地址。对于 3 个参数,按照stdcall约定,它看起来是这样的:

    push arg1
    push arg2
    push arg3
    call proc

proc:
    pop  r1   ; the return address
    pop  r2
    pop  r2
    pop  r2
    push r1
    ret
Run Code Online (Sandbox Code Playgroud)

当使用cdecl约定时,可以节省 2 条指令和 1 个寄存器的使用:

    push arg1
    push arg2
    push arg3
    call proc
    pop  r2
    pop  r2
    pop  r2

proc:
    ret
Run Code Online (Sandbox Code Playgroud)

而且因为对于单一语言来说,最好在所有平台上使用单一调用约定,因此 CCALL 更简单、更通用,看起来更好。(C语言是在6800还是高科技的时代创建的)。

But notice, that on these platforms, the assembly programs and the native languages (for example different kind of BASICs) usually use register argument passing which is of course much more faster on such small systems.

Anyway, it is just a tradition. You can set the compiler to use whatever convention you want. For example, WinAPI is written in C++ but still uses stdcall convention, because it is better on x86 platform.