programing

변수에 레이블의 주소를 저장하고 goto를 사용하여 점프 할 수 있습니까?

copysource 2021. 1. 17. 11:49
반응형

변수에 레이블의 주소를 저장하고 goto를 사용하여 점프 할 수 있습니까?


나는 모두가 gotos를 싫어한다는 것을 안다. 내 코드에서 내가 고려하고 편안하게 생각하는 이유로 효과적인 솔루션을 제공합니다 (예 : 대답으로 "하지 말 것"를 찾고 있지 않고 귀하의 예약을 이해하며 내가 사용하는 이유를 이해합니다.) 어쨌든).

지금까지는 환상적 이었지만 본질적으로 레이블에 대한 포인터를 저장 한 다음 나중에 레이블로 이동할 수 있어야하는 방식으로 기능을 확장하고 싶습니다.

이 코드가 작동하면 필요한 기능 유형을 나타냅니다. 그러나 작동하지 않으며 검색 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;
}

C 및 C ++ 표준은이 기능을 지원하지 않습니다. 그러나 GCC (GNU Compiler Collection)에는 이 문서에 설명 된대로이를 수행하기위한 비표준 확장이 포함되어 있습니다. 기본적으로 레이블 주소를 "void *"유형으로보고하는 특수 연산자 "&&"를 추가했습니다. 자세한 내용은 기사를 참조하십시오.

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에 따르면 a의 구문 goto은 다음과 같습니다.

    goto  식별자  ;

따라서 레이블의 주소를 가져올 수 있다고해도 goto와 함께 사용할 수 없습니다.

유사한 효과를 위해 computed와 같은 a goto결합 할 수 있습니다 .switchgoto

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이 기술을 사용하여 임의의 위치에서를 수행 할 수는 없지만 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;
  }
}

물론이 작업을 많이한다면 일부 매크로를 작성하여 래핑하는 것이 좋습니다.

이 기술은 몇 가지 편리한 매크로와 함께 C로 코 루틴 을 구현하는 데 사용할 수도 있습니다 .


"C Reference Manual"버전 ( Dennis Ritchie가 작성한 문서 참조)으로 알려진 아주 아주 오래된 버전의 C 언어 (공룡이 지구를 배회했던 시간을 생각해보십시오 )에서 레이블은 공식적으로 "array of int"유형을 가졌습니다. (이상하지만 사실), int *변수를 선언 할 수 있음을 의미 합니다.

int *target;

레이블의 주소를 해당 변수에 할당하십시오.

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

나중에 해당 변수를 goto문의 피연산자로 사용할 수 있습니다 .

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

그러나 ANSI C에서는이 기능이 폐기되었습니다. 표준 현대 C에서는 레이블의 주소를 사용할 수 없으며 "매개 변수화"를 수행 할 수 없습니다 goto. 이 동작은 switch명령문, 함수 포인터 및 기타 방법 등 으로 시뮬레이션되어야 합니다. 실제로 "C Reference Manual"자체에서도 "라벨 변수는 일반적으로 나쁜 생각입니다. switch 문은 거의 항상 불필요하게 만듭니다." ( "14.4 레이블" 참조 ).


나는 그 느낌을 안다면 모두가 그것을하지 말아야한다고 말한다. 그냥 가지고 할 수 있습니다. GNU C에서는 &&the_label;레이블 주소를 사용 합니다. ( https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html )에서 추측 한 구문 은 실제로 GNU C에서 사용 goto *ptr하는 구문 void*입니다.

또는 어떤 이유로 인라인 어셈블리를 사용하려는 경우 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_label_pointer.

매크로 확장은 다음과 같습니다.

asm goto("jmp *%0" : : "ri"(the_label_pointer) : : the_label, label2, label3);

이것은 gcc 4.5 이상과 asm gotoclang 8.0 이후 얼마 동안 지원 을받은 최신 clang으로 컴파일됩니다 . https://godbolt.org/z/BzhckE . 의 "루프를"멀리 최적화 GCC9.1이 같은 결과 ASM의 외모, i=i/ i--그냥 넣어 the_label jumpto . 따라서 C 소스에서와 같이 정확히 한 번만 실행됩니다.

# gcc9.1 -O3 -fpie
main:
    leaq    .L2(%rip), %rax     # ptr = &&label
    jmp *%rax                     # from inline asm
.L2:
    xorl    %eax, %eax          # return 0
    ret

그러나 clang은 이러한 최적화를 수행하지 않았으며 여전히 루프가 있습니다.

# 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

레이블 주소 연산자 &&는 gcc에서만 작동합니다. 그리고 분명히 jumpto 어셈블리 매크로는 각 프로세서에 대해 특별히 구현되어야합니다 (이는 32 비트 및 64 비트 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.


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 computer 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++] ();

ReferenceURL : https://stackoverflow.com/questions/1777990/is-it-possible-to-store-the-address-of-a-label-in-a-variable-and-use-goto-to-jum

반응형