誰もがゴトーを嫌うことを知っています。私のコードでは、私が考えて快適な理由で、彼らは効果的な解決策を提供します(つまり、私は答えとして「それをしない」を探していません、あなたの予約を理解し、私がそれらを使用している理由を理解していますとにかく)。
これまでは素晴らしいものでしたが、基本的にラベルへのポインタを保存し、後でそれらにアクセスできるように機能を拡張したいと思います。
このコードが機能する場合、必要な機能のタイプを表します。しかし、それは機能せず、30分間のグーグル検索では何も明らかにされていません。誰にもアイデアはありますか?
int main (void)
{
int i=1;
void* the_label_pointer;
the_label:
the_label_pointer = &the_label;
if( i-- )
goto *the_label_pointer;
return 0;
}
CおよびC++標準はこの機能をサポートしていません。ただし、GNU Compiler Collection(GCC)には、これを行うための非標準の拡張機能が含まれています この記事 で説明されています。本質的に、特別な演算子 "&&ラベルのアドレスを「void *」タイプとして報告します。詳細については、記事を参照してください。
追伸つまり、例では「&」の代わりに「&&」を使用するだけで、GCCで機能します。
P.P.S。言って欲しくないことは知っているが、とにかく言うよ...しないでください!!!
Setjmp/longjmpでも同様のことができます。
int main (void)
{
jmp_buf buf;
int i=1;
// this acts sort of like a dynamic label
setjmp(buf);
if( i-- )
// and this effectively does a goto to the dynamic label
longjmp(buf, 1);
return 0;
}
C99標準、§6.8.6によると、goto
の構文は次のとおりです。
後藤識別子;
したがって、ラベルのアドレスを取得できたとしても、gotoで使用することはできません。
goto
をswitch
と組み合わせることができます。これは、計算されたgoto
に似ており、同様の効果があります。
int foo() {
static int i=0;
return i++;
}
int main(void) {
enum {
skip=-1,
run,
jump,
scamper
} label = skip;
#define STATE(lbl) case lbl: puts(#lbl); break
computeGoto:
switch (label) {
case skip: break;
STATE(run);
STATE(jump);
STATE(scamper);
default:
printf("Unknown state: %d\n", label);
exit(0);
}
#undef STATE
label = foo();
goto computeGoto;
}
難読化されたCコンテスト以外にこれを使用する場合、私はあなたを追い詰めて傷つけます。
switch ... case
ステートメントは本質的に 計算済みgoto
です。どのように機能するかの良い例は、 Duff's Device として知られる奇妙なハックです:
send(to, from, count)
register short *to, *from;
register count;
{
register n=(count+7)/8;
switch(count%8){
case 0: do{ *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
}while(--n>0);
}
}
この手法を使用して任意の場所からgoto
を実行することはできませんが、変数に基づいて関数全体をswitch
ステートメントでラップし、その変数を設定する場所を示すことができますgo、およびgoto
switch文。
int main () {
int label = 0;
dispatch: switch (label) {
case 0:
label = some_computation();
goto dispatch;
case 1:
label = another_computation();
goto dispatch;
case 2:
return 0;
}
}
もちろん、これを頻繁に行う場合は、いくつかのマクロを作成してラップする必要があります。
この手法は、いくつかの便利なマクロとともに、 Cのコルーチン の実装にも使用できます。
非常に非常に古いバージョンのC言語(恐竜が地球を歩き回った時代を想像してください)、「Cリファレンスマニュアル」バージョン( document Dennis Ritchieによって書かれたもの)として知られ、正式にラベルを付けます「intの配列」型(奇妙な、しかし真)を持っていた。つまり、int *
変数
int *target;
ラベルのアドレスをその変数に割り当てます
target = label; /* where `label` is some label */
後で、その変数をgoto
ステートメントのオペランドとして使用できます
goto target; /* jumps to label `label` */
ただし、ANSI Cでは、この機能は廃止されました。標準の最新のCでは、ラベルのアドレスを取得できず、「parametrized」goto
を実行できません。この動作は、switch
ステートメント、関数へのポインター、および他のメソッドなどでシミュレートされることになっています。実際、「Cリファレンスマニュアル」自体でさえ、「ラベル変数は一般に悪い考えです。switchステートメントほとんど常に不要にします」( "14.4ラベル" を参照)。
私はその気持ちを知っているので、誰もがそれを行うべきではないと言っています。ただhasする必要があります。 In GNU Cは&&the_label;
を使用してラベルのアドレスを取得します。( https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values .html )推測した構文goto *ptr
上の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);
これは、gcc 4.5以降でコンパイルされ、clang 8.0以降しばらくしてasm goto
サポートを取得したばかりの最新のclangでコンパイルされます。 https://godbolt.org/z/BzhckE 。結果のasmはGCC9.1のようになり、i=i
/i--
の「ループ」を最適化し、the_label
afterjumpto
。したがって、Cソースのように、まだ1回だけ実行されます。
# 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 Assemblyマクロは、各プロセッサ専用に実装する必要があります(これは32ビットと64ビットの両方のx86で動作します)。
また、(asm goto
なしで)同じ関数の2つの異なるポイントでスタックの状態が同じであるという保証はないことに注意してください。そして、少なくともいくつかの最適化をオンにすると、コンパイラーは、ラベルの後のポイントに何らかの値を含むレジスターを想定する可能性があります。これらの種類のものは簡単にめちゃくちゃになり、コンパイラが予期しない狂気のたわごとをすることができます。コンパイルされたコードを必ず読んでください。
これらが、asm goto
が安全であるために、ジャンプする場所/ジャンプする場所をコンパイラーに知らせ、ジャンプと宛先の一貫したコード生成を取得する必要がある理由です。
ここで説明する機能(gccの&&を含む)は、CでForth言語インタープリターを実装するための理想的なものであることに注意してください。 Forthの内部インタープリターの動作は無視できないほど優れています。
関数ポインターとwhileループを使用します。他の人が修正を後悔しなければならないようなコードを作成しないでください。
ラベルのアドレスを何らかの形で外部から変更しようとしていると思います。関数ポインターが機能します。
#include <stdio.h>
int main(void) {
void *fns[3] = {&&one, &&two, &&three};
char p;
p = -1;
goto start; end: return 0;
start: p++;
goto *fns[p];
one: printf("hello ");
goto start;
two: printf("World. \n");
goto start;
three: goto end;
}
Cのラベルを使用して公式にサポートできるのは、goto
itだけです。お気づきのとおり、アドレスを取得したり、変数などに保存したりすることはできません。したがって、「それをしないでください」と言う代わりに、「あなたはそれをすることはできません」と言います。
別の解決策を見つける必要があるようです。パフォーマンスが重要な場合、おそらくアセンブリ言語ですか?
&&を使用して変数にラベルを割り当てることができます。変更したコードは次のとおりです。
int main (void)
{
int i=1;
void* the_label_pointer = &&the_label;
the_label:
if( i-- )
goto *the_label_pointer;
return 0;
}
これを読んでください: setjmp.h-Wikipedia 前述したように、変数にジャンプポイントを保存して後でジャンプできるsetjmp/longjmpで可能です。
関数へのポインターを使用して、Fortranのコンピューターgotoのようなことができます。
// global variables up here
void c1(){ // chunk of code
}
void c2(){ // chunk of code
}
void c3(){
// chunk of code
}
void (*goTo[3])(void) = {c1, c2, c3};
// then
int x = 0;
goTo[x++] ();
goTo[x++] ();
goTo[x++] ();
このスレッド によると、ラベルポイントは標準ではないため、ラベルポイントが機能するかどうかは、使用しているコンパイラによって異なります。