帧指针有什么优点?

时间:2017-10-17 19:38:28

标签: assembly mips

我们正在研究MIPS汇编程序(我猜这个问题一般可以应用于汇编),老师向我们介绍了帧指针

如果我有一个函数序言,我曾经直接做堆栈指针

addiu $sp, $sp, -8   ; alloc 2 words in the stack
sw $s0, 4($sp)       ; save caller function $s0 value in the stack
sw $ra, ($sp)        ; save the return address for the callee function

在功能结局中:

move $v0, $0         ; set 0 as return value
lw $s0, 4($sp)       ; pick up caller $s0 value from the stack
lw $ra, ($sp)        ; pick up return address to return to the caller
addiu $sp, $sp, 8    ; dealloc the stack words I used
jr $ra               ; return back to caller

老师说当我们在汇编中编写函数时,使用帧指针对我们人类很有用:

addiu $sp, $sp, -12  ; alloc 3 words in the stack
sw $fp, 8($sp)       ; save caller frame pointer in the stack
addiu $fp, $sp, 8    ; set $fp to the uppermost address of the activation frame
sw $ra, -4($fp)      ; saving like the first example, but relative 
sw $s0, -8($fp)      ; to the frame pointer

老师还说有时堆栈指针继续分配其他空间并且因为我们需要注意,所以在函数内引用激活框架更难。 使用帧指针,我们将有一个指向激活帧的静态指针。

是的,但我需要在函数中使用激活,因为它只包含调用函数的已保存数据吗?

我认为这使得实施起来更加困难。有一个真正的实际例子,帧指针对程序员来说是一个很大的优势吗?

2 个答案:

答案 0 :(得分:2)

在堆栈上动态分配可变数量的空间时,您只需要一个帧指针。在C中使用可变长度数组和/或alloca的函数是需要帧指针的函数的示例。因为函数使用的堆栈量是变量的,所以不能使用堆栈指针的常量偏移来访问变量,并且需要一种方法来在函数返回时撤消变量长度分配。使用帧指针解决了这两个问题。您可以使用它来使用常量偏移来处理堆栈变量,并将堆栈指针恢复为函数开头时的值。

在MIPS上,如果总堆栈分配超过32k,那么在仅使用固定大小堆栈分配的函数中使用帧指针作为优化也是有意义的。 MIPS支持的有限寻址模式仅允许相对于堆栈指针或任何其他寄存器的16位符号扩展偏移。由于堆栈指针指向堆栈的底部,因此只有非负的偏移量可以与堆栈指针一起使用,因此堆栈中只有32k可以在单个指令中寻址。通过使用帧指针并将其指向堆栈帧的中间(而不是帧的顶部),它可以用于在单个指令中寻址高达64k的堆栈。

否则使用帧指针只会使程序员受益,而不是程序。如果程序中的所有函数都使用带有帧指针的标准堆栈帧,则帧指针和存储在堆栈中的所有已保存帧指针值将形成堆栈帧的链接列表。在调试时,可以轻松地遍历此链接列表以创建函数调用的引用。但是,使用合适的现代调试器,还可以将元数据(展开信息)嵌入到可执行文件中,即使未使用帧指针,调试器也可以使用它来遍历堆栈帧。现代编译器可以自动执行此操作,但在汇编语言中,包含所有必要的额外指令以使其工作可能会非常痛苦。

如果在函数期间堆栈指针可以通过固定大小分配和解除分配多次更改,那么在程序中的任何给定点跟踪变量相对于堆栈指针的位置可能会很痛苦。虽然它在任何给定位置始终处于固定偏移量,但这将根据位置而改变。确定该偏移可能是棘手的并且容易出错。使用一个帧指针会给每个变量一个相对于永不改变的帧指针的偏移量,因为帧指针值不会改变。

请注意,如果您觉得需要使用帧指针,因为最后两个原因之一就是您必须首先考虑为什么要在汇编中进行编程。这些是现代编译器不需要使用帧指针的情况,因此会产生更好的代码。

答案 1 :(得分:1)

帧指针省略是C和C ++编译器实现的standard optimization option。优点是它可以释放寄存器供其他用途。然而,它经常被禁用,它使调试程序崩溃变得非常困难。即使是生成堆栈跟踪等基本内容也很困难,您不再知道父函数的框架位于何处,因此不知道在何处查找返回地址。检查局部变量的状态同样会变得很痛苦。在调试应用程序时,这并不重要,当程序在生产中崩溃时,它又回来了。

需要调试元数据才能知道要查看的位置,您必须知道框架的大小。通常,使代码可调试和可诊断是更多比使其易于编写更重要。典型的程序员花在调试和测试上的时间比写作要多。