如果程序计数器指向下一条要执行的指令的地址,那么帧指针的作用是什么?
6 回答
它就像堆栈指针的更稳定版本
一些局部变量和参数的存储通常分配在堆栈帧中,这些堆栈帧只需在函数调用后将堆栈指针弹出回其原始级别即可自动释放。
但是,堆栈指针经常被调整,以便将参数推送到堆栈中以获得新的调用级别,并且至少在进入方法时进行一次调整,以便分配其自己的局部变量。调整堆栈指针还有其他更模糊的原因。
所有这些调整都使使用偏移量来获取参数、局部变量以及在某些语言中的中间词法范围变得复杂。编译器可能不太难跟踪,但如果正在调试程序,那么调试器(人或程序)也必须跟踪变化的偏移量。
如果从技术上讲是不必要的开销,则更简单的是分配一个寄存器来指向当前帧。在 x86 上,这是%ebp
. 在进入函数时,它可能与堆栈指针具有固定的关系。
除了调试之外,这还简化了异常管理,甚至可以通过消除或优化对堆栈指针的一些调整来收回成本。
您提到了程序计数器,因此值得注意的是,通常帧指针是一个完全软件结构,而不是硬件实现的东西,除非几乎每台机器都可以执行寄存器+偏移寻址模式。像 x86 这样的一些机器确实以寻址模式和宏指令的形式提供了一些硬件支持,用于创建和恢复帧。但是,有时会发现核心指令更快,并且最终不推荐使用宏操作。
这不是一个真正的 C 问题,因为它完全依赖于编译器。
然而,堆栈帧是考虑当前函数及其父函数的有用方式。通常,帧指针指向堆栈上的特定位置(对于给定的堆栈深度),您可以从中定位传入的参数以及局部变量。
这是一个示例,假设您调用一个函数,该函数接受一个参数并返回 1 和该参数之间的所有数字的总和。C 代码将类似于:
unsigned int x = sumOf (7);
: :
unsigned int sumOf (unsigned int n) {
unsigned int total = 0;
while (n > 0) {
total += n;
n--;
}
return total;
}
为了调用这个函数,调用者将 7 压入堆栈然后调用子程序。该函数本身设置帧指针并为局部变量分配空间,因此您可能会看到代码:
mov r1,7 ; fixed value
push r1 ; push it for subroutine
call sumOf ; then call
retLoc: mov [x],r1 ; move return value to variable
: :
sumOf: mov fp,sp ; Set frame pointer to known location
sub sp,4 ; Allocate space for total.
: :
此时(在 之后sub sp,4
),您有以下堆栈区域:
+--------+
| n(7) |
+--------+
| retLoc |
+--------+
fp -> | total |
+--------+
sp -> | |
+--------+
你可以看到你可以通过使用帧指针“上方”的地址和帧指针“下方”的局部变量来找到传入的参数。
该函数可以通过使用 来访问传入的值 (7) [fp+8]
,即内存的内容fp+8
(在此示例中,每个单元格为 4 个字节)。它还可以访问自己的局部变量 ( total
) [fp-0]
,其中的内存内容位于fp-0
。fp-0
即使减去零没有效果,我也使用了命名法,因为其他本地人将具有相应的较低地址,例如fp-4
,fp-8
等等。
当你在堆栈上上下移动时,帧指针也会移动,通常在调用函数之前将前一个帧指针压入堆栈,以便在离开该函数时轻松恢复。但是,虽然堆栈指针在函数中可能会剧烈移动,但帧指针通常保持不变,因此您始终可以找到相关变量。
这里有很好的讨论,有例子和所有。
简而言之:FP 指向堆栈上函数框架内的固定点(并且在函数执行期间不会更改),因此所有传递的参数和函数的本地(“自动”)变量都可以通过 FP 的偏移量访问(虽然 SP 可以在函数执行期间发生变化,而 PC 肯定会发生变化;-)。
通常是返回地址(但有时只是过去最后一个参数,例如)。关键是帧指针在方法的生命周期内是固定的,而堆栈指针可以在执行期间移动。
这非常依赖于实现(更多的是机器概念,而不是真正的语言概念)。
从您提供的评论中提取到另一个答案:
哇...堆栈指针?...这是程序计数器的同义词吗?
阅读有关调用堆栈的信息。基本上,调用堆栈存储当前方法的本地数据(局部变量、方法的参数和调用者的返回地址)。堆栈指针指向分配新空间的结构的顶部(通过将堆栈指针移动“更高”)。
帧指针指向当前帧中的一块内存区域(当前局部函数),通常它指向当前局部函数的返回地址。
由于没有人对此做出回应,我会试一试。帧指针(如果有内存的话)与堆栈指针一起是堆栈的一部分。堆栈由堆栈帧(有时称为激活记录)组成。堆栈指针指向堆栈的顶部,而帧指针通常指向帧结构中的某个固定点,例如返回地址的位置。有更详细的描述以及维基百科上的图片。