一些局部变量和参数的存储通常分配在堆栈帧中,只需在函数调用后将堆栈指针弹回其原始级别即可自动释放.
但是,经常调整堆栈指针,以便在新的调用级别上将参数推送到堆栈,并且在进入方法时至少调整一次,以便分配其自己的局部变量.调整堆栈指针还有其他更为模糊的原因.
所有这些调整都使得偏移的使用变得复杂,以获得参数,本地,以及在某些语言中,中间词法范围.编译器跟踪可能并不太难,但如果程序正在调试,那么调试器(人或程序)也必须跟踪变化的偏移量.
如果在技术上是不必要的开销,只需分配一个寄存器指向当前帧就更简单了.在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;
}
Run Code Online (Sandbox Code Playgroud)
为了调用此函数,调用者将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.
: :
Run Code Online (Sandbox Code Playgroud)
此时(在此之后sub sp,4),您有以下堆栈区域:
+--------+
| n(7) |
+--------+
| retLoc |
+--------+
fp -> | total |
+--------+
sp -> | |
+--------+
Run Code Online (Sandbox Code Playgroud)
你可以看到你可以通过使用帧指针上方的地址和帧指针下面的局部变量找到传入的参数.
该函数可以通过使用[fp+8]存储器的内容来访问传入的值(7)fp+8(在这个例子中,每个单元中的每个是4个字节).它还可以访问自己的本地变量(total)[fp-0],内存的内容为fp-0.我已经使用了fp-0命名法,即使减去零也没有效果,因为其他本地人会有相应的较低地址,如fp-4,fp-8等等.
当您在堆栈中上下移动时,帧指针也会移动,通常在调用函数之前将前一帧指针压入堆栈,以便在离开该函数时轻松恢复.但是,虽然堆栈指针在函数内可能会疯狂移动,但帧指针通常保持不变,因此您始终可以找到相关变量.
| 归档时间: |
|
| 查看次数: |
3194 次 |
| 最近记录: |