是否可以将标签的地址存储在变量中并使用goto跳转到它?

时间:2009-11-22 06:17:11

标签: c pointers label goto memory-address

我知道每个人都讨厌搞砸。在我的代码中,由于我考虑过并且很满意的原因,他们提供了一个有效的解决方案(即我不是在寻找“不要那样做”作为答案,我理解你的预订,并理解我为什么使用它们无论如何)。

到目前为止,它们一直很精彩,但我想以这样的方式扩展功能,这要求我基本上能够存储指向标签的指针,然后再转到它们。

如果此代码有效,它将代表我需要的功能类型。但它不起作用,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;
}

14 个答案:

答案 0 :(得分:58)

C和C ++标准不支持此功能。但是,GNU编译器集合(GCC)包含一个非标准扩展,用于执行此操作,如this article中所述。基本上,他们增加了一个特殊的运营商“&&&”报告标签的地址为“void *”类型。有关详细信息,请参阅文章。

P.S。换句话说,只需使用“&&”而不是“&”在你的例子中,它将适用于GCC P.P.S.我知道你不想让我这么说,但无论如何我都会说,......不要那样做!

答案 1 :(得分: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;
}

答案 2 :(得分:12)

根据C99标准§6.8.6,goto的语法是:

    goto identifier ;

因此,即使您可以获取标签的地址,也不能将其与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比赛以外的任何其他内容,我会追捕并伤害您。

答案 3 :(得分:9)

switch ... case语句基本上是computed 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转换语句。

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

当然,如果你做了很多,你想写一些宏来包装它。

此技术以及一些便利宏甚至可用于实现coroutines in C

答案 4 :(得分:9)

在非常非常旧的C语言版本中(想想恐龙漫游地球的时间),被称为“C参考手册”版本(指的是Dennis Ritchie编写的document),正式标注有类型“数组的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 Labels")。

答案 5 :(得分:8)

我知道这种感觉然后每个人都说不应该这样做;只需要 即可完成。这是如何做到的:

#define jumpto(a) asm("jmp *%0"::"r"(a):)

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

  the_label:

  the_label_pointer = &&the_label;

  if( i-- )
    jumpto(the_label_pointer);

  return 0;
}

标签解除引用运营商&&只适用于gcc。很明显,跳转组件宏需要专门为每个处理器实现(这个处理器适用于32位和64位x86)。还要记住,不能保证堆栈状态在同一函数中的两个不同点处是相同的。至少在开启一些优化的情况下,编译器可能会假定某些寄存器在标签后的某个点包含某些值。这些事情很容易搞砸,然后做编译器没想到的疯狂的事情。务必证明阅读已编译的代码。

答案 6 :(得分:5)

我会注意到这里所描述的功能(包括&& in gcc)是用于在C中实现Forth语言解释器的IDEAL。它将所有“不要那样做”的论点从水中吹出 - 适合在这个功能和Forth的内部翻译工作方式之间太好了,不容忽视。

答案 7 :(得分:4)

使用函数指针和while循环。不要制作一段代码,其他人将不得不后悔为你修理。

我认为你试图以某种方式在外部改变标签的地址。函数指针将起作用。

答案 8 :(得分:3)

你可以用C中的标签做的唯一官方支持的事情是goto它。正如您所注意到的,您无法获取它的地址或将其存储在变量或其他任何内容中。因此,我不会说“不要那样做”,而是说“你做不到”。

看起来你必须找到一个不同的解决方案。也许汇编语言,如果这是性能关键的?

答案 9 :(得分:2)

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

答案 10 :(得分:1)

阅读本文:setjmp.h - Wikipedia如前所述,使用setjmp / longjmp可以将变量存储在变量中并稍后跳回。

答案 11 :(得分:0)

根据this thread,标签点不是标准,因此它们是否起作用取决于您正在使用的编译器。

答案 12 :(得分:0)

您可以使用&amp;&amp;来为变量指定标签。 这是您修改后的代码。


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

  the_label:


  if( i-- )
    goto *the_label_pointer;


  return 0;
}

答案 13 :(得分:0)

你可以使用指向函数的指针来执行类似Fortran计算机的操作。

  

//全局变量在这里

     

void c1(){//代码块

     

}

     

void c2(){//代码块

     

}

     

void c3(){   //代码块

     

}

     

void(* goTo [3])(void)= {c1,c2,c3};

     

//然后
  int x = 0;

     

goTo [x ++]();

     

goTo [x ++]();

     

goTo [x ++]();

相关问题