思维导图
一.计算机硬件对函数的支持
函数是通过指定的输入参数根据一定的处理方法从而得出输出参数的过程.
1.1函数的执行过程
函数或者方法会有输入参数,方法体,返回值等特征.汇编语言中为这些特征设计了一些寄存器.
- $a0-$a3:用于保存输入参数
- $v0-$v1:用于保存返回值
- $ra:保存返回地址,具体地址是函数调用指令地址的下一个字节的地址,是为了返回函数调用地址而存在.
- 程序计数器PC:用于保存当前运行的指令地址,使用函数调用指令是,$ra其实就是被赋值为PC+4的值.
函数的调用也有特定的指令支持
- 连接跳转指令jal:用于跳转到某个函数所在的地址,他会将此时的PC+4的值赋给$ra,以便函数执行后返回.
- 强制跳转指令jr:跳转至指定的寄存器位置,一般用于jr $ra,标识函数执行后返回.
现在函数的执行过程就比较清晰了,在函数的调用处调用jal指令,然后执行过程,最后通过jr指令返回调用地址.
1.2寄存器原始值的保存
- 栈指针$sp:保存栈顶的栈帧的地址.
- 帧指针$fp:同$sp一起确定一个函数在栈中的位置,可以根据这个值获取母函数的$sp及$fp,确定其界限,还可以存储局部变量.
图是盗用的,帧指针就是$fp,栈指针就是$sp
当调用一个函数时,我们需要保存其参数,返回值,返回地址.然而,当这个函数中又调用了其他的函数时,就没有那么多的寄存器以供使用了,所以,会使用一个先进后出的数据结构-----栈,保存母函数的相关数据.
栈是先进后出的有序队列.与专有的寄存器$sp配合.输入数据成为入栈(push),入栈时的顺序是高地址>低地址,输出数据成为出栈(pop)顺序则和入栈时相反.一般的保存约定是不保存临时寄存器$t0-$t9,保存保留寄存器$s0-$s7.
1.3嵌套函数实例
下面使用一个递归函数调用的实例来理解函数是如何被调用的.
//以C函数举例
int fact (int n){
if(n < 1){
return 1;
}else{
return (n * fact(n - 1));
}
}
以下是编译后的指令
//开始调用fact方法
fact:
addi $sp,$sp,-8 //保留连个数据4 * 2,所以将栈指针的地址减去8个字节
sw $ra,4($sp) //保存返回地址
sw $a0,0