-1

假设我们有以下简单的程序:

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()被调用一次,我们如何确定对变量ab、执行的保存/恢复操作的数量c,以及w何时存储在调用者保存的寄存器与被调用者保存的寄存器中?可能是我不理解术语,但是对确定每个变量在每种情况下有多少加载/存储的过程的解释会很好。

感谢您的时间。

4

2 回答 2

2

唯一的答案是“至少为零”。

  1. 现代编译器将删除a,bcfrom foo()。由于这些变量不存在,它们将永远不会被加载或存储。

  2. w变量可能存在,也可能通过展开循环来消除。

  3. ab变量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 会保存 x86-64 上被调用者保存的rbxrbp寄存器。这些寄存器保存一次,恢复一次。GCC 的输出foo()never calls 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
于 2013-02-04T01:21:09.193 回答
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 = 2and a += v + b,因为aandb没有被使用。这留下:

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(我们无法真正说出那是什么......)。编译器很可能会再走一步并“展开”循环,在这种情况下,可能会再删除一个寄存器。

于 2013-02-04T01:30:12.553 回答