62

私は誰もが後藤を嫌っていることを知っています。私のコードでは、私が検討し、満足している理由から、それらは効果的な解決策を提供します(つまり、答えとして「それをしないでください」を探しているわけではありません。あなたの予約を理解し、なぜそれらを使用しているのかを理解していますとりあえず)。

これまでのところ、それらは素晴らしいものでしたが、本質的にラベルへのポインターを保存して後でそれらに移動できるようにする必要があるような方法で機能を拡張したいと考えています。

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

14 に答える 14

73

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

PS つまり、例では「&」の代わりに「&&」を使用するだけで、GCC で動作します。
PPS あなたが私に言われたくないのはわかっていますが、とにかく言います... そんなことはしないでください!!!

于 2009-11-22T06:21:01.413 に答える
20

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

于 2013-07-22T20:24:29.913 に答える
16

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;
}
于 2009-11-22T06:20:54.363 に答える
13

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

    識別子に移動 します 

そのため、ラベルのアドレスを取得できたとしても、goto では使用できませんでした。

同様の効果を得るために、 agotoと a を組み合わせることができます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 コンテスト以外に使用する場合は、追い詰めて傷つけます。

于 2009-11-22T06:32:35.040 に答える
13

「C リファレンス マニュアル」バージョン (Dennis Ritchie によって書かれたドキュメントを参照) として知られる、非常に古いバージョンの C 言語 (恐竜が地球を歩き回った時代を考えてみてください) では、ラベルは正式に「array of int」型を持っていました。 (奇妙ですが、本当です)、int *変数を宣言できることを意味します

int *target;

ラベルのアドレスをその変数に割り当てます

target = label; /* where `label` is some label */

goto後でその変数をステートメントのオペランドとして使用できます

goto target; /* jumps to label `label` */

ただし、ANSI C では、この機能は廃止されました。標準の最新の C では、ラベルのアドレスを取得することはできず、「パラメーター化」を行うことはできませんgoto。この動作は、ステートメント、関数へのポインター、およびその他のメソッドなどでシミュレートされることになっていswitchます。実際、「C リファレンスマニュアル」自体でさえ、「ラベル変数は一般的に悪い考えです。switch ステートメントにより、ほとんどの場合不要になります」と述べられています。 ( 「14.4 ラベル」を参照)。

于 2009-11-22T06:33:41.103 に答える
10

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、変数に基づくステートメントで関数全体をラップし、移動先を示すその変数と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 でコルーチンを実装するためにも使用できます。

于 2009-11-22T06:30:05.540 に答える
7

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

于 2013-04-23T20:21:34.833 に答える
4

関数ポインターと while ループを使用します。他の誰かがあなたのために修正したことを後悔しなければならないコードを作成しないでください。

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

于 2009-11-22T06:31:28.533 に答える
4
#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;
}
于 2016-03-31T01:44:34.177 に答える
3

C でラベルを使用して公式にサポートされている唯一のことは、gotoそれです。お気づきのように、そのアドレスを取得したり、変数などに格納したりすることはできません。だから、「そんなことはやめなさい」と言う代わりに、「そんなことはできない」と言いたいのです。

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

于 2009-11-22T06:20:46.143 に答える
1

これを読んでください: setjmp.h - ウィキペディア前に述べたように、 setjmp/longjmp を使用すると、変数にジャンプポイントを格納して後でジャンプして戻すことができます。

于 2009-11-22T06:28:38.403 に答える
1

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


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

  the_label:


  if( i-- )
    goto *the_label_pointer;


  return 0;
}
于 2014-07-29T07:45:12.407 に答える
0

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

于 2009-11-22T06:21:13.363 に答える
0

関数へのポインターを使用して、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++] ();
于 2015-11-21T16:55:27.800 に答える