2009-11-22 4 views
45

모두가 싫어하는 것을 알고 있습니다. 내 코드에서, 내가 고려하고 편안함을 이유로 효과적인 솔루션을 제공한다. (즉, 나는 대답을하지 않는다. 나는 당신의 예약을 이해하고 왜 내가 그것을 사용하고 있는지 이해한다. 어쨌든).변수에 레이블의 주소를 저장하고 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

당신은 설명 할 수 있습니까? –

+3

이 게시물에서 Remo.D의 대답을 기반으로하는 유한 상태 시스템을 구현하고 있습니다. http://stackoverflow.com/questions/132241/ 내 버전은 이보다 상당히 복잡하게 진화했지만 이것이 기본입니다. 구조. 지금까지는 효과적 이었지만 상태 전환이나 콜백 또는 무언가를 통해 설정된 일부 변수를 통해 호출 상태와 현재 상태에 액세스 할 수있는 상황을 상태에 사용할 수있게하려고합니다. –

+1

중복의 http://stackoverflow.com/questions/938518/c-c-goto – qrdl

답변

3

C에서 레이블을 사용하여 공식적으로 지원할 수있는 것은 goto입니다. 당신이주의했듯이, 당신은 그것의 주소를 취하거나 변수 또는 다른 것에 저장할 수 없습니다. 그래서 "하지마"라고 말하면서 "나는 할 수 없다"라고 말할 것입니다.

다른 솔루션을 찾아야하는 것처럼 보입니다. 아마도 성능상 중요한 어셈블리 언어일까요?

+3

+1 그냥 어셈블리에서 그것을하기위한, 그게 내가 이전에 비슷한 문제를 해결하는 방법입니다. – mrduclaw

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

56

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

P. 즉, 예제에서 "&"대신 "& &"을 사용하면 GCC에서 작동합니다.
P.P.S. 나는 네가 내가 그 말을하기를 원하지 않는다는 것을 안다. 그러나 나는 어쨌든 그것을 말할 것이다. 그것을하지 마라 !!!

+8

PPS 용 +1! –

+0

+1도 PPS에 대해! 나는 내 자신을 추가 할 것이다 : 그것을하지 마십시오! –

+7

goto 레이블 주소는 인터프리터 작성에 적합합니다. –

0

this thread에 따르면 레이블 포인트는 표준이 아니므로 작동 여부는 사용중인 컴파일러에 따라 다릅니다.

1

읽어보기 : setjmp.h - Wikipedia 이전에 말했듯이 변수에 점퍼를 저장하고 나중에 다시 점프 할 수있는 setjmp/longjmp를 사용하는 것이 가능합니다.

9

switch ... case 진술은 본질적으로 computed goto입니다.

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 문에 전체 기능을 포장 할 수 있습니다 그것이 작동하는 방법의 좋은 예는 Duff's Device로 알려진 기괴한 해킹 변수를 입력 한 다음 이동하려는 위치를 나타내는 해당 변수를 설정하고 switch 문은 goto입니다.

물론이 작업을 많이 수행하는 경우 매크로를 작성하여 포장하고 싶을 것입니다.

이 기술은 일부 편리한 매크로와 함께 coroutines in C을 구현하는 데 사용될 수도 있습니다.

+1

'switch/case'가 계산 된'goto'로 구현된다는 보장은 없습니다. 꽤 자주 if/else if/else if/...와 같이 컴파일되며 생성 된 어셈블리는 단일 주소를 계산하기보다는 각 값을 테스트합니다. –

+1

@SamHocevar 물론, 구현 방법에 의존 할 수는 없습니다 (구멍이없는 작은 범위를 사용하는 이런 경우는 이런 식으로 최적화 될 가능성이 훨씬 높습니다). 그러나 최적화가 적용되는지 여부에도 불구하고, 이는 통과 (fall-through) 동작 때문에 전달하는 값을 조건으로하는 'goto'와 의미 상 동일합니다. 동작은 동일합니다. 구현은 성능에만 영향을 미칩니다. 그리고 OP의 질문에 대한 적절한 답변 인 것 같습니다. 왜냐하면 그는'goto'를 사용하여 상태 머신을 만들려고하고 있기 때문에,'switch '가 그 트릭을 할 것입니다. –

3

함수 포인터와 while 루프를 사용하십시오. 누군가 다른 사람이 당신을 위해 고쳐야 할 코드 조각을 만들지 마십시오.

나는 외부에서 라벨의 주소를 변경하려고한다고 가정합니다. 함수 포인터가 작동합니다.

12

C99 표준에 따르면, 6.8.6을 §하는 goto에 대한 구문은 다음과 같습니다 그래서

 
    gotoidentifier; 

, 당신이 레이블의 주소를 취할 수있다하더라도, 당신은 고토와 함께 사용할 수없는 .

당신은 유사한 효과를 위해, 계산 된 goto 같이하는 switch, 함께 goto을 결합 할 수 있습니다 : 당신이 난독 C 경연 대회 이외의 다른이를 사용하는 경우

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)의 차이점은 무엇입니까? –

+1

'#'은 전 처리기 문자열 연산자 (http://en.wikipedia.org/wiki/C_preprocessor#Quoting_macro_arguments)입니다. 식별자를 문자열로 변환합니다. 'puts (lbl)'는'lbl'이'char * '이 아니기 때문에 컴파일되지 않습니다. – outis

+0

대신 실행하면 경고와 함께 컴파일되고 충돌합니다. – outis

8

"C Reference Manual"버전 (Dennis Ritchie가 작성한 document을 말함)으로 알려진 매우 오래된 버전의 C 언어 (공룡이 지구를 배회 한 시간을 생각해 보라)에서 레이블에는 정식으로 " INT의 배열 "(이상한, 그러나 조정), 당신은

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

나중에 당신이로 그 변수를 사용할 수있는 int * 변수

int *target; 

를 선언하고 그 변수에 라벨의 주소를 할당 할 수 있음을 의미 01의 피연산자문

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

그러나 ANSI C에서는이 기능이 삭제되었습니다. 표준 현대 C에서는 레이블의 주소를 가져올 수 없으며 "매개 변수화 된"goto을 사용할 수 없습니다. 이 동작은 switch 문, 포인터 대 함수 및 다른 방법 등으로 시뮬레이트 된 것으로되어 있습니다. 실제로 "C 참조 설명서"자체도 "레이블 변수는 일반적으로 나쁜 생각이므로 switch 문을 사용하면 거의 항상 불필요하게됩니다 "("14.4 Labels" 참조).

2

나는주의 것 기능적으로 불면 C.에서 넷째 언어 인터프리터를 구현하기위한 이상적입니다 (GCC에서 & & 포함) 여기에 설명 된 모든 "하지 않는"물 밖으로 인수 - 사이의 적합 그 기능과 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

그냥 'lea eax, label; mov label_ptr, eax' (intel 구문), 포인터를 변수에 저장 하시겠습니까? – Calmarius

+0

어셈블리에서 구현 될 수 있다는 것은 의심의 여지가 없습니다 (이 경우 더 잘 고려 될 수 있음). C로 구현할 때의 이점 중 하나는 컴파일러가 최적화를 수행한다는 것입니다. – Fabel

+0

가장 좋은 답변 중 하나는 대단히 감사합니다. 리버스 엔지니어링 프로젝트에서 저를 도왔습니다. – lama12345

0

& &을 사용하여 변수에 레이블을 지정할 수 있습니다. 수정 된 코드는 다음과 같습니다.


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

    the_label: 


    if(i--) 
    goto *the_label_pointer; 


    return 0; 
} 
0

당신은 함수에 대한 포인터와 포트란의 컴퓨터 고토 같은 작업을 수행 할 수 있습니다.

가 // 글로벌 변수 여기까지

공극 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 참조). –