在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 /处理嵌套函数时,下划线的其他特性/要点是什么?
感谢
答案 0 :(得分:3)
对于数字2
,这取决于许多事情,包括编译器和平台。基本上,向函数传递和返回参数的不同方法称为calling conventions。文章Calling conventions on the x86 platform详细介绍了操作顺序,您可以看到它只是通过平台和编译器的这种小组合来获得多么丑陋和复杂,这很可能是您听到各种不同场景的原因{{ 3}}涵盖了更广泛的场景,包括64 bit
平台,但更难阅读。它变得更加复杂,因为gcc
实际上可能不是push
和pop
堆栈而是直接操纵堆栈指针,我们可以看到这样的示例,尽管在汇编The gen on function calling conventions.中。关于调用约定很难概括,如果参数的数量足够小,许多调用约定可以完全避免使用stack
并且将仅使用registers
。
对于数字3
,嵌套函数不会改变任何内容,它只会重复该过程以进行下一个函数调用。
关于数字1
正如Sean所指出的,.Net
编译为字节代码,并在堆栈上执行所有操作。 here上的维基百科页面就是一个很好的例子。
x86-64 ABI
Common Intermediate Language是另一个很棒的文档,如果你想了解一个特定的调用约定是如何工作的。 Figure 3.5 and 3.6
很简洁,因为它们给出了一个包含许多参数的函数的好例子,以及如何使用general purpose registers
,floating point registers
和stack
的组合传递每个参数。在查看涵盖调用约定的文档时,这种不错的图表很难找到。
答案 1 :(得分:1)
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上的这篇文章。