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

Jos*_*eek 53 c label pointers 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;
}
Run Code Online (Sandbox Code Playgroud)

Mic*_*yan 60

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

PS换句话说,在你的例子中只使用"&&"而不是"&",它将适用于GCC.
PPS我知道你不想让我说出来,但无论如何我都会说,......不要那样做!

  • 转到标签地址非常适合编写解释器. (14认同)
  • 我想知道为什么他们在世界上使用双与号(逻辑和),而现有的 get-the-address-of-an-identifier '&' 最有意义。我能想到的唯一原因是标签标识符似乎作为变量标识符存在于并行但独立的范围中,因此如果两者的名称相同,则获取标签与变量的地址之间可能存在歧义(可以说这只是在同一个函数中声明 int foo 和 foo: 的不好做法)。如果这进入标准,我希望'&',而不是'&&'。 (3认同)
  • 完全做到。如果您正在编写一个解释器循环,那就是这样做的方法。 (2认同)

R S*_*hko 15

你可以用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;
}
Run Code Online (Sandbox Code Playgroud)

  • 只是注意setjmp/longjmp可能很慢,因为它们比程序计数器更能保存和恢复. (11认同)
  • 这是行不通的:根据“i”是否存储在寄存器中或*在堆栈上*,其原始值(“1”)将由“longjmp()”恢复或不恢复,因此可能导致无限循环。 (2认同)

out*_*tis 12

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

    goto identifier ;

因此,即使您可以获取标签的地址,也不能将其与goto一起使用.

您可以将a goto与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;
}
Run Code Online (Sandbox Code Playgroud)

如果您将此用于除了混淆的C比赛之外的其他任何事情,我会追捕你并伤害你.

  • 为罪恶思考而使用+1,并使用超出职责范围的宏. (3认同)

Bri*_*ell 10

switch ... case陈述基本上是计算的goto.它是如何工作的一个很好的例子是奇怪的黑客被称为Duff的设备:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

您无法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;
  }
}
Run Code Online (Sandbox Code Playgroud)

当然,如果你经常这么做,你会想要写一些宏来包装它.

这种技术以及一些方便的宏甚至可以用来在C中实现协同程序.

  • @SamHocevar当然,你不能依赖它的实现方式(虽然像这样的情况,你使用的是一个没有小孔的小范围,更有可能以这种方式进行优化).但是,尽管是否应用了优化,它在语义上等同于`goto`,它由于传入的行为而以您传入的值为条件.行为是相同的,实现只影响性能.它似乎是OP问题的一个相关答案,因为他正在寻找使用`goto`s构建一个状态机,因为`switch`可以解决这个问题. (5认同)
  • 无法保证`switch/case`将被实现为计算的`goto`.通常它被编译为好像它是一系列`if/else if/else if/...`并且生成的程序集将测试每个值而不是计算单个地址跳转到. (2认同)

AnT*_*AnT 10

在非常非常古老的C语言版本中(想想恐龙漫游地球的时间),被称为"C参考手册"版本(指的是Dennis Ritchie编写的文档),标签正式有类型"int of int" (奇怪,但却是真的),这意味着你可以声明一个int *变量

int *target;
Run Code Online (Sandbox Code Playgroud)

并将label的地址分配给该变量

target = label; /* where `label` is some label */
Run Code Online (Sandbox Code Playgroud)

稍后您可以将该变量用作goto语句的操作数

goto target; /* jumps to label `label` */
Run Code Online (Sandbox Code Playgroud)

但是,在ANSI C中,此功能被抛弃了.在标准的现代C中,您无法获取标签的地址,也无法进行"参数化" goto.这种行为应该用switch语句,指向函数的指针和其他方法等来模拟.实际上,即使是"C参考手册"本身也说"标签变量通常是一个坏主意; switch语句几乎总是不必要" (参见"14.4标签").


Fab*_*bel 10

我知道这种感觉,然后每个人都说不应该这样做; 它必须要完成.这是怎么做的:

// 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;
}
Run Code Online (Sandbox Code Playgroud)

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


小智 5

我将注意到,这里描述的功能(包括&&在gcc中)是用于在C中实现Forth语言解释器的IDEAL.这使得所有"不要那样做"的论点脱离了水 - 这种功能与方式之间的契合Forth的内部翻译工作太好了,不容忽视.


Gre*_*ill 3

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

看来您必须找到不同的解决方案。也许是汇编语言,如果这对性能至关重要的话?

  • +1只是在汇编中完成,这就是我之前解决类似问题的方法。 (3认同)