我们有一个功能:
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传递的插槽中,并且在返回时,堆栈存储器的这两个双字持有可能更新的*a
和*b
值。)
您编译时已禁用优化。特别是对于调用者将指针传递给本地人的特殊情况,此问题的解决方案是内联整个函数,编译器将在启用优化时执行此操作。
允许编译器创建test
的私有克隆,它通过值或寄存器或者编译器想要使用的任何自定义调用约定获取其args。但是,大多数编译器实际上并没有这样做,并依赖于内联而不是私有函数的自定义调用约定来摆脱arg传递开销。
或者,如果它已被声明为static test
,那么编译器就已经知道它是私有的,并且理论上可以使用它想要的任何自定义调用约定,而无需使用像test.clone1234
这样的名称进行克隆。 gcc确实有时会对恒定传播做到这一点,例如:如果调用者传递编译时常量但gcc选择不内联。 (或者不能因为你使用了__attribute__((noinline)) static test() {}
)
顺便说一句,如果有一个很好的register-args调用约定,比如x86-64 System V,那么调用者就可以执行lea 12(%rsp), %rdi
/ lea 8(%rsp), %rsi
/ call test
等等。 i386 System V调用约定旧且低效,传递堆栈上的所有内容,强制存储/重新加载。
您基本上已经确定了stack-args调用约定具有更高开销并且通常很糟糕的原因之一。
如果直接访问arg1
和arg2
,则表示您正在访问不属于此函数的堆栈部分。当某人使用buffer overflow attack从调用堆栈访问其他数据时,会发生这种情况。
当你的调用有参数时,参数被推入堆栈(在你的情况下是&arg1
和&arg2
),函数可以使用它们作为这个函数的有效参数列表。