是否有可能将一个标签的地址存储在一个variables,并使用goto跳转到它?
我知道每个人都讨厌gotos。 在我的代码中,由于我已经考虑和适应的原因,他们提供了一个有效的解决scheme(即我不寻找“不这样做”作为答案,我理解你的保留,并理解为什么我使用它们无论如何)。
到目前为止,他们已经很棒了,但是我想要扩展function的方式需要我基本上能够存储指向标签的指针,然后再去找它们。
如果这个代码工作,它将代表我需要的functiontypes。 但它不起作用,30分钟的谷歌search没有透露任何东西。 有没有人有任何想法?
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; }
C和C ++标准不支持这个function。 但是,GNU编译器集合(GCC)包含一个非标准扩展,用于执行此操作,如本文所述 。 实质上,他们添加了一个特殊的运算符“&&”,将标签的地址报告为“void *”types。 详情请参阅文章。
PS换句话说,在你的例子中只用“&&”而不是“&”,它将在GCC上工作。
PPS我知道你不想让我说出来,但我会说,无论如何,不要这么做!
你可以用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; }
根据C99标准§6.8.6, goto
的语法是:
转到 标识符 ;
所以,即使你可以拿到一个标签的地址,也不能在转到时使用它。
你可以把一个goto
和一个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比赛之外的任何事情,我会追捕你,伤害你。
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
,但是你可以把你的整个函数封装在一个基于variables的switch
语句中,然后设置这个variables指明你想去的地方,然后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; } }
当然,如果你做了这么多,你会想写一些macros来包装它。
这个技巧,连同一些方便的macros,甚至可以用来在C中实现协程 。
在非常非常非常古老的C语言版本(恐龙漫游地球的时间)中,被称为“C参考手册”版本(指Dennis Ritchie编写的文档 ),标签正式具有types“int的数组” (奇怪,但是是真的),这意味着你可以声明一个int *
variables
int *target;
并将标签的地址分配给该variables
target = label; /* where `label` is some label */
稍后你可以使用该variables作为goto
语句的操作数
goto target; /* jumps to label `label` */
但是,在ANSI C中,此function已被抛出。 在标准的现代C中,你不能拿到一个标签的地址,你不能做“参数化”的goto
。 这种行为应该用switch
语句,指针函数和其他方法来模拟。实际上,即使“C参考手册”本身也说过:“标签variables通常是一个坏主意; switch语句几乎总是不必要的“(见”14.4标签“ )。
我知道大家都说不应该这样做, 它只是要做。 以下是如何做到这一点:
#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汇编macros需要专门针对每个处理器(这一个适用于32位和64位x86)。 还要记住,不能保证堆栈的状态在同一个函数中的两个不同点上是一样的。 至less在打开一些优化的情况下,编译器可能会假定某些寄存器在标签之后的某个点上包含某个值。 这样的事情可以很容易搞砸,然后做编译器不期望的疯狂狗屎。 一定要certificate读取已编译的代码。
唯一官方支持的事情,你可以做一个标签在C是goto
它。 正如你已经注意到的,你不能把它的地址或存储在一个variables或其他任何东西。 所以,我不会说“不这样做”,而是说“你不能这样做”。
看起来你将不得不寻找一个不同的解决scheme。 也许汇编语言,如果这是性能至关重要?
使用函数指针和一个while循环。 不要做一段代码,其他人将不得不后悔修复你。
我认为你试图改变外部标签的地址。 函数指针将起作用。
我会注意到,这里所描述的函数(包括gcc中的&&)是理想的,可以在C语言中实现一个Forth语言解释器。将所有“不这样做”的观点都抛出水面 – 这种function和方式福斯的内心解读作品太好了,无法忽视。
阅读此: setjmp.h – 维基百科正如前面所说,可以用setjmp / longjmp来将一个jumppoint存储在一个variables中,然后再跳回来。
#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; }
根据这个线程 ,标签点不是一个标准,所以不pipe它们是否工作都取决于你正在使用的编译器。
您可以使用&&将variables分配给variables。 这是你修改的代码。
int main (void) { int i=1; void* the_label_pointer = &&the_label; the_label: if( i-- ) goto *the_label_pointer; return 0; }
你可以做一些像Fortran的电脑转到function的指针。
//全局variables在这里
void c1(){//代码块
}
void c2(){//大量的代码
}
void c3(){//代码块
}
void(* goTo [3])(void)= {c1,c2,c3};
// 然后
int x = 0;goTo [x ++]();
goTo [x ++]();
goTo [x ++]();