为什么功能激活记录中需要动态link? (在静态范围语言中)

Why is dynamic link required in function activation record? (in static scoped language)

我读到动态 link 指向先前的激活记录(也称为“堆栈框架”),因此它在动态范围编程语言中有意义。但是在静态作用域的编程语言中,为什么访问link(指向下一层嵌套中函数的激活记录)是不够的? 特别是在 C 语言中——为什么不需要访问 link?为什么需要动态 link?

您的问题可能与 -fomit-frame-pointer optimizing option of GCC, then see this.

有关

顺便说一句,很多人都在命名调用帧(在call stack) what you name activation record. The notion of continuation, and also of continuation passing style and A-normal form中是密切相关的。

动态 link 实际上只对 nested functions (and perhaps closures) 有用,标准 C 没有它们。有人说 display links。标准 C 没有嵌套函数,因此不需要任何相关技巧(显示 link、蹦床、...)。

GCC compiler provides nested functions as a C language extension, and implement them with dynamic links on activation records, very closely to what you are thinking about. Read also the wikipages on man or boy test and on trampoline.

我会使用这个我比较熟悉的命名法:

Activation record: Stack frame

Dynamic link: [saved] frame pointer

因此,我将您的问题解释为:为什么需要帧指针?[1]

帧指针不需要

一些编译器(例如 Green Hills C++、带有 -O2 的 GCC)通常不会生成一个,或者可以要求不生成它(MSVC、GCC)。

也就是说,它当然有它的好处:

  • 轻松遍历call stack: generating a stack trace就像遍历帧指针构成头部的链表一样简单。使实施堆栈跟踪和调试器变得更加容易。

  • 代码生成更简单:可以通过索引帧指针而不是一直在变化的堆栈指针来引用堆栈变量。堆栈指针随着每个 push/pop 而变化,帧指针在函数内保持不变(在 prologue/epilogue 之间)

  • 如果出现问题,stack unwinding 可以使用帧指针来完成。这就是 Borland 的结构化异常处理 (SEH) 的工作原理。

  • 简化堆栈管理setjmp(3), alloca(3) and C99-VLA 的具体实现可能(并且通常)依赖于它。

缺点:

  • 寄存器用法: x86 只有 8 个通用寄存器。其中之一需要完全专用于保存帧指针。
  • 开销:每个函数都会生成prologue/epilogue。

但是正如您所注意到的,编译器可以生成非常好的代码而无需维护帧指针。


[1] 如果不是这个意思,请详细说明。