Is it possible to store the address of a label in a variable and use goto to jump to it?

前端 未结 14 1599
执念已碎
执念已碎 2020-12-04 09:35

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\

相关标签:
14条回答
  • 2020-12-04 10:11

    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.

    0 讨论(0)
  • 2020-12-04 10:12

    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.

    0 讨论(0)
  • 2020-12-04 10:13

    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").

    0 讨论(0)
  • 2020-12-04 10:13
    #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;
    }
    
    0 讨论(0)
  • 2020-12-04 10:19

    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.

    0 讨论(0)
  • 2020-12-04 10:19

    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;
    }
    
    0 讨论(0)
提交回复
热议问题