主要是如果把所有的寄存器都压栈和出栈,一方面是性能下降,毕竟处理器访问内存的时间是很长的,一个无用的寄存器做保护,压栈和出栈相比计算本身很慢;另一方面,也占用了内存,当函数调用栈很深或者出现递归的时候,就会更加明显。
从较深层的原因去分析,为什么会出现caller save
和callee save
?
在程序中,有的声明的变量,有的是临时变量,声明的变量往往会alive很久,但临时变量用完即废,举个例子:
int a, b, c, d;
a = b - c + d;
...
上面的a b c d
是声明出来的变量,假如分配的寄存器是r0~r3
,计算a
的值,处理器无法一条指令完成,中间结果需要使用临时的变量进行存储,比如tmp
,分配了t0
寄存器:
首先机选tmp = b - c
:
sub r1, r2, t0;
然后再计算a = tmp + d;
add t0, r3, r0;
…(tmp
生命期到此为止,t0
也可以释放)
你会发现tmp
这中临时的变量,生命期非常短,有专门的寄存器来保存这种临时变量,这种寄存器里面的值总是易变的,用完即废(保存完tmp
,接下来又保存tmp1
,tmp2
…);
所以函数调用过程中,对于这两类寄存器,有着不同的保护责任方。前面这种寄存器,如果在callee
里面改了,不做保护的话,caller
里面声明的变量的值都变了,破坏了功能,所以这类寄存器,callee
使用了一个就要保护一个;但针对第二类的寄存器,临时变量用完即废,callee
破坏了也可能没啥影响,callee
就没义务保护,但是如果caller
确实希望保存这个临时变量,那么必须由caller
声明进行保护。
具体的calling convention
,受保护的寄存器将由callee
负责保护,而不受保护的寄存器,将由caller
根据情况而定声明保护。
RISC-V
的情况如下图: