帧指针有什么优点?
What are the advantages of a frame pointer?
我们正在学习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, [=11=] ; 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
老师也说了有时候栈指针一直在分配其他的space,在函数内引用激活帧比较难,需要注意。
使用帧指针,我们将有一个指向激活帧的静态指针。
是的,但是我是否曾经需要在函数内使用激活,因为它只包含调用函数的保存数据?
我认为这只会让事情更难实施。有没有真正的实例说明帧指针对程序员有很大的好处?
在堆栈上动态分配可变数量的 space 时,您绝对只需要一个帧指针。在 C 中使用可变长度数组 and/or alloca
的函数是需要帧指针的函数示例。因为函数使用的堆栈数量是可变的,所以您不能使用堆栈指针的常量偏移量来访问变量,并且您需要一种方法来撤消函数 returns 时的可变长度分配。使用帧指针可以解决这两个问题。您可以使用它来使用常量偏移来寻址堆栈变量,并将堆栈指针恢复到它在函数开始时的值。
在 MIPS 上,如果总堆栈分配超过 32k,则在仅使用固定大小堆栈分配的函数中使用帧指针作为优化也是有意义的。 MIPS 支持的有限寻址模式仅允许相对于堆栈指针或任何其他寄存器的 16 位符号扩展偏移量。由于栈指针指向栈底,栈指针只能使用非负偏移量,因此一条指令只能寻址栈上的 32k。通过使用帧指针并将其指向堆栈帧的中间(而不是帧的顶部),它可以用于在一条指令中寻址多达 64k 的堆栈。
否则使用帧指针只会对程序员有好处,对程序没有好处。如果程序中的所有函数都使用带有帧指针的标准堆栈帧,则帧指针和存储在堆栈中的所有已保存帧指针值形成一个堆栈帧链表。这个链接列表可以很容易地被横穿,以在调试时创建函数调用的引用。但是,使用合适的现代调试器,也可以将元数据(展开信息)嵌入到可执行文件中,即使不使用帧指针,调试器也可以使用它来遍历堆栈帧。这是现代编译器可以自动完成的事情,但在汇编语言中,包含所有必要的额外指令以使其工作可能会非常痛苦。
如果堆栈指针可以在函数期间通过固定大小的分配和解除分配多次更改,那么在程序中的任何给定点跟踪变量相对于堆栈指针的位置可能会很痛苦。虽然它在任何给定位置始终处于固定偏移量,但它会根据位置而变化。确定偏移量可能很棘手且容易出错。使用帧指针会给每个变量一个相对于永不改变的帧指针的偏移量,因为帧指针值不会改变。
请注意,如果您觉得需要使用帧指针是因为最后两个原因之一,您必须首先考虑为什么要使用汇编编程。在这些情况下,现代编译器不需要使用帧指针,因此会生成更好的代码。
帧指针省略是 C 和 C++ 编译器实现的 standard optimization option。优点是它可以释放一个寄存器用于其他用途。然而,它经常被禁用,这使得调试程序崩溃变得非常困难。即使是生成堆栈跟踪等基本内容也变得非常困难,您不再知道父函数的框架位于何处,因此不知道到哪里寻找 return 地址。检查局部变量的状态同样会很痛苦。这不仅在调试应用程序时很重要,当程序在生产中崩溃时它又会回来困扰。
需要调试元数据才能知道在哪里查看,您必须知道帧的大小。通常,使代码可调试和可诊断 比使其易于编写更 重要。典型的程序员花在调试和测试上的时间比写的要多。
我们正在学习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, [=11=] ; 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
老师也说了有时候栈指针一直在分配其他的space,在函数内引用激活帧比较难,需要注意。 使用帧指针,我们将有一个指向激活帧的静态指针。
是的,但是我是否曾经需要在函数内使用激活,因为它只包含调用函数的保存数据?
我认为这只会让事情更难实施。有没有真正的实例说明帧指针对程序员有很大的好处?
在堆栈上动态分配可变数量的 space 时,您绝对只需要一个帧指针。在 C 中使用可变长度数组 and/or alloca
的函数是需要帧指针的函数示例。因为函数使用的堆栈数量是可变的,所以您不能使用堆栈指针的常量偏移量来访问变量,并且您需要一种方法来撤消函数 returns 时的可变长度分配。使用帧指针可以解决这两个问题。您可以使用它来使用常量偏移来寻址堆栈变量,并将堆栈指针恢复到它在函数开始时的值。
在 MIPS 上,如果总堆栈分配超过 32k,则在仅使用固定大小堆栈分配的函数中使用帧指针作为优化也是有意义的。 MIPS 支持的有限寻址模式仅允许相对于堆栈指针或任何其他寄存器的 16 位符号扩展偏移量。由于栈指针指向栈底,栈指针只能使用非负偏移量,因此一条指令只能寻址栈上的 32k。通过使用帧指针并将其指向堆栈帧的中间(而不是帧的顶部),它可以用于在一条指令中寻址多达 64k 的堆栈。
否则使用帧指针只会对程序员有好处,对程序没有好处。如果程序中的所有函数都使用带有帧指针的标准堆栈帧,则帧指针和存储在堆栈中的所有已保存帧指针值形成一个堆栈帧链表。这个链接列表可以很容易地被横穿,以在调试时创建函数调用的引用。但是,使用合适的现代调试器,也可以将元数据(展开信息)嵌入到可执行文件中,即使不使用帧指针,调试器也可以使用它来遍历堆栈帧。这是现代编译器可以自动完成的事情,但在汇编语言中,包含所有必要的额外指令以使其工作可能会非常痛苦。
如果堆栈指针可以在函数期间通过固定大小的分配和解除分配多次更改,那么在程序中的任何给定点跟踪变量相对于堆栈指针的位置可能会很痛苦。虽然它在任何给定位置始终处于固定偏移量,但它会根据位置而变化。确定偏移量可能很棘手且容易出错。使用帧指针会给每个变量一个相对于永不改变的帧指针的偏移量,因为帧指针值不会改变。
请注意,如果您觉得需要使用帧指针是因为最后两个原因之一,您必须首先考虑为什么要使用汇编编程。在这些情况下,现代编译器不需要使用帧指针,因此会生成更好的代码。
帧指针省略是 C 和 C++ 编译器实现的 standard optimization option。优点是它可以释放一个寄存器用于其他用途。然而,它经常被禁用,这使得调试程序崩溃变得非常困难。即使是生成堆栈跟踪等基本内容也变得非常困难,您不再知道父函数的框架位于何处,因此不知道到哪里寻找 return 地址。检查局部变量的状态同样会很痛苦。这不仅在调试应用程序时很重要,当程序在生产中崩溃时它又会回来困扰。
需要调试元数据才能知道在哪里查看,您必须知道帧的大小。通常,使代码可调试和可诊断 比使其易于编写更 重要。典型的程序员花在调试和测试上的时间比写的要多。