热门问题
时间线
聊天
视角
调用堆栈
用于存储运行程序子程序的栈数据结构 来自维基百科,自由的百科全书
Remove ads
调用堆栈(英语:Call stack,台湾称呼叫堆叠)别称有:执行栈(execution stack)、控制栈(control stack)、运行时栈(run-time stack)与机器栈(machine stack),是计算机科学中存储有关正在执行的子程序的消息的栈。英文有时直接简称“栈”(the stack),但栈中不一定仅存储子程序消息。几乎所有计算机程序都依赖于调用堆栈,而高级语言一般将调用堆栈的细节隐藏至后台。
此条目可参照英语维基百科相应条目来扩充。 (2020年7月28日) |
调用堆栈最经常被用于存放子程序的返回地址。在调用任何子程序时,主程序都必须暂存子程序执行完毕后应该返回到的地址。因此,如果被调用的子程序还要调用其他的子程序,其自身的返回地址就必须存入调用堆栈,在其自身执行完毕后再行取回。在递归程序中,每一层次递归都必须在调用堆栈上增加一条地址,因此如果程序出现无限递归(或仅仅是过多的递归层次),调用堆栈就会产生栈溢出。
Remove ads
功能
调用堆栈的主要功能是存放返回地址。除此之外,调用堆栈还用于存放:
结构

调用栈由栈帧(stack frame)组成,它们也叫做“活动记录”或“活动帧”。它们是机器依赖并且ABI依赖的数据结构,包含了子例程状态信息。每个栈帧对应于一个子例程的仍未通过返回来完成的一次调用[1]。在堆栈顶部的栈帧对应当前执行例程。例如,一个叫做DrawLine的子例程当前正在运行,它被子例程DrawSquare所调用,右侧图示中给出了调用栈的顶部状况。
栈解绕

从被调用函数返回会弹出堆栈的顶部帧,并可能留下返回的一个值。更一般性的动作是从堆栈弹出一个或多个帧,从而恢复在程序中其它某处的执行,这叫做栈解绕(unwind),并且在使用了非局部控制结构的时候必须进行,比如例外处理。在这种情况下,一个函数的栈帧包含了指定例外处理器的一个或多个入口(entry)。在一个例外被抛出的时候,堆栈被解绕直到找到了准备处理(接住)这个抛出例外类型的一个处理器。
一些语言有要求一般性解绕的其他控制结构。Pascal允许使用全局性的goto语句,将控制从嵌套的函数传提交来并进入此前调用的外面的函数。这个操作要求堆栈被解绕,为了恢复正确的上下文,从而将控制传送给包围它的外面函数中的目标语句,需要移除多少栈帧就移除多少。与之类似,C语言有setjmp与longjmp函数充当非局部跳转,它们分别保存和恢复:栈指针、程序计数器、被调用者保存的寄存器和ABI要求的任何其他内部状态。
Common Lisp允许通过使用unwind-protect特殊算子,控制在堆栈被解绕时都要做些什么。在支持续体的编程语言比如Scheme和新泽西Standard ML中,在应用续体的时候,堆栈(在逻辑上)被解绕并重绕(rewind)上这个续体的堆栈,接着盘绕(wind)上要传递的那一个值。Scheme语言提供了对续体调用进行“保护”的dynamic-wind,只要控制离开或进入它所界定范围,包括通过应用续体这种非局部跳转方式,在分别对控制栈“解绕”或“重绕”的特定点上,都会执行任意的thunk。
解绕并重饶不是实现续体的唯一方式,例如通过使用多个显式的堆栈,应用一个续体可以简单的停用或舍弃当前堆栈并激活这个续体的堆栈。在支持续体的编程语言和执行栈可以在运行时检查并修改的编程语言比如Smalltalk和Cilk中,由这些堆栈链接起来而实现的实际运行栈被称为面条式堆栈[2],它包含了变量绑定和其他环境特征。在必须支持续体的时候,一个函数的局部变量在这个函数返回时不能被销毁:一个保存了的续体可以在往后时重入(re-enter)这个函数,并且不仅期望其中的变量不变动,而且期望整个栈都存在使得这个函数能再次返回。要解决这个问题,栈帧可以在面条式堆栈结构中动态分配,并在不再有续体引用的时候,将其留作垃圾回收。这种类型的结构还解决了上行和下行的函数参数问题,因为在这种基底上很容易实现头等词法闭包。
安全性
在较底层语言(如汇编语言与C语言中),程控消息与资料可能一同被存入调用堆栈中,因此造成安全隐患,可能允许恶意程序通过栈缓冲区溢出(stack buffer overflow)来获取程序的控制权。
参见
引用
延伸阅读
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads