在大会上总计n00b,但我觉得我已经掌握了它。但是我对在函数中使用寄存器的最佳实践有疑问。
根据我的理解:在ARM11上的13个可用通用寄存器中,按照惯例,寄存器0-3用于传入参数(0和1也用于返回值),而4- 12用于存储函数持续时间的工作值。
然而,我也看到了代码示例,其中人们也使用寄存器0-3来表示工作值,只要它们中的任何一个都可用,因为它们不需要推送&将前一个值弹出到堆栈中。
虽然我可以理解为什么有人可能想要避免额外的推动和放大弹出步骤,似乎除了将值传入和传出函数之外,使用r0-r3可能会导致出现问题(因为您无法保证您调用的任何函数都会保留它们的值)。
那么这里的最佳做法是什么?我应该何时(如果有的话)使用寄存器0-3作为工作值,何时应该进入寄存器4-12?
答案 0 :(得分:3)
似乎除了传递值之外,还使用r0-r3 并且在一个功能之外可能会导致问题(因为你 不保证你打电话的任何功能都会保留它们 值)。
这正好可以使用r4-r11,因为ABI指定被调用方必须保留这些值:)
寄存器r0-r3被调用者保存,因此调用者必须确保在函数调用之前保存存储在这些寄存器中的任何重要值。作为被调用者,您可以在这些寄存器上执行任何操作。
答案 1 :(得分:1)
......似乎除了将值传入和传出函数之外,使用r0-r3可能会导致出现问题(因为您无法保证您调用的任何函数都会保留它们的值)。 / p>
寄存器比内存快,寄存器比L2缓存快,寄存器比L1缓存快,寄存器快。通过使用R4-R8,您可以创建额外的存储和负载。在手工编码的汇编程序中,这将创建额外的指令。对于ARM叶子汇编程序函数,没有序言,结尾是bx lr
。多么简单。
您的陈述似乎除了传递值之外的任何内容使用r0-r3对于许多算法和函数来说都是不正确的。考虑一个GCD实现,
int gcd(int a, int b)
{
while(a!=b)
if(a>b)
a = a - b;
else
b = b - a;
return a;
}
在算法期间,参数a
和b
会不断更新。第一次迭代后,永远不需要原始的a
和b
值。这个事实在编译器优化中是众所周知的Static single assignment form。寄存器重命名为 0 , 1 等。
因此输入参数通常不需要保持。无需将它们复制到r4-r8并强制生成堆栈帧。 ARM编译器将努力不这样做。人类不需要手动编码。如果必须,除非您正在学习,否则最好让编译器生成代码。 David Seal的ARM ARM的ARM gcd 算法是
gcd: cmp r0, r1
subgt r0, r0, r1
sublt r1, r1, r0
bne gcd
bx lr
例程是五条指令。如果保存了输入参数,则会使例程的大小加倍。
gcd:
stmfd sp!, {r4, r5} ; extra code plus two data
mov r4, r0 ; extra code
mov r5, r1 ; extra code
1: cmp r4, r5
subgt r4, r4, r5
sublt r5, r5, r4
bne 1b
mov r0, r4 ; extra code to setup return
ldmfd sp!, {r4, r5} ; extra code plus two data
bx lr
对于小输入,您可以将执行时间增加三倍。没有额外的代码,理解汇编程序也更容易。你永远不应该保存你不使用的寄存器。对于生产质量,专业代码,使用r1
到r3
到位并将其存储在堆栈中总是有意义的。