【问题标题】:C++ (nested) function call instructions - registersC++(嵌套)函数调用指令 - 寄存器
【发布时间】:2013-03-09 07:04:35
【问题描述】:

C++ FAQ:

假设一个具有寄存器和堆栈的典型 C++ 实现, 寄存器和参数在 调用 g(),然后从 g() 内部的堆栈中读取参数 并在 g() 返回 f() 时再次读取以恢复寄存器。

关于嵌套函数调用

void f()
{
  int x = /*...*/;
  int y = /*...*/;
  int z = /*...*/;
  ...code that uses x, y and z...
  g(x, y, z);
  ...more code that uses x, y and z...
}

1/ C++ 的所有实现都带有寄存器和堆栈吗?这是否意味着:实现依赖于编译器/处理器/计算机体系结构?

2/ 当我调用f() 时,指令序列是什么(没有汇编语言,只是全局)?我已经阅读了关于这个主题的不同内容,而且我不记得提到的寄存器,但只有堆栈。

3/ 当您处理嵌套函数时,还有哪些额外的特性/要点需要强调?

谢谢

【问题讨论】:

  • “嵌套函数”?你的意思是“函数调用”吗?在 C++ 中调用函数并没有什么特别之处。事实上,这是一种相当常见的编程方式。
  • "所有的 C++ 实现都带有寄存器和堆栈吗?"没有.????
  • "当我调用 f() 时会发生什么?" f() 被调用并流到那里,这不是真正的火箭科学
  • 我的意思是函数调用。是的,这是一个相当常见的问题,关于指令/当我在 C++ 中调用函数时内部会发生什么(因为有机会对这个问题有一个明确的答案,并且因为我想知道嵌套调用的特别之处)
  • 我把问题说得更清楚了,我正在寻找函数调用说明的工作流程:)

标签: c++ architecture call cpu-registers function-calls


【解决方案1】:

对于编号2,这取决于许多因素,包括编译器和平台。基本上,将参数传递和返回函数的不同方式称为calling conventions。文章Calling conventions on the x86 platform 详细介绍了操作顺序,您可以看到仅使用平台和编译器的这种小组合就变得多么丑陋和复杂,这很可能是您听到各种不同场景的原因,The gen on function calling conventions. 涵盖更广泛的场景,包括64 bit 平台,但更难阅读。它变得更加复杂,因为gcc 可能实际上不是pushpop 堆栈,而是直接操作堆栈指针,我们可以看到一个这样的示例,尽管在汇编here 中。调用约定很难一概而论,如果参数的数量足够小,许多调用约定可以完全避免使用stack,而只会使用registers

对于号码3,嵌套函数并没有改变任何东西,它只是在下一个函数调用时再次重复该过程。

至于编号1 正如 Sean 指出的那样,.Net 编译为字节码,并在堆栈上执行所有操作。 Common Intermediate Language 上的维基百科页面就是一个很好的例子。

x86-64 ABIdocument 是另一个很棒的文档,如果您想详细了解一个特定的调用约定是如何工作的。 Figure 3.5 and 3.6 很简洁,因为它们给出了一个很好的例子,说明了一个具有许多参数的函数以及如何使用 general purpose registersfloating point registersstack 的组合传递每个参数。在查看涉及调用约定的文档时,这种漂亮的图表很少见。

【讨论】:

    【解决方案2】:

    1. 尽管寄存器/堆栈实现是 C++ 编译器最常见​​的底层实现,但没有什么可以阻止您使用不同的架构。例如,您可以编写一个编译器来生成 Java 字节码或 .NET 字节码,在这种情况下,您将拥有一个基于堆栈的 C++ 编译器。

    2. 当你调用 f() 时,典型的做法是:

    • 将返回地址压栈并跳转到f()

    • 在 f() 中:

    • 为局部变量 x、y 和 z 分配空间。这通常在堆栈上完成。查看调用堆栈上的this article

    • 当您到达 g(x,y,z) 时,编译器将生成代码,通过访问 f() 堆栈帧中的值将值压入堆栈。请注意,C/C++ 将参数从右向左推送。

    • 当您到达 f() 的末尾时,编译器会插入一个 return 指令。栈顶有要返回的地址(在调用 f() 之前被推送)

    3. 嵌套函数没有什么特别之处,因为一切都遵循相同的基本模板:

    • 调用函数 - 推送参数并调用函数。
    • 在函数内 - 为堆栈内的局部变量分配空间

    现在这是一般方法。编译器将引入他们自己的优化以提高性能。例如,编译器可能会选择将前 2 个参数存储在寄存器中(例如)。

    注意:虽然通过堆栈传递参数是迄今为止最常见的方法,但还有其他方法。如果您有兴趣了解更多信息,请查看register windows 上的这篇文章。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-26
      • 2016-10-05
      相关资源
      最近更新 更多