3.7.5 寄存器中的局部存储空间
3.7.5 寄存器中的局部存储空间
寄存器组是唯一被所有过程共享的资源。虽然在给定时刻只有一个过程是活动的,我们仍然必须确保当一个过程(调用者)调用另一个过程(被调用者)时,被调用者不会覆盖调用者稍后会使用的寄存器值。为此,x86-64 采用了一组统一的寄存器使用惯例,所有的过程(包括程序库)都必须遵循。
根据惯例,寄存器 %rbx、%rbp 和 %r12~%r15 被划分为被调用者保存寄存器。当过程 P 调用过程 Q 时,Q 必须保存这些寄存器的值,保证它们的值在 Q 返回到 P 时与 Q 被调用时是一样的。过程 Q 保存一个寄存器的值不变,要么就是根本不去改变它,要么就是把原始值压入栈中,改变寄存器的值,然后在返回前从栈中弹出旧值。压入寄存器的值会在栈帧中创建标号为“保存的寄存器”的一部分,如图 3-25 中所示。有了这条惯例,P 的代码就能安全地把值存在被调用者保存寄存器中(当然,要先把之前的值保存到栈上),调用 Q,然后继续使用寄存器中的值,不用担心值被破坏。
所有其他的寄存器,除了栈指针 %rsp,都分类为调用者保存寄存器。这就意味着任何函数都能修改它们。可以这样来理解“调用者保存”这个名字:过程 P 在某个此类寄存器中有局部数据,然后调用过程 Q。因为 Q 可以随意修改这个寄存器,所以在调用之前首先保存好这个数据是 P(调用者)的责任。
来看一个例子,图 3-34a 中的函数 P。它两次调用 Q。在第一次调用中,必须保存 x 的值以备后面使用。类似地,在第二次调用中,也必须保存 Q(y) 的值。图 3-34b 中,可以看到 GCC 生成的代码使用了两个被调用者保存寄存器:%rbp 保存 x 和 %rbx 保存计算出来的 Q(y) 的值。
long P(long x, long y)
{
long u = Q(y);
long v = Q(x);
return u + v;
}a) 调用函数
long P(long x, long y)
x in %rdi, y in %rsi
1 P:
2 pushq %rbp Save %rbp
3 pushq %rbx Save %rbx
4 subq $8, %rsp Align stack frame
5 movq %rdi, %rbp Save x
6 movq %rsi, %rdi Move y to first argument
7 call Q Call Q(y)
8 movq %rax, %rbx Save result
9 movq %rbp, %rdi Move x to first argument
10 call Q Call Q(x)
11 addq %rbx, %rax Add saved Q(y) to Q(x)
12 addq $8, %rsp Deallocate last part of stack
13 popq %rbx Restore %rbx
14 popq %rbp Restore %rbp
15 ret Return
b) 调用函数生成的汇编代码
图 3-34 展示被调用者保存寄存器使用的代码。在第一次调用中,必须保存 x 的值,第二次调用中,必须保存 Q(y) 的值
在函数的开头,把这两个寄存器的值保存到栈中(第 2~3 行)。在第一次调用 Q 之前,把参数 x 复制到 %rbp(第 5 行)。在第二次调用 Q 之前,把这次调用的结果复制到 %rbx(第 8 行)。在函数的结尾(第 13~14 行),把它们从栈中弹出,恢复这两个被调用者保存寄存器的值。注意它们的弹出顺序与压入顺序相反,说明了栈的后进先出规则。
练习题 3.34 一个函数 P 生成名为 a0~a7 的局部变量,然后调用函数 Q,没有参数。GCC 为 P 的第一部分产生如下代码:
long P(long x)
x in %rdi
1 P:
2 pushq %r15
3 pushq %r14
4 pushq %r13
5 pushq %r12
6 pushq %rbp
7 pushq %rbx
8 subq $24, %rsp
9 movq %rdi, %rbx
10 leaq 1(%rdi), %r15
11 leaq 2(%rdi), %r14
12 leaq 3(%rdi), %r13
13 leaq 4(%rdi), %r12
14 leaq 5(%rdi), %rbp
15 leaq 6(%rdi), %rax
16 movq %rax, (%rsp)
17 leaq 7(%rdi), %rdx
18 movq %rdx, 8(%rsp)
19 movl $0, %eax
20 call Q
A. 确定哪些局部值存储在被调用者保存寄存器中。
B. 确定哪些局部变量存储在栈上。
C. 解释为什么不能把所有的局部值都存储在被调用者保存寄存器中。