誰もがそれをすべきではないと言う気持ちを私は知っています。それはただ行わなければなりません。GNU C では&&the_label;
、ラベルのアドレスを取得するために使用します。( https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.htmlgoto *ptr
) aで推測した構文void*
は、実際には GNU C が使用するものです。
または、何らかの理由でインライン アセンブリを使用する場合は、GNU Cで行う方法を次に示します。asm goto
// unsafe: this needs to use asm goto so the compiler knows
// execution might not come out the other side
#define unsafe_jumpto(a) asm("jmp *%0"::"r"(a):)
// target pointer, possible targets
#define jumpto(a, ...) asm goto("jmp *%0" : : "r"(a) : : __VA_ARGS__)
int main (void)
{
int i=1;
void* the_label_pointer;
the_label:
the_label_pointer = &&the_label;
label2:
if( i-- )
jumpto(the_label_pointer, the_label, label2, label3);
label3:
return 0;
}
ラベルのリストには、 のすべての可能な値を含める必要がありますthe_label_pointer
。
マクロ展開は次のようになります
asm goto("jmp *%0" : : "ri"(the_label_pointer) : : the_label, label2, label3);
asm goto
これは gcc 4.5 以降でコンパイルされ、clang 8.0 の後 にサポートされたばかりの最新の clang でコンパイルされます。https://godbolt.org/z/BzhckE . 結果として得られる asm は、GCC9.1 では次のようになります。これは、/ の「ループ」を最適i=i
化し、. したがって、C ソースの場合と同様に、1 回だけ実行されます。i--
the_label
jumpto
# gcc9.1 -O3 -fpie
main:
leaq .L2(%rip), %rax # ptr = &&label
jmp *%rax # from inline asm
.L2:
xorl %eax, %eax # return 0
ret
しかし、clang はその最適化を行わず、まだループがあります。
# clang -O3 -fpie
main:
movl $1, %eax
leaq .Ltmp1(%rip), %rcx
.Ltmp1: # Block address taken
subl $1, %eax
jb .LBB0_4 # jump over the JMP if i was < 1 (unsigned) before SUB. i.e. skip the backwards jump if i wrapped
jmpq *%rcx # from inline asm
.LBB0_4:
xorl %eax, %eax # return 0
retq
ラベル アドレス演算子 && は gcc でのみ機能します。そして明らかに、jumpto アセンブリ マクロは、プロセッサごとに特別に実装する必要があります (これは 32 ビットと 64 ビットの両方の x86 で動作します)。
また、(なしasm goto
では) 同じ関数内の 2 つの異なるポイントでスタックの状態が同じであるという保証がないことに注意してください。そして、少なくともいくつかの最適化がオンになっていると、コンパイラーは、いくつかのレジスターがラベルの後のポイントに何らかの値を含むと想定する可能性があります。この種のことは簡単に台無しになり、コンパイラが予期しないクレイジーなことをする可能性があります。コンパイルされたコードを必ず校正して読んでください。
これらが、asm goto
ジャンプする/ジャンプする可能性のある場所をコンパイラに知らせ、ジャンプと宛先の一貫したコード生成を取得することにより、安全にする必要がある理由です。