计算机硬件对过程的支持
基本概念:
- 过程(procedure)或函数是程序员进行结构化编程的工具,两者均可以提高程序的可理解性和代码的重用性。过程允许程序员将精力集中在任务的一部分,由于参数能传递数值并返回结果,因此参数承担过程与其他程序,数据之间接口的角色。
过程运行中,程序必须遵循以下六个步骤:
- ① 将参数放在过程可以访问的位置。
- ② 将控制转交给过程。
- ③ 或得过程所需的存储资源
- ④执行需要的任务。
- ⑤ 将结果的值放在调用程序可以访问的位置。
- ⑥ 将控制权返回初始点,因为一个过程可能有一个程序中的多个调用点。
MIPS软件在为过程调用分配32个寄存器时遵循以下约定:
- $a0~$a3:用于传递参数的四个参数寄存器
- $v0~$v1:用于返回值的两个寄存器
- $ra: 用于返回起始点的返回地址寄存器。
跳转和链接指令:
jal ProcedureAddress
其作用是:跳转到某个地址的同时将下一条指令的地址保存在寄存器$ra
(31号寄存器)中。
为了支持这种情况,类似MIPS的计算机使用了寄存器跳转(jump register)指令jr
,用于case
语句,表示无条件跳转到寄存器所指定的地址。
总结:调用者将参数存储在$a0~$a3寄存器中,然后使用
jal X
跳转到过程X(被调用者),被调用者执行运算,将结果放在$v0~$v1,然后使用jr \$ra
指令将控制权返回给调用者。
2.8.1使用更多的寄存器
为什么会使用更多寄存器?
- 过程运行时需要多余4个的参数寄存器,或多于2个返回值寄存器。
解决办法:
- 将准备在当前过程中用来存储多余4个的参数值的寄存器中的值存储在栈中,然后在当前过程中使用这些寄存器,当前过程结束时在将栈中保存的值存到相应的寄存器中。从而避免寄存器不足问题。其中29号寄存器是栈指针寄存器($sp)
- 按照惯例,栈增长是按照从高地址到低地址的顺序进行的。这意味着将数据压栈时,栈指针值减小;而数据出栈时,栈长缩短,栈指针增大。
例子:
将下面c程序转化为MIPS汇编
int leaf_example(int g, int h, int i, int j){
int f;
f = (g+h) - (i+j);
return f;
}
g, h, i, j, f,对应$a0,$a1, $a2, $a3, $s0.
addi $sp, $sp, -12
sw $t1,8($sp)
sw $t2,4($sp)
sw $s0,0($sp)
add $t0, $a0, $a1
add $t1, $a2, $a3
add $s0, $t0, $t1
add $v0, $s0, $zero
lw $s0, 0($sp)
lw $t0, 4($sp)
lw $t1, 8($sp)
addi $sp, $sp, 12
jr $ra
2.8.2嵌套过程
- 概念:
- 叶过程:不调用其他过程的叶过程。
1、为什么
- 假设主程序将参数3存入寄存器$a0,然后使用
jal A
调用过程A.再假设过程A通过jal B
调用过程B,参数为7,同样存入$a0, 由于A尚未结束任务,所以在寄存器$a0的使用上存在冲突。同样寄存器$ra保存返回地址上也存在冲突。
2、怎么处理
- 将其他所有必须保留的寄存器压栈,就像将保存寄存器压栈一样。调用者将所有调用后还需要的参数寄存器或临时寄存器压栈。被调用者将返回地址寄存器$ra,和调用者使用的保存寄存器($s0~$s7)都压栈。栈指针$sp,随着寄存器个数调整,到返回时恢复。
3、例子
- 计算阶乘的递归过程:
int fact(int n)
{
if(n < 1) return (1);
else return (n*fact(n-1));
}
- MIPS汇编版本:n对应寄存器$a0
fact:
addi $sp, $sp, -8
sw $ra, 4($sp)
sw $a0, 0($sp)
slti $t0, $a0, 1
beq $t0, $zero, L1
addi $v0, $zero, 1
addi $sp, $sp, 8
jr $ra
L1:
addi $a0, $a0, -1
jal fact
lw $a0, 0($sp)
lw $ra, 4($sp)
addi $sp, $sp, 8
mul $v0, $a0, $v0
jr $ra
- 全局指针($gp):指向静态数据区的保留寄存器。