【问题标题】:what's on the stack when a function is called?调用函数时堆栈上有什么?
【发布时间】:2011-04-08 01:56:38
【问题描述】:

我只能想象 1)参数; 2) 局部变量;

还有什么?

1) 函数返回地址? 2) 函数名?

【问题讨论】:

标签: c++ c compiler-construction programming-languages callstack


【解决方案1】:

这确实取决于平台和架构,但通常:

  • 函数返回地址
  • 调用者的 CPU 寄存器的保存值 - 最重要的是调用者的堆栈帧指针值
  • 用 alloca() 分配的变量。
  • 有时 - 用于异常处理的额外内容,这非常依赖于平台。
  • 有时 - 检测堆栈破坏的保护值

据我所知,函数名称永远不会在堆栈中,除非您的代码将它放在那里。

【讨论】:

    【解决方案2】:

    我觉得a picture真的是一千个字。

    【讨论】:

    • 除了图片和解释不是很准确,因为参数通常作为寄存器和入栈值的组合传递;调用约定会考虑它们的类型和 CPU 系列运动的寄存器数量等。类似地,如果优化器为它们找到寄存器,局部变量可能实际上并不存在于堆栈中。
    • 托尼:你在所有方面都是正确的。但是,让我们先介绍一般情况,对吧?
    【解决方案3】:

    这取决于调用约定;对于 Unix,您通常在 SYSV ABI(应用程序二进制接口)中查找此信息。

    你可能会发现:

    • 返回地址(如果机器是流行的 Intel 架构)。在更现代的架构上,返回地址是在寄存器中传递的。

    • 被调用者保存寄存器——这些寄存器“属于”被调用者选择借用的调用者,因此必须保存和恢复。

    • 任何无法在寄存器中传递的传入参数。在 IA-32 中,no 参数在寄存器中传递;他们都在堆栈上。在 x86-64 中,最多可以在寄存器中传递六个整数和六个浮点参数,因此很少需要为此目的使用堆栈。

    • 您可能会或可能不会找到已保存的堆栈指针或帧指针。大多数现代调用约定都没有帧指针以节省额外的寄存器。在这个设计中,每个帧的大小在编译时就知道了,所以恢复旧的堆栈指针只是添加一个常量的问题。但这使得实现alloca() 变得更加困难。

      较旧的 Intel 调用约定同时使用堆栈指针和帧指针,这会消耗一个额外的寄存器,但它简化了 alloca() 并且还简化了堆栈展开。

    • 存储类为auto的局部变量在堆栈上分配。

    • 如果硬件没有提供足够的寄存器来保存所有计算的中间结果,堆栈可能包含编译器临时文件,这些临时文件保存的值会“溢出”。 (如果在任何时候实时中间结果的数量——程序稍后需要的——超过编译器可用于存储中间结果的寄存器数量,就会发生这种情况。)

    • 您可能会发现使用alloca() 分配的变量。

    • 您可能会发现元数据表明哪些 PC 范围在哪些异常处理程序的范围内,或其他非常依赖于平台的异常内容。

    • C 和 C++ 不支持垃圾回收,但在支持垃圾回收的语言中,您经常会发现元数据,这些元数据标识了您可以在堆栈帧中找到指针的位置。

    • 最后,堆栈可能包含“填充”,用于确保堆栈指针在 8 字节或 16 字节边界上对齐。

    调用约定是复杂的野兽,堆栈框架布局不适合胆小的人!

    【讨论】:

      猜你喜欢
      • 2021-05-10
      • 2014-11-24
      • 2011-11-20
      • 1970-01-01
      • 2020-10-24
      • 2020-07-12
      • 1970-01-01
      • 2014-08-30
      • 1970-01-01
      相关资源
      最近更新 更多