62

我知道每个人都讨厌 gotos。在我的代码中,出于我考虑并感到满意的原因,它们提供了一个有效的解决方案(即我不是在寻找“不要那样做”作为答案,我理解您的保留,并理解我为什么使用它们反正)。

到目前为止,它们都很棒,但我想以一种方式扩展功能,要求我基本上能够存储指向标签的指针,然后再访问它们。

如果此代码有效,它将代表我需要的功能类型。但它不起作用,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 编译器集合 (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 ) 你猜到的语法void*实际上是 GNU C 使用的。

或者如果你出于某种原因想使用内联汇编,这里是如何使用GNU Casm 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 一起编译,该 clangasm goto在 clang 8.0 之后的一段时间内得到了支持。 https://godbolt.org/z/BzhckE。对于 GCC9.1,生成的 asm 看起来像这样,它优化了i=i/的“循环” i--只是把. 所以它仍然只运行一次,就像在 C 源代码中一样。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)不能保证堆栈的状态在同一个函数的两个不同点是相同的。并且至少在打开一些优化的情况下,编译器可能会假设一些寄存器在标签之后的点包含一些值。这类事情很容易搞砸,然后做编译器没想到的疯狂事情。请务必校对已编译的代码。

这就是为什么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,这就像一个 computed 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 语言版本中(想想恐龙在地球上漫游的时间),被称为“C 参考手册”版本(指的是丹尼斯·里奇(Dennis Ritchie)编写的文档),标签的正式类型为“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使用此技术从任意位置执行 a,但您可以将整个函数包装在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 - Wikipedia如前所述,可以使用 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 回答