-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()/復元操作の数をどのように判断できますか? 私は用語を理解していないかもしれませんが、各状況で各変数が持つロード/ストアの数を決定するプロセスの説明は素晴らしいでしょう.abcw

お時間をいただきありがとうございます。

4

2 に答える 2

2

唯一の答えは「少なくともゼロ」です。

  1. 最新のコンパイラは、、、およびをa削除bcますfoo()。これらの変数は存在しないため、読み込まれたり保存されたりすることはありません。

  2. w変数が存在するか、ループを展開することで変数が削除される可能性があります。

  3. 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 はx86-64 で callee-save であるrbxおよびレジスタを保存します。rbpこれらのレジスタは一度退避され、一度復元されます。foo()never callsに対する GCC の出力は、直接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」(Application Binary Interface)と呼ばれる仕様に依存するレジスタとは何ですか - これはプロセッサフ​​ァミリごとに異なり、オペレーティングシステム間でも異なることがよくあります。しかし、基本的な原則は、コンパイラが生成するマシン コードは特定の期待に基づいて構築されているということです。どの関数も 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 + bab

int bar(int v) {
    printf(“v=%d\n”, v);
    return v;
  }

さて、巧妙なコンパイラがbarfoo にインライン化すると、次のようになります。

int foo() {
    int b = 2, w;

    for (w = 0; w<10; w++) {
      printf(“v=%d\n”, b);
      b += b;
    }
    return 12;
  }

現実的には、これは 2 つまたは 3 つのレジスタと、実行する必要があるものすべてで実行printfできます (それが何であるかはわかりません...)。コンパイラーは別のステップに進み、ループを「展開」する可能性があります。その場合、おそらくもう 1 つのレジスターを削除できます。

于 2013-02-04T01:30:12.553 に答える