2009-11-22 70 views
45

我知道每个人都讨厌gotos。在我的代码中,出于我已经考虑并且很舒服的原因,他们提供了一个有效的解决方案(即,我不是在寻找“不这样做”作为答案,我理解您的保留意见,并理解为什么我使用它们无论如何)。是否可以将标签的地址存储在变量中并使用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; 
} 
+0

你能解释一下为什么你需要将这些标签存储指针? – 2009-11-22 06:23:10

+3

我正在实现一个有限状态机,基于Remo.D在这篇文章中的回答http://stackoverflow.com/questions/132241/我的版本已经发展到比这更多地涉及,但这代表了基本结构体。到目前为止,它一直很有效,但我想通过在状态转换时设置的一些变量或通过回调或某物访问某些上下文来访问调用状态和当前状态。 – 2009-11-22 06:36:01

+1

重复http://stackoverflow.com/questions/938518/c-c-goto – qrdl 2009-11-22 07:52:52

回答

3

唯一正式支持的事情,你可以在C标签做的是goto它。正如你所注意到的,你不能把它的地址或存储在一个变量或其他任何东西中。所以,我不会说“不这样做”,而是说“你不能那样做”。

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

+3

+1只是在汇编中,这就是我以前解决类似的问题。 – mrduclaw 2009-11-22 06:22:36

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

只需谨慎一点,setjmp/longjmp可能会很慢,因为它们比程序计数器保存和恢复的要多得多。 – RickNZ 2009-11-22 07:00:32

56

C和C++标准不支持此功能。但是,GNU编译器集合(GCC)包含一个非标准扩展,如this article中所述。基本上,他们添加了一个特殊的运算符“& &”,它将标签的地址报告为类型“void *”。详情请参阅文章。

P.S.换句话说,在你的例子中,只使用“& &”而不是“&”,它可以在GCC上工作。
P.P.S.我知道你不想让我说出来,但无论如何我会说,不要这样做!

+8

+1为PPS! – 2009-11-22 06:57:48

+0

+1也适用于PPS!我会加我自己的:不要这样做! – 2009-11-22 08:11:17

+7

goto标签地址非常适合编写口译员。 – 2013-11-05 17:22:21

0

根据this thread,标签点不是一个标准,所以无论它们是否工作都取决于您使用的编译器。

1

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

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

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

这种技术,以及一些便利的宏,甚至可以用来实现coroutines in C

+1

不能保证'switch/case'将被实现为计算的'goto'。很多时候,它被编译为一系列“if/else if/else if/...”,并且生成的程序集将测试每个值,而不是计算要跳转到的单个地址。 – 2011-12-08 09:46:05

+1

@SamHocevar当然,你不能依赖于它将如何实现(虽然这种情况下,你正在使用一个没有漏洞的小范围,更有可能通过这种方式进行优化)。但是,尽管是否应用了优化,但由于传递行为的原因,它在语义上等同于“goto”,它以您通过的值为条件。行为是相同的,实现只影响性能。这似乎是OP的问题的一个相关答案,因为他打算使用'goto's构建一个状态机,'switch'就可以做到这一点。 – 2011-12-08 23:00:37

3

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

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

12

按照C99标准,第6.8.6,为goto语法是:

 
    gotoidentifier; 

所以,即使你可以采取一个标签的地址,你无法转到使用。

你可以用switch,这就好比一个计算goto,有同样的效果结合了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; 
} 

如果你用这个不是模糊ç竞赛其他任何东西,我将你打倒并伤害你。

+0

puts(#lbl)和puts(lbl)之间的区别是什么? – 2009-11-22 07:31:08

+1

'#'是预处理器字符串化操作符(http://en.wikipedia.org/wiki/C_preprocessor#Quoting_macro_arguments)。它将标识符转换为字符串。 'puts(lbl)'不会编译,因为'lbl'不是'char *'。 – outis 2009-11-22 07:54:06

+0

相反,如果你运行它,它会编译时会出现警告和崩溃。 – outis 2009-11-23 01:33:43

8

在非常非常非常非常旧的C语言版本中(想到恐龙漫游地球的时间),被称为“C参考手册”版本(指Dennis Ritchie编写的document),标签正式具有“为int数组”(奇怪,但真),这意味着你可以声明int *变量

int *target; 

和标签的地址赋给变量

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

以后您可以使用该变量作为01的操作数声明

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

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

2

我会注意到,这里描述的功能(包括& &在GCC)是理想的实现C中的Forth语言解释器,打击所有“不这样做”的论点出来的水 - 之间的配合Forth的内部解释器的功能和方式太好了,无法忽视。

6

我知道那种感觉,那么大家都说不应该这样做;它只是要完成。以下是如何做到这一点:

#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的。显然,jumpto汇编宏需要专门针对每个处理器(这一个适用于32位和64位x86)。另外请记住,不能保证堆栈的状态在同一个函数的两个不同点上是相同的。至少在打开一些优化的情况下,编译器可能会假定某些寄存器在标签之后的某个点上包含某个值。这些事情很容易搞砸,然后做编译器不期望的疯狂的狗屎。一定要证明阅读编译后的代码。

+0

难道你不能只是'抽出eax,标签; mov label_ptr,eax'(intel语法),将指针存储在变量中? – Calmarius 2014-10-16 08:45:07

+0

毫无疑问,它可以在汇编中实现(在这种情况下可能会被认为更好)。在C中实现它的一个好处是编译器会做一些优化。 – Fabel 2014-10-17 16:51:49

+0

这里最好的答案之一,非常感谢,帮助我在一个逆向工程项目。 – lama12345 2016-03-30 05:03:08

0

您可以使用& &将变量分配给变量。 这是您的修改代码。


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

    the_label: 


    if(i--) 
    goto *the_label_pointer; 


    return 0; 
} 
0

你可以这样做的Fortran的计算机转到与函数指针。

//全局变量在这里

无效C1(){//代码块

}

无效C2(){//代码块

}

void c3(){ //代码块

}

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

//然后
int x = 0;

goTo [x ++]();

goTo [x ++]();

goTo [x ++]();

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

请注意,这不是标准的C++,而是由GNU C++编译器提供的扩展(请参阅https://gcc.gnu.org/onlinedocs/gcc-6.2.0/gcc/Labels-as-Values.html#Labels-作为值)。 Clang也有这个扩展,而Visual C++没有(见http://stackoverflow.com/questions/6421433/address-of-labels-msvc)。 – 2016-10-03 08:09:10

相关问题