4

GCC コンパイル (を使用gcc --omit-frame-pointer -s):

    int the_answer() { return 42; }

の中へ

            .Text
    .globl _the_answer
    _the_answer:
        subl    $12, %esp
        movl    $42, %eax
        addl    $12, %esp
        ret
       .subsections_via_symbols

ここで '$12' 定数は何をしているのですか? また、'%esp' レジスタは何ですか?

4

4 に答える 4

8

簡単な答え: フレームをスタックします。

長い答え: 関数を呼び出すと、コンパイラはスタック ポインターを操作して、関数変数などのローカル データを許可します。コードが変更されているのでesp、スタック ポインターがここで起こっていると思います。GCC は、実際には必要のない場所でこれを最適化するのに十分賢いと思っていたでしょうが、最適化を使用していない可能性があります。

于 2009-02-01T00:12:21.197 に答える
4
_the_answer:
    subl    $12, %esp
    movl    $42, %eax
    addl    $12, %esp
    ret

最初の subl は、スタック ポインターをデクリメントして、関数で使用される可能性のある変数のためのスペースを作ります。たとえば、1 つのスロットをフレーム ポインタに使用し、別のスロットをリターン アドレスを保持するために使用できます。フレームポインターを省略すべきだと言いました。これは通常、フレームポインタを保存/復元するためのロード/ストアを省略していることを意味します。しかし、多くの場合、コードはまだメモリを予約しています。その理由は、スタックを分析するコードがはるかに簡単になるからです。スタックのオフセットに最小限の幅を与えるのは簡単なので、フレーム ポインターの保存を省略した場合でも、いつでも FP+0x12 にアクセスして最初のローカル変数スロットを取得できることがわかります。

まあ、eax私の知る限り、x86では呼び出し元への戻り値を処理するために使用されます。最後の addl は、関数用に以前に作成されたフレームを破棄するだけです。

関数の最初と最後に命令を生成するコードを関数の「エピローグ」「プロローグ」と呼びます。これは、GCC で関数のプロローグを作成する必要がある場合に、私のポートが行うことです (可能な限り高速で用途が広いことを意図した実際のポートでは、より複雑になります)。

void eco32_prologue(void) {
    int i, j;
    /* reserve space for all callee saved registers, and 2 additional ones:
     * for the frame pointer and return address */
    int regs_saved = registers_to_be_saved() + 2;
    int stackptr_off = (regs_saved * 4 + get_frame_size());

    /* decrement the stack pointer */
    emit_move_insn(stack_pointer_rtx, 
                   gen_rtx_MINUS(SImode, stack_pointer_rtx, 
                                 GEN_INT(stackptr_off)));

    /* save return adress, if we need to */
    if(eco32_ra_ever_killed()) {
        /* note: reg 31 is return address register */
        emit_move_insn(gen_rtx_MEM(SImode, 
                           plus_constant(stack_pointer_rtx, 
                                         -4 + stackptr_off)),  
                       gen_rtx_REG(SImode, 31));
    }

    /* save the frame pointer, if it is needed */
    if(frame_pointer_needed) {
        emit_move_insn(gen_rtx_MEM(SImode, 
                           plus_constant(stack_pointer_rtx, 
                                         -8 + stackptr_off)), 
                       hard_frame_pointer_rtx);
    }

    /* save callee save registers */
    for(i=0, j=3; i<FIRST_PSEUDO_REGISTER; i++) {
        /* if we ever use the register, and if it's not used in calls
         * (would be saved already) and it's not a special register */
        if(df_regs_ever_live_p(i) && 
           !call_used_regs[i] && !fixed_regs[i]) {
            emit_move_insn(gen_rtx_MEM(SImode, 
                               plus_constant(stack_pointer_rtx, 
                                             -4 * j + stackptr_off)), 
                           gen_rtx_REG(SImode, i));
            j++;
        }
    }

    /* set the new frame pointer, if it is needed now */
    if(frame_pointer_needed) {
        emit_move_insn(hard_frame_pointer_rtx, 
                       plus_constant(stack_pointer_rtx, stackptr_off));
    }
}

主に GCC に例外処理にとって重要な命令 (つまり、フレーム ポインタが格納されている場所など) を伝えるなど、他の問題を処理するコードをいくつか省略しました。さて、呼び出し先保存レジスタは、呼び出し元が呼び出し前に保存する必要のないものです。呼び出された関数は、必要に応じてそれらを保存/復元します。最初の行でわかるように、常にリターン アドレスとフレーム ポインターにスペースを割り当てます。そのスペースはほんの数バイトであり、問​​題にはなりません。ただし、ストア/ロードは必要な場合にのみ生成します。最後に、「ハード」フレーム ポインタが「実際の」フレーム ポインタ レジスタであることに注意してください。これは、gcc の内部的な理由から必要です。「frame_pointer_needed」フラグは、できないときはいつでもGCCによって設定されますフレームポインターの保存を省略します。場合によっては、alloca(スタックポインタを動的に変更する) を使用する場合など、保存する必要があります。GCC はそのすべてを気にします。そのコードを書いてからしばらく経っていることに注意してください。したがって、上記で追加した追加のコメントがすべて間違っていないことを願っています:)

于 2009-02-01T00:18:45.910 に答える
3

スタックの整列。関数のエントリでespは、リターン アドレスが によってプッシュされたため、-4 mod 16callです。12 を引くと再調整されます。mmx/sse/などを使用しているマルチメディア コードを除いて、x86 でスタックを 16 バイトに揃える正当な理由はありませんが、3.x 時代のどこかで、gcc 開発者はとにかくスタックを常に揃えておくべきだと判断しました。プロローグ/エピローグのオーバーヘッド、スタックサイズの増加、およびその結果、いくつかの特別な目的のためのすべてのプログラムでのキャッシュスラッシングの増加 (偶然にも私の関心のある分野の一部ですが、それでも不公平で悪いと思います)決断)。

通常、任意の最適化レベルを有効にすると、gcc はリーフ関数 (関数呼び出しを行わない関数) のスタック アラインメントの無駄なプロローグ/エピローグを削除しますが、呼び出しを開始するとすぐに戻ってきます。

で問題を解決することもできます-mpreferred-stack-boundary=2

于 2011-03-30T14:20:22.877 に答える
1

GCC 4.3.2 を使用すると、関数に対して次のようになります。

the_answer:
movl    $42, %eax
ret

...次のコマンドラインを使用して、周囲のジャンクを追加します。echo 'int the_answer() { return 42; }' | gcc --omit-frame-pointer -S -x c -o - -

どのバージョンを使用していますか?

于 2009-02-01T00:15:47.757 に答える