Is it possible to store the address of a label in a variable and use goto to jump to it?
I know everyone hates gotos. In my code, for reasons I have considered and am comfortable with, they provide an effective solution (ie I'm not looking for "don't do that" as an answer, I understand your reservations, and understand why I am using them anyway).
So far they have been fantastic, but I want to expand the functionality in such a way that requires me to essentially be able to store pointers to the labels, t开发者_开发百科hen go to them later.
If this code worked, it would represent the type of functionality that I need. But it doesn't work, and 30 min of googling hasn't revealed anything. Does anyone have any ideas?
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;
}
The C and C++ standards do not support this feature.
However, the GNU Compiler Collection (GCC) includes a non-standard extension for doing this, as described in the Labels as Values section of the Using the GNU Compiler Collection manual.
Essentially, they have added a special unary operator &&
that reports the address of the label as type void *
. See the article for details. With that extension, you could just use &&
instead of &
in your example, and it would work on GCC.
P.S. I know you don’t want me to say it, but I’ll say it anyway… DON’T DO THAT!!!
I know the feeling then everybody says it shouldn't be done; it just has to be done. In GNU C use &&the_label;
to take the address of a label. (https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html) The syntax you guessed, goto *ptr
on a void*
, is actually what GNU C uses.
Or if you want to use inline assembly for some reason, here's how to do it with GNU C asm 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 list of labels must include every possible value for the_label_pointer
.
The macro expansion will be something like
asm goto("jmp *%0" : : "ri"(the_label_pointer) : : the_label, label2, label3);
This compiles with gcc 4.5 and later, and with the latest clang which just got asm goto
support some time after clang 8.0. https://godbolt.org/z/BzhckE. The resulting asm looks like this for GCC9.1, which optimized away the "loop" of i=i
/ i--
and just put the the_label
after the jumpto
. So it still runs exactly once, like in the C source.
# gcc9.1 -O3 -fpie
main:
leaq .L2(%rip), %rax # ptr = &&label
jmp *%rax # from inline asm
.L2:
xorl %eax, %eax # return 0
ret
But clang didn't do that optimization and still has the loop:
# 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
The label address operator && will only work with gcc. And obviously the jumpto assembly macro needs to be implemented specifically for each processor (this one works with both 32 and 64 bit x86).
Also keep in mind that (without asm goto
) there would be no guarantee that the state of the stack is the same at two different points in the same function. And at least with some optimization turned on it's possible that the compiler assumes some registers to contain some value at the point after the label. These kind of things can easily get screwed up then doing crazy shit the compiler doesn't expect. Be sure to proof read the compiled code.
These are why asm goto
is necessary to make it safe by letting the compiler know where you will / might jump, getting consistent code-gen for the jump and the destination.
You can do something similar with 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;
}
In the very very very old version of C language (think of the time dinosaurs roamed the Earth), known as "C Reference Manual" version (which refers to a document written by Dennis Ritchie), labels formally had type "array of int" (strange, but true), meaning that you could declare an int *
variable
int *target;
and assign the address of label to that variable
target = label; /* where `label` is some label */
Later you could use that variable as the operand of goto
statement
goto target; /* jumps to label `label` */
However, in ANSI C this feature was thrown out. In the standard modern C you cannot take address of a label and you cannot do "parametrized" goto
. This behavior is supposed to be simulated with switch
statements, pointers-to-functions and other methods etc. Actually, even "C Reference Manual" itself said that "Label variables are a bad idea in general; the switch statement makes them almost always unnecessary" (see "14.4 Labels").
According to the C99 standard, § 6.8.6, the syntax for a goto
is:
goto identifier ;
So, even if you could take the address of a label, you couldn't use it with goto.
You could combine a goto
with a switch
, which is like a computed goto
, for a similar effect:
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;
}
If you use this for anything other than an obfuscated C contest, I will hunt you down and hurt you.
The switch ... case
statement is essentially a computed goto
. A good example of how it works is the bizarre hack known as 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);
}
}
You can't do a goto
from an arbitrary location using this technique, but you can wrap your entire function in a switch
statement based on a variable, then set that variable indicating where you want to go, and goto
that switch statement.
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;
}
}
Of course, if you do this a lot, you'd want to write some macros to wrap it.
This technique, along with some convenience macros, can even be used to implement coroutines in C.
I will note that the functionally described here (including && in gcc) is IDEAL for implementing a Forth language interpreter in C. That blows all the "don't do that" arguments out of the water - the fit between that functionality and the way Forth's inner interpreter works is too good to ignore.
Use function pointers and a while loop. Don't make a piece of code someone else will have to regret fixing for you.
I presume you're trying to change the address of the label somehow externally. Function pointers will work.
#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;
}
The only officially supported thing that you can do with a label in C is goto
it. As you've noticed, you can't take the address of it or store it in a variable or anything else. So instead of saying "don't do that", I'm going to say "you can't do that".
Looks like you will have to find a different solution. Perhaps assembly language, if this is performance-critical?
Read this: setjmp.h - Wikipedia As previously said it is possible with setjmp/longjmp with which you can store a jumppoint in a variable and jump back later.
You can assign label to variable using &&. Here is your modified code.
int main (void)
{
int i=1;
void* the_label_pointer = &&the_label;
the_label:
if( i-- )
goto *the_label_pointer;
return 0;
}
According to this thread, label points are not a standard, so whether they work or not would depend on the compiler you're using.
You can do something like Fortran's computed goto with pointers to functions.
// 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++] ();
Or use try/catch
#include <iostream>
template<int N> struct GoTo{};
template<int N> using Label = GoTo<N>;
int main() {
int x;
std::cin >> x;
try {
if(x==1) throw GoTo<1>{};
if(x==2) throw GoTo<2>{};
if(x==3) throw GoTo<3>{};
throw x;
}
catch(Label<1>){
std::cout << 1;
}
catch(Label<2>){
std::cout << 2;
}
catch(Label<3>){
std::cout << 3;
}
catch(int x){
std::cout << x;
}
}
精彩评论