根据Computer Organization and Design MIPS 5th ed第103页,我的印象是帧指针$fp被设置为堆栈的第一个字。
int func(int g) {
int f = 9;
return g+f;
}
mips gcc 12.2.0生成的汇编代码。
func:
addiu $sp,$sp,-24
sw $fp,20($sp)
move $fp,$sp
sw $4,24($fp)
li $2,9 # 0x9
sw $2,8($fp)
lw $3,24($fp)
lw $2,8($fp)
nop
addu $2,$3,$2
move $sp,$fp
lw $fp,20($sp)
addiu $sp,$sp,24
jr $31
nop
如果$fp(帧指针)和$sp(堆栈指针)包含相同的地址,这是否有效?我认为$fp应该指向堆栈上的第一个字,即
func:
addiu $sp,$sp,-24
sw $fp,20($sp)
addiu $fp,$sp,20 #let $fp point to the first word on the stack
对于$fp应该指向堆栈的哪一部分,是否有任何规则,或者这完全取决于软件开发人员/编译器的决定?
1条答案
按热度按时间xdnvmnnf1#
愚者似乎遵循ABI链接的in this answer。
ABI要求:
在堆栈指针寄存器的任何其它使用之前,必须调整堆栈指针以分配堆栈帧。
一个函数通过从进入该函数的$sp中减去堆栈帧的大小来分配堆栈帧。这个$sp调整必须在$sp在函数中使用之前以及在任何跳转或分支指令之前发生。
因此,不可能实现书中描述的帧指针,这需要一个
move $fp, $sp
,然后一个addiu $sp, $sp, XX
。因此,愚者(未优化)根据此ABI生成的代码在帧下方有一个
fp
。ABI还规定了归航/阴影区:即使前四个args没有在堆栈上传递,调用程序也必须始终保留堆栈上的相应空间,以便被调用程序可以将args寄存器保存在该空间上。
通过查看指令
sw $4,24($fp)
并注意$fp + 24 = original $sp = just above the allocated frame
,您可以看到此行为。这意味着即使是非叶函数(调用其他函数的函数)通常也会有
$sp
=$fp
,因为编译器知道它需要多少空间。但是,您可以创建不满足此条件的情况,例如by using the infamous
alloca
:此代码经过编译(未优化),可实现以下功能:
为了更好地理解正在发生的事情,绘制堆栈的状态可能是值得的。
一般来说,编译器使用的帧处理策略会随着时间的推移而变化,您必须对书籍中的内容持保留态度,因为没有人有时间或可能在每次发布新版本的编译器时更新和重新发布书籍。
只要一定要理解书中的例子是如何工作的,你就能很容易地适应新的约定。