c - 将局部变量的地址压入堆栈(程序集)的目的是什么

标签 c assembly stack instruction-set

让我们有一个函数:

int caller()
{
   int arg1 = 1;
   int arg2 = 2
   int a = test(&arg1, &arg2)
}
test(int *a, int *b)
{
    ...
}

所以我不明白为什么 &arg1 和 &arg2 也必须像这样压入堆栈

enter image description here

我可以理解我们可以通过使用在被调用者中获取 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/

相关文章:

c - 堆栈内存使用连续动态分配的数组与静态

c++ - 变量周围的堆栈已损坏

ruby - 无需递归或使用 Ruby/Erlang 堆栈迭代生成排列

c - 如何在 Mac OS X 上编译和运行 C 程序

将复杂条件从 C 转换为 Fortran

c++ - 从地址调用函数

pointers - ARM:如何处理堆栈指针?

assembly - IBM Mainframe Assembler 程序显示在添加 COBOL 调用后 CPU 时间和运行时间出现了极大的跳跃。为什么?可以做些什么来加快速度?

c - 完善免费功能——求职面试

c - 扩展名为 "h.in"的文件是什么意思?