10

GCC では、( のように) ラベルのアドレスを取得し、そこにジャンプする ( ) ことにより、計算された gotoを使用できます。GCCのマニュアルには、関数内のどこからでもこのアドレスにジャンプできると書かれていますが、別の関数からこのアドレスにジャンプすることは未定義です。void *addr = &&labeljump *addr

コードにジャンプすると、レジスタの値について何も想定できないため、おそらくメモリからそれらをリロードします。ただし、スタック ポインターの値も必ずしも定義されているとは限りません。たとえば、追加の変数を宣言するネストされたスコープからジャンプする可能性があります。

問題は、GCC がスタック ポインターの値を正しい値に設定する方法です (高すぎるか低すぎる可能性があります)。そして、これはどのように相互作用し-fomit-frame-pointerますか(もしそうなら)?

最後に、余分なポイントとして、どこからラベルにジャンプできるかについての実際の制約は何ですか? たとえば、おそらく割り込みハンドラから実行できます。

4

3 に答える 3

12

一般に、アドレスが取得されるラベルを持つ関数がある場合、gcc は、関数内の任意の間接 goto からそのラベルにジャンプできるようにする必要があります。問題 (すべてがフレーム ポインターからインデックス化される)、またはスタック ポインターがそれらすべてで一貫していること。一般に、これは、関数の開始時に一定量のスタック スペースを割り当て、その後スタック ポインターに決して触れないことを意味します。したがって、変数を含む内部スコープがある場合、スペースは内部スコープではなく、関数の開始時に割り当てられ、関数の終了時に解放されます。コンストラクタとデストラクタ (存在する場合) のみを内部スコープに関連付ける必要があります。

ラベルへのジャンプに関する唯一の制約は、あなたが指摘したものです-ラベルを含む関数内からのみ実行できます。他の関数や割り込みハンドラなどの他のスタック フレームからではありません。

編集

あるスタック フレームから別のスタック フレームにジャンプできるようにするには、setjmp/longjmp などを使用してスタックを巻き戻す必要があります。これを間接 goto と組み合わせることができます。次のようなものです。

if (target = (void *)setjmp(jmpbuf)) goto *target;

そうすればlongjmp(jmpbuf, label_address);、呼び出された関数から呼び出してスタックを巻き戻し、ラベルにジャンプできます。割り込みハンドラから機能する限りsetjmp/longjmp、これは割り込みハンドラからも機能します。にも依存しますがsizeof(int) == sizeof(void *)、常にそうとは限りません。

于 2012-09-12T16:39:32.633 に答える
2

gotoが計算されるという事実が、それがローカル変数に与える影響に追加されるとは思いません。ローカル変数の存続期間は、宣言またはそれを超えて宣言を入力することから始まり、変数のスコープに到達できないときに終了します。これには、すべての異なる種類の制御フロー、特にgotoとが含まれlongjmpます。したがって、そのような変数はすべて、それらが宣言されている関数から戻るまで、常に安全です。

Cのラベルは、エングロビン関数全体に表示されるため、これが計算されたものである場合、大きな違いはありませんgotogoto計算されたステートメントを多かれ少なかれ複雑なステートメントにいつでも置き換えることができますswitch

ローカル変数に関するこのルールの注目すべき例外の1つは、可変長配列VLAです。それら必然的にスタックポインタを変更するので、それらは異なるルールを持っています。宣言のブロックを終了するとすぐに存続期間が終了し、可変的に変更されたタイプの宣言後にスコープに入ることが許可されませんgotolongjmp

于 2012-09-12T19:26:36.777 に答える
0

関数プロローグでは、-fomit-frame-pointer を使用しても、スタックの現在の位置が呼び出し先の保存済みレジスタに保存されます。

以下の例では、sp+4 が r7 に格納され、エピローグ (LBB0_3) で復元されます (r7+4 -> r4; r4 -> sp)。このため、関数内のどこにでもジャンプでき、関数内の任意の時点でスタックを拡大でき、スタックを台無しにすることはありません。(jump *addr を介して) 関数から飛び出すと、このエピローグをスキップして、スタックを完全に台無しにしてしまいます。

スタックにメモリを動的に割り当てる alloca も使用する短い例:

clang -arch armv7 -fomit-frame-pointer -c -S -O0 -o - stack.c

#include <alloca.h>

int foo(int sz, int jmp) {
    char *buf = alloca(sz);
    int rval = 0;

    if( jmp ) {
        rval = 1;
        goto done;
    }

    volatile int s = 2;

    rval = s * 5;

done:

    return rval;
}

と分解:

_foo:
@ BB#0:
    push    {r4, r7, lr}
    add r7, sp, #4
    sub sp, #20
    movs    r2, #0
    movt    r2, #0
    str r0, [r7, #-8]
    str r1, [r7, #-12]
    ldr r0, [r7, #-8]
    adds    r0, #3
    bic r0, r0, #3
    mov r1, sp
    subs    r0, r1, r0
    mov sp, r0
    str r0, [r7, #-16]
    str r2, [r7, #-20]
    ldr r0, [r7, #-12]
    cmp r0, #0
    beq LBB0_2
@ BB#1:
    movs    r0, #1
    movt    r0, #0
    str r0, [r7, #-20]
    b   LBB0_3
LBB0_2:
    movs    r0, #2
    movt    r0, #0
    str r0, [r7, #-24]
    ldr r0, [r7, #-24]
    movs    r1, #5
    movt    r1, #0
    muls    r0, r1, r0
    str r0, [r7, #-20]
LBB0_3:
    ldr r0, [r7, #-20]
    subs    r4, r7, #4
    mov sp, r4
    pop {r4, r7, pc}
于 2012-09-12T16:43:38.173 に答える