假设我们有以下简单程序:
int foo() {
int a = 1, b = 2, c = 3, w;
c = abs(c);
c = c + b + 7;
for (w = 0; w<10; w++) {
b += bar(b);
}
return c;
}
int bar(int v) {
int a = 1, b = 2;
a += v + b;
printf(“v=%d\n”, v);
return v;
}
如果调用foo()
一次,我们如何确定为变量a
,b
,c
和{{1}执行的保存/恢复操作的数量当存储在调用者 - 存档寄存器vs 被调用者 - 存储寄存器中时?可能是我不理解术语,而是解释一个过程,在这个过程中,每个变量在每种情况下确定每个变量的加载/存储量是多少。
感谢您的时间。
答案 0 :(得分:2)
唯一的答案是“至少为零”。
现代编译器将从a
中删除b
,c
和foo()
。由于这些变量不存在,因此永远不会加载或存储它们。
w
变量可能存在,也可以通过展开循环来消除。
a
中的b
和bar()
变量不需要,因此它们也可能会被删除。
编译器可能会产生如下输出:
int foo()
{
printf("v=%d\n", 2);
printf("v=%d\n", 4);
printf("v=%d\n", 8);
printf("v=%d\n", 16);
printf("v=%d\n", 32);
printf("v=%d\n", 64);
printf("v=%d\n", 128);
printf("v=%d\n", 256);
printf("v=%d\n", 512);
printf("v=%d\n", 1024);
return 12;
}
如您所见,此优化版本中没有要保存或恢复的变量。
使用-O2
进行编译时,GCC会将rbx
和rbp
寄存器保存在x86-64上,这些寄存器是被调用者保存的。这些寄存器保存一次并恢复一次。 GCC的foo()
输出从不会调用bar()
,而是直接调用printf()
。
foo:
pushq %rbp ; save register rbp
movl $2, %ebp ; b = 2
pushq %rbx ; save register rbx
movl $10, %ebx ; ctr = 10
subq $8, %rsp
.L4: ; begin loop
movl %ebp, %esi
xorl %eax, %eax
movl $.LC0, %edi
call printf ; printf("v=%d\n", b)
addl %ebp, %ebp ; b += b
subl $1, %ebx ; ctr--
jne .L4 ; end loop (when ctr == 0)
addq $8, %rsp
movl $12, %eax ; return 12
popq %rbx ; restore register rbx
popq %rbp ; restore register rbp
ret
答案 1 :(得分:0)
术语“调用者保存寄存器”和“被调用者保存寄存器”分别指由函数调用者保存的寄存器和被调用函数保存的寄存器。什么寄存器取决于处理器架构,以及称为“ABI”(应用程序二进制接口)的规范 - 这从处理器系列到处理器系列各不相同,并且在操作系统之间通常也不同。但基本原则是编译器产生的机器代码是按照一定的期望构建的:任何函数都可以自由使用R0,R1,R2,并且调用者必须期望在调用中修改它们。 R3-R6由被调用函数保存,如果调用函数使用R7或R8,它必须在调用任何函数之前保存它们。 [这是一个“任意处理器”,实际上并不存在]。
所以要分析你的功能......
让我们先选择这个功能:
int foo() {
int a = 1, b = 2, c = 3, w;
c = abs(c);
c = c + b + 7;
for (w = 0; w<10; w++) {
b += bar(b);
}
return c;
}
未使用变量'a',因此可以删除。
函数'abs'可以在编译时解析,因此可以删除该行。
c = c + b + 7;
可以在编译时解析为2 + 3 + 7 = 12.我们只需将常量12移动到return语句并删除c
。
所以,现在我们离开了:
int foo() {
int b = 2, w;
for (w = 0; w<10; w++) {
b += bar(b);
}
return 12;
}
至少使用2个寄存器。
对下一个功能做同样的事情:
int bar(int v) {
int a = 1, b = 2;
a += v + b;
printf(“v=%d\n”, v);
return v;
}
我们可以删除a = 1, b = 2
和a += v + b
,因为未使用a
和b
。
这留下了:
int bar(int v) {
printf(“v=%d\n”, v);
return v;
}
现在,一个聪明的编译器会将bar
内联到foo中,现在我们得到:
int foo() {
int b = 2, w;
for (w = 0; w<10; w++) {
printf(“v=%d\n”, b);
b += b;
}
return 12;
}
实际上,这可以通过两个或三个寄存器来完成,加上printf
需要做的任何事情(我们无法确切地告诉它是什么......)。编译器可能会进行另一步并“展开”循环,在这种情况下,可能会删除一个以上的寄存器。