web-dev-qa-db-ja.com

ラベルのアドレスを変数に保存し、gotoを使用してそれにジャンプすることは可能ですか?

誰もがゴトーを嫌うことを知っています。私のコードでは、私が考えて快適な理由で、彼らは効果的な解決策を提供します(つまり、私は答えとして「それをしない」を探していません、あなたの予約を理解し、私がそれらを使用している理由を理解していますとにかく)。

これまでは素晴らしいものでしたが、基本的にラベルへのポインタを保存し、後でそれらにアクセスできるように機能を拡張したいと思います。

このコードが機能する場合、必要な機能のタイプを表します。しかし、それは機能せず、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;
}
53
Joshua Cheek

CおよびC++標準はこの機能をサポートしていません。ただし、GNU Compiler Collection(GCC)には、これを行うための非標準の拡張機能が含まれています この記事 で説明されています。本質的に、特別な演算子 "&&ラベルのアドレスを「void *」タイプとして報告します。詳細については、記事を参照してください。

追伸つまり、例では「&」の代わりに「&&」を使用するだけで、GCCで機能します。
P.P.S。言って欲しくないことは知っているが、とにかく言うよ...しないでください!!!

60

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;
}
15

C99標準、§6.8.6によると、gotoの構文は次のとおりです。

後藤識別子;

したがって、ラベルのアドレスを取得できたとしても、gotoで使用することはできません。

gotoswitchと組み合わせることができます。これは、計算された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コンテスト以外にこれを使用する場合、私はあなたを追い詰めて傷つけます。

12
outis

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のコルーチン の実装にも使用できます。

10
Brian Campbell

非常に非常に古いバージョンの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ラベル" を参照)。

10
AnT

私はその気持ちを知っているので、誰もがそれを行うべきではないと言っています。ただ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_labelafterjumpto。したがって、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が安全であるために、ジャンプする場所/ジャンプする場所をコンパイラーに知らせ、ジャンプと宛先の一貫したコード生成を取得する必要がある理由です。

10
Fabel

ここで説明する機能(gccの&&を含む)は、CでForth言語インタープリターを実装するための理想的なものであることに注意してください。 Forthの内部インタープリターの動作は無視できないほど優れています。

5
Kip Ingram

関数ポインターとwhileループを使用します。他の人が修正を後悔しなければならないようなコードを作成しないでください。

ラベルのアドレスを何らかの形で外部から変更しようとしていると思います。関数ポインターが機能します。

4
user208608
#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;
}
4
RUE_MOHR

Cのラベルを使用して公式にサポートできるのは、goto itだけです。お気づきのとおり、アドレスを取得したり、変数などに保存したりすることはできません。したがって、「それをしないでください」と言う代わりに、「あなたはそれをすることはできません」と言います。

別の解決策を見つける必要があるようです。パフォーマンスが重要な場合、おそらくアセンブリ言語ですか?

3
Greg Hewgill

&&を使用して変数にラベルを割り当てることができます。変更したコードは次のとおりです。


int main (void)
{
  int i=1;
  void* the_label_pointer = &&the_label;

  the_label:


  if( i-- )
    goto *the_label_pointer;


  return 0;
}
1
Mayank

これを読んでください: setjmp.h-Wikipedia 前述したように、変数にジャンプポイントを保存して後でジャンプできるsetjmp/longjmpで可能です。

1
icefex

関数へのポインターを使用して、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++] ();
0
QuentinUK

このスレッド によると、ラベルポイントは標準ではないため、ラベルポイントが機能するかどうかは、使用しているコンパイラによって異なります。

0
Kaleb Brasee