在C / C ++中理解函数调用的堆栈框架?

时间:2013-04-18 16:13:04

标签: c++ c compiler-construction stack

我也是C / C ++和汇编语言的新手。     这也可能是一个非常基本的问题。     我试图了解堆栈帧是如何构建的以及哪些变量(params)按什么顺序被推送到堆栈?     一些搜索结果显示.... C / C ++的编译器基于函数内执行的操作来决定。例如,如果函数假设只是将传递的int参数的值递增1并返回(类似于++运算符),它将把函数和局部变量的所有函数和局部变量放在寄存器中并执行加法。 ...想知道哪个寄存器用于返回/传递值?....如何返回引用? .....差异b / w eax,ebx,ecx和edx。

请求书籍/博客/链接或任何类型的材料来理解寄存器,堆栈和堆引用在函数调用期间使用/构建和销毁....还有主函数的存储方式? 在此先感谢

2 个答案:

答案 0 :(得分:5)

你的问题在这里是临界的。 programmers可能是一个更好的地方。

理解堆栈等概念的好书可能是Queinnec的Lisp In Small Pieces(它很好地解释了堆栈对Lisp的意义)。另外,SICP  是read的好书。

D.Knuth的书和MMIX也很好读。

仔细阅读维基百科Call stack页面。

理论上,不需要调用堆栈,并且一些语言和实现(例如旧的SML / NJ)不使用任何堆栈(但是在垃圾收集堆中分配了调用帧)。请参阅A.Appel的旧论文Garbage Collection Can be Faster than Stack Allocation(并了解有关garbage collection的更多信息)。

通常C和C ++实现都有一个堆栈(通常使用硬件堆栈)。某些C局部变量可能没有任何堆栈位置(因为它们已经过优化,或者保存在寄存器中)。有时,C局部变量的堆栈位置可能会发生变化(编译器会在某些情况下使用一个调用堆栈槽,而对于同一局部变量的其他实例则使用另一个调用堆栈槽)。当然,一些临时值可能会像您的局部变量一样编译(因此保留在寄存器中,在一个堆栈槽中,然后在另一个堆栈槽中,等等....)。优化编译器时可能会对变量做一些奇怪的技巧。

在某些旧机器IBM/360或我BM z/series上,没有硬件堆栈; C编译器使用的堆栈是软件约定(例如,某些寄存器专用于该用途,没有特定的硬件支持)

考虑递归定义函数的执行(或解释)(就像古老的 factorial 天真编码)。阅读recursion(一般来说,in computer science),primitive recursive functionslambda calculusdenotational semanticsstack automatonregister allocation,{{ 3}},tail callscontinuationsABIinterruptsPosix signalssigaltstack(2)getcontext(2)等....等...

另请阅读有关longjmp(3)的书籍。在实践中,调用堆栈非常重要,以至于几个硬件资源(包括堆栈指针寄存器,通常是调用帧基指针寄存器,以及可能隐藏的机制,例如缓存相关)在共同的处理器上致力于它。

您还可以查看GCC编译器使用的中间表示。然后使用-fdump-tree-allComputer Architecture。如果查看生成的程序集,请确保将-S -fverbose-asm传递给gcc命令。

另请参阅GCC MELT probe

我给了很多链接。很难回答得更好,因为我不知道你的背景。

答案 1 :(得分:3)

  

我试图了解如何构建堆栈帧以及哪些堆栈帧   变量(params)被推送到什么顺序堆叠?

这取决于处理器的架构。但是,通常,堆栈从高地址向低地址增长(如果我们将内存地址视为数值)。一个堆栈帧是“无论此功能放在堆栈上”

放在堆栈上的“东西”通常是:

  • 将地址返回给调用函数。
  • 一个帧指针,指向呼叫开始时的堆栈帧。
  • 当此函数返回时需要“保留”的已保存寄存器。
  • 局部变量。
  • 调用堆栈中“next”函数的参数。
  

C / C ++编译器根据a中执行的操作决定   功能。例如,如果函数假设只是递增值   通过传递的int参数的1和返回(类似于++运算符)它   会把所有...   寄存器中函数和局部变量的参数    并执行添加....想知道哪个寄存器用于   返回/传递值?....如何返回引用?

编译器具有如何传递参数的规则,对于常规函数调用[即,不是“内联”函数],参数总是以相同的顺序传递,在寄存器和堆栈存储器的相同组合中。如果不是这种情况,编译器必须在决定传递参数之前确切地知道函数的作用。

不同的处理器架构有不同的规则。 x86-32通常有一个或两个寄存器用于输入参数,通常有一个寄存器用于返回值。 x86-64最多使用5个寄存器将前五个值传递给函数。任何其他参数都在寄存器中传递。

返回引用与返回任何其他值没有什么不同。值(在这种情况下是要返回的对象的地址)。在x86-32中,返回值在EAX中。在x86-64中,返回值在RAX中。在ARM中,R0用于返回值。在29K中,R96用作返回值。