MIPS阶乘汇编代码分析

MIPS阶乘汇编代码分析

#压栈入参
addiu $sp,$0,0x10010080

设置栈指针**$sp**

addiu $s0,$0,5  #n=5
sw $s0,0($sp)
addiu $sp,$sp,-4

因为是求5的阶乘,所以初始参数为5

jal FACT
nop
j END
nop

FACT为阶段函数,nopmips的规范跳转之后必须跟nop

FACT:
#压栈返回地址
sw $ra,0($sp)
addiu $sp,$sp,-4

#读取入参
lw $s0,8($sp)

#压栈返回值
sw $0,0($sp)
addiu $sp,$sp,-4

#递归base条件
bne $s0,$0 ,RECURSION
nop

#读取返回地址
lw $t1,8($sp)

#出栈:返回值,返回地址
addiu $sp,$sp,8

#压栈返回值
addiu $s0 ,$zero,1
sw $s0,0($sp)
addiu $sp,$sp,-4

jr $t1
nop

上面是完整FACT代码,下面逐个分析

#压栈返回地址
sw $ra,0($sp)
addiu $sp,$sp,-4

ra保存的是pc的值,这句话就是将ra的值保存进栈。一般会和下面这句连用

jal FACT 
nop

jal表示跳转的同是把pc的值保存进去ra,所以上面两段代码配合就可以做到记录返回地址的作用。我们要知道在上面一段代码后面就是计算阶乘代码,所以每次压栈这个地址都是表示要进一次阶乘计算代码。而跳到FACT之后呢,紧接着就是压栈ra,然后将栈指针减4,指向一个存储区域。

#读取入参
lw $s0,8($sp)

读取入参之前,会有两次压栈,比如5这个参数压栈之后,后面就会压栈ra(存在jal指令)所以参数在当前sp的前8个,此时s0保存前一个参数。

#压栈返回值
sw $0,0($sp)
addiu $sp,$sp,-4

这里就是用0来占个位置,后续会复写该值。

#递归base条件
bne $s0,$0 ,RECURSION
nop

bne指令当s0不等于0是,跳到RECURSION。否则知心下面的语句

#读取返回地址
lw $t1,8($sp)

#出栈:返回值,返回地址
addiu $sp,$sp,8

#压栈返回值
addiu $s0 ,$zero,1
sw $s0,0($sp)
addiu $sp,$sp,-4

jr $t1
nop

读取返回地址,就是没次执行jal FACT这行代码所对应的pc位置。

出栈返回值和返回地址:当触发递归终止条件是此时返回值应该为0,因为一直用0来占位置。返回地址就上一次执行jal FACT的那个地址。

之后然如返回值1,因为阶乘终止条件会返回1,所以1就是返回值。将1压入栈,此时会覆盖上一个返回地址的位置。可能有人会奇怪,返回地址不是已经出站了吗?其实返回地址仍然在栈顶,因为每次压栈sp都会减4,所以一般来说sp指的位置都是没有指的。所以返回地址应该是在sp-12到s-8的那个位置,前两个分别是空和返回值。但是此时1会覆盖此返回值,并且sp-4。此时递归终止条件逻辑就写完了。

RECURSION:#recursion

addiu $s1,$s0,-1
sw $s1,0($sp)
addiu $sp,$sp,-4

jal FACT 
nop

lw $s0,20($sp)
lw $s1,4($sp)
lw $t1,16($sp)

mult $s1,$s0
mflo $s2
#出栈
addiu $sp,$sp,16

sw $s2,0($sp)
addiu $sp,$sp,-4
jr $t1
nop

上面是递归代码,下面对其进行分析

addiu $s1,$s0,-1
sw $s1,0($sp)
addiu $sp,$sp,-4

这三句其实就是参数减一然后将新的参数压入栈,栈指针-4。对应到高级语言就是f(n-1)的部分;

jal FACT 
nop

jal会把 当前pc的值给ra然后跳到fact,这里紧跟着后面就是讲ra的值压入栈,其实就调用地址入栈后面出栈计算紧跟着就是弹出当时压进去的调用地址。

lw $s0,20($sp)
lw $s1,4($sp)
lw $t1,16($sp)

上面三句要结合栈的情况来看,假设我们初始参数不是5而是1,ra前面的数表示不同深度的调用地址

100969288848076
11-ra002-ra0

88表示的是当前深度的参数,80为当前深度的返回值(只是占了一个位置),现在我们会进入下面的代码

#读取返回地址
lw $t1,8($sp)

#出栈:返回值,返回地址
addiu $sp,$sp,8

#压栈返回值
addiu $s0 ,$zero,1
sw $s0,0($sp)
addiu $sp,$sp,-4

jr $t1
nop

t1此时为2-ra,紧接着80和76会被出栈,栈顶为84。然后会把1压入栈也就是覆盖84的2-ra(2-ra已经被保存所以不用担心),压栈返回值之后栈是下面这样。

1009692888480
11-ra001

然后跳到t1也就是2-ra,代码如下1

lw $s0,20($sp)
lw $s1,4($sp)
lw $t1,16($sp)

mult $s1,$s0
mflo $s2
#出栈
addiu $sp,$sp,16

sw $s2,0($sp)
addiu $sp,$sp,-4
jr $t1
nop

s0就是100地址对应的1,s1就是84,t1就是1-ra。紧接着就是s1*s0然后保存到s2,将前面四个参数出栈,再将s2保存到栈顶,此时栈是下面这样。

10096
11

最后跳到1-ra 重复执行上面的步骤,结果为1然后返回。

可能我说的比较啰嗦,但是从这个例子就能明白递归的原理,无非就是保存每次的调用地址。最后在一次次进入调用地址。

完整代码如下:

#压栈入参
addiu $sp,$0,0x10010080

addiu $s0,$0,5  #n=5
sw $s0,0($sp)
addiu $sp,$sp,-4

jal FACT
nop
j END
nop

FACT:
#压栈返回地址
sw $ra,0($sp)
addiu $sp,$sp,-4

#读取入参
lw $s0,8($sp)

#压栈返回值
sw $0,0($sp)
addiu $sp,$sp,-4

#递归base条件
bne $s0,$0 ,RECURSION
nop

#读取返回地址
lw $t1,8($sp)

#出栈:返回值,返回地址
addiu $sp,$sp,8

#压栈返回值
addiu $s0 ,$zero,1
sw $s0,0($sp)
addiu $sp,$sp,-4

jr $t1
nop


RECURSION:#recursion

addiu $s1,$s0,-1
sw $s1,0($sp)
addiu $sp,$sp,-4

jal FACT 
nop

lw $s0,20($sp)
lw $s1,4($sp)
lw $t1,16($sp)

mult $s1,$s0
mflo $s2
#出栈
addiu $sp,$sp,16

sw $s2,0($sp)
addiu $sp,$sp,-4
jr $t1
nop

END:

在这里插入图片描述
结果如上图 16进制78就是120

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值