在堆栈上推送局部变量地址的目的是什么(程序集)

amj*_*jad 3 c assembly stack instruction-set

我们有一个功能:

int caller()
{
   int arg1 = 1;
   int arg2 = 2
   int a = test(&arg1, &arg2)
}
test(int *a, int *b)
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

所以我不明白为什么&arg1和&arg2必须像这样被推到堆栈上

在此输入图像描述

我可以理解我们可以通过使用获取被调用者中的arg1和arg2的地址

movl  8(%ebp), %edx
movl  12(%ebp), %ecx
Run Code Online (Sandbox Code Playgroud)

但是如果我们不把这两个推到堆栈上,我们也可以通过使用它们来解决它们的问题:

leal 8(%ebp), %edx
leal 12(%ebp), %ecx 
Run Code Online (Sandbox Code Playgroud)

那么为什么要在堆栈上推送&arg1和&arg2呢?

Pet*_*des 5

在一般情况下,test当你传递任意指针时必须工作,包括to extern int global_var或者其他什么.然后main必须根据ABI /调用约定来调用它.

所以asm定义test不能假设任何关于哪里的int *a点,例如它指向其调用者的堆栈帧.

(或者您可以将其视为在本地化的逐个引用中优化掉地址,因此调用者必须将指向的对象放在arg传递的槽中,并且返回堆栈内存的那两个dword保持可能更新的*a和的值*b.)

您编译时已禁用优化.特别是对于调用者将指针传递给本地人的特殊情况,此问题的解决方案是内联整个函数,编译器将在启用优化时执行此操作.

允许编译器test通过值,寄存器或编译器想要使用的任何自定义调用约定来创建私有克隆.但是,大多数编译器实际上并没有这样做,并依赖于内联而不是私有函数的自定义调用约定来摆脱arg传递开销.

或者如果它已被声明static test,那么编译器就已经知道它是私有的,并且理论上可以使用它想要的任何自定义调用约定,而无需使用类似名称的克隆test.clone1234.gcc确实有时会为常量传播执行此操作,例如,如果调用者传递编译时常量但gcc选择不内联.(或者因为你用过而不能__attribute__((noinline)) static test() {})


和BTW,具有良好的寄存器ARGS调用诸如x86-64的系统V约定,呼叫者会做lea 12(%rsp), %rdi/ lea 8(%rsp), %rsi/ call test或东西.i386 System V调用约定旧且低效,传递堆栈上的所有内容,强制存储/重新加载.

您基本上已经确定了stack-args调用约定具有更高开销并且通常很糟糕的原因之一.