让我们有一个函数:
int caller()
{
int arg1 = 1;
int arg2 = 2
int a = test(&arg1, &arg2)
}
test(int *a, int *b)
{
...
}
所以我不明白为什么 &arg1 和 &arg2 也必须像这样压入堆栈
我可以理解我们可以通过使用在被调用者中获取 arg1 和 arg2 的地址
movl 8(%ebp), %edx
movl 12(%ebp), %ecx
但如果我们不将这两个压入堆栈, 我们还可以使用以下方式获取他们的地址:
leal 8(%ebp), %edx
leal 12(%ebp), %ecx
那么为什么还要将 &arg1 和 &arg2 压入堆栈呢?
最佳答案
在一般情况下,当您向它传递任意指针时,test
必须工作,包括指向 extern int global_var
或其他。然后 main
必须根据 ABI/调用约定调用它。
所以 test
的 asm 定义不能假设任何关于 int *a
指向的地方,例如它指向其调用者的堆栈框架。
(或者您可以将其视为优化掉局部引用调用中的地址,因此调用者必须将指向的对象放置在 arg 传递槽中,并返回堆栈的这 2 个双字内存保存 *a
和 *b
的潜在更新值。)
您在禁用优化的情况下编译。特别是对于调用者将指针传递给局部变量的特殊情况,此问题的解决方案是内联整个函数,编译器在启用优化时会执行此操作。
编译器允许制作test
的私有(private)克隆,通过值、寄存器或编译器想要使用的任何自定义调用约定获取其参数。不过,大多数编译器实际上并没有这样做,而是依靠内联而不是私有(private)函数的自定义调用约定来消除参数传递开销。
或者如果它被声明为 static test
,那么编译器就已经知道它是私有(private)的,理论上可以使用它想要的任何自定义调用约定,而无需使用 这样的名称进行克隆测试.clone1234
。 gcc 有时确实会为不断传播而这样做,例如如果调用者传递了编译时常量但 gcc 选择不内联。 (或者不能,因为您使用了 __attribute__((noinline)) static test() {}
)
顺便说一句,通过像 x86-64 System V 这样的良好的 register-args 调用约定,调用者会执行 lea 12(%rsp), %rdi
/lea 8(%rsp), %rsi
/call test
之类的。 i386 System V 调用约定陈旧且效率低下,将堆栈上的所有内容都传递到强制存储/重新加载。
您基本上已经确定了 stack-args 调用约定具有更高开销并且通常很糟糕的原因之一。
关于c - 将局部变量的地址压入堆栈(程序集)的目的是什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52379538/