48

x86を少し手に入れようとしています。gcc -S -O0 を使用して 64 ビット Mac でコンパイルしています。

C のコード:

printf("%d", 1);

出力:

movl    $1, %esi
leaq    LC0(%rip), %rdi
movl    $0, %eax        ; WHY?
call    _printf

「printf」が呼び出される前に %eax が 0 にクリアされる理由がわかりません。printf印刷された文字数を私の最善の推測に戻すため、%eax準備のためにゼロに設定されますが、準備を整える責任がprintfあると想定していました。また、対照的に、printf自分の関数を呼び出すと、準備する必要はありません。では、なぜ扱いが異なるのだろうか。int testproc(int p1)gcc%eaxgccprintftestproc

4

3 に答える 3

54

x86_64 ABI では、関数に可変引数がある場合AL(の一部EAX) は、その関数への引数を保持するために使用されるベクトル レジスタの数を保持することが期待されます。

あなたの例では:

printf("%d", 1);

には整数の引数があるため、ベクトル レジスタは必要ないため、AL0 に設定されます。

一方、例を次のように変更すると:

printf("%f", 1.0f);

次に、浮動小数点リテラルがベクトル レジスタに格納され、それに応じて次のALように設定され1ます。

movsd   LC1(%rip), %xmm0
leaq    LC0(%rip), %rdi
movl    $1, %eax
call    _printf

予想通り:

printf("%f %f", 1.0f, 2.0f);

2 つの浮動小数点引数があるため、コンパイラは次のように設定さALれます。2

movsd   LC0(%rip), %xmm0
movapd  %xmm0, %xmm1
movsd   LC2(%rip), %xmm0
leaq    LC1(%rip), %rdi
movl    $2, %eax
call    _printf

他の質問について:

puts%eaxまた、単一のポインターしか使用しませんが、呼び出しの直前にゼロに設定されています。どうしてこれなの?

すべきではありません。例えば:

#include <stdio.h>

void test(void) {
    puts("foo");
}

でコンパイルするとgcc -c -O0 -S、次のように出力されます。

pushq   %rbp
movq    %rsp, %rbp
leaq    LC0(%rip), %rdi
call    _puts
leave
ret

ゼロに%eaxはなりません。ただし、削除する#include <stdio.h>と、結果のアセンブリは%eaxを呼び出す直前にゼロになりputs()ます。

pushq   %rbp
movq    %rsp, %rbp
leaq    LC0(%rip), %rdi
movl    $0, %eax
call    _puts
leave
ret

その理由は、2 番目の質問に関連しています。

これは、自分の void proc() 関数の呼び出しの前にも発生します (-O2 が設定されていても) が、void proc2(int param) 関数を呼び出したときにゼロにはなりません。

コンパイラが関数の宣言を認識しない場合、コンパイラはそのパラメータについて何も仮定せず、関数は可変引数を十分に受け入れることができます。空のパラメーター リストを指定した場合も同じことが当てはまります (これはすべきではなく、ISO/IEC によって廃止予定の C 機能としてマークされています)。コンパイラーは関数パラメーターに関する十分な情報を持っていない%eaxため、関数が可変引数を持つように定義されている可能性があるため、関数を呼び出す前にゼロに設定します。

例えば:

#include <stdio.h>

void function() {
    puts("foo");
}

void test(void) {
    function();
}

wherefunction()には空のパラメーター リストがあり、結果は次のようになります。

pushq   %rbp
movq    %rsp, %rbp
movl    $0, %eax
call    _function
leave
ret

voidただし、次のように、関数がパラメーターを受け入れない場合を指定する推奨プラクティスに従う場合:

#include <stdio.h>

void function(void) {
    puts("foo");
}

void test(void) {
    function();
}

次に、コンパイラfunction()は が引数を受け入れないことを認識します。特に、可変引数を受け入れないため、%eaxその関数を呼び出す前にクリアしません。

pushq   %rbp
movq    %rsp, %rbp
call    _function
leave
ret
于 2011-06-02T09:45:03.570 に答える
41

x86_64 System V ABIレジスタの使用表から:

  • %rax       一時登録; with variable arguments は、使用されるベクトル レジスタの数に関する情報を渡します。1回目のリターンレジスター...

printfは可変引数の関数であり、使用されるベクトル レジスタの数は 0 です。

呼び出し元は の上位バイトにガベージを残すことが許可されているため、 はprintfのみをチェックする必要があることに注意してください。(それでも、ゼロにする最も効率的な方法です)%al%raxxor %eax,%eax%al

詳細については、この Q&Aタグ wiki を参照してください。上記のリンクが古い場合は、最新の ABI リンクを参照してください。

于 2011-06-02T09:36:33.860 に答える