高效的C编程之:寄存器分配
14.7 寄存器分配
编译器一项很重要的优化功能就是对寄存器的分配。与分配在寄存器中的变量相比,分配到内存的变量访问要慢得多。所以如何将尽可能多的变量分配到寄存器,是编程时应该重点考虑的问题。
| 注意 | 当使用-g或-dubug选项编译程序时,为了确保调试信息的完整性,寄存器分配的效率比不使用-g或-dubug选项低很多。 |
14.7.1 变量寄存器分配
一般情况下,编译器会对C函数中的每一个局部变量分配一个寄存器。如果多个局部变量不会交迭使用,那么编译器会对它们分配同一个寄存器。当局部变量多于可用的寄存器时,编译器会把多余的变量存储到堆栈。这些被写入堆栈需要访问存储器的变量被称为溢出(Spilled)变量。
为了提高程序的执行效率:
· 使溢出变量的数量最少;
· 确保最重要的和经常用到的变量被分配在寄存器中。
可以被分配到寄存器的变量包括:
· 程序中的局部变量;
· 调用子程序时传递的参数;
· 与地址无关变量。
另外,在一些特定条件下,结构体中的域也可以被分配到寄存器中。
表14.1显示了当C编译器采用ARM-Thumb过程调用标准时,内部寄存器的编号、名字和分配方法。
表14.1 C编译器寄存器用法
寄存器编号 | 可选寄存器名 | 特殊寄存器名 | 寄存器用法 |
r0 | a1 |
| 函数调用时的参数寄存器,用来存放前4个函数参数和存放返回值。在函数内如果将这些寄存器用作其他用途,将破坏其值。 |
r1 | a2 |
| |
r2 | a3 |
| |
r3 | a4 |
| |
r4 | v1 |
| 通用变量寄存器 |
r5 | v2 |
| |
r6 | v3 |
| |
r7 | v4 |
| |
r8 | v5 |
| |
r9 |
| v6或SB或TR | 平台寄存器,不同的平台对该寄存器的定义不同 |
r10 | v7 |
| 通用变量寄存器。在使用堆栈边界检测的情况下,r10保存堆栈边界的地址 |
r11 | v8 |
| 通用变量寄存器。 |
r12 |
| IP | 临时过渡寄存器,函数调用时会破坏其中的值 |
r13 |
| SP | 堆栈指针 |
r14 |
| LR | 链接寄存器 |
r15 |
| PC | 程序计数器 |
从表14.1可以看出,编译器可以分配14个变量到寄存器而不会发生溢出。但有些寄存器编译器会有特殊用途(如r12),所以在编写程序时应尽量限制变量的数目,使函数内部最多使用12个寄存器。
| 注意 | 在C语言中,可以使用关键词register给指定变量分配专用寄存器。但不同的编译器对该关键词的处理可能不同,使用时要查阅相关手册。 |
14.7.2 指针别名
C语言中的指针变量可以给编程带来很大的方便。但使用指针变量时要特别小心,它很可能使程序的执行效率下降。在一个函数中,编译器通常不知道是否有2个或2个以上的指针指向同一个地址对象。所以编译器认为,对任何一个指针的写入都将会影响从任何其他指针的读出,但这样会明显降低代码执行的效率。这就是著名的"寄存器别名(Pointer Aliasing)"问题。
| 注意 | 一些编译器提供了"忽略指针别名"选项,但这可能给程序带来潜在的bug。ARM编译器是遵循ANSI/ISO标准的编译器,不提供该选项。 |
1.局部变量指针别名问题
通常情况下,编译器会试图对C函数中的每一个局部变量分配一个寄存器。但当局部变量是指向内存地址的指针时,情况有所不同。先来看一个简单的例子。
void add(int * i)
{
int total1=0,total2=0;
total1+= *i;
- Linux下C编程基础之:常用编辑器(08-13)
- Linux下C编程基础之:gcc编译器(08-13)
- Linux下C编程基础之:gdb调试器(08-13)
- Linux下C编程基础之:make工程管理器(08-13)
- Linux下C编程基础之:使用autotools(08-13)
- Linux下C编程基础之:实验内容(08-13)