2009-11-22 34 views
45

我知道每個人都討厭gotos。在我的代碼中,出於我已經考慮並且很舒服的原因,他們提供了一個有效的解決方案(即,我不是在尋找「不這樣做」作爲答案,我理解您的保留意見,並理解爲什麼我使用它們無論如何)。是否可以將標籤的地址存儲在變量中並使用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

你能解釋一下爲什麼你需要將這些標籤存儲指針? – 2009-11-22 06:23:10

+3

我正在實現一個有限狀態機,基於Remo.D在這篇文章中的回答http://stackoverflow.com/questions/132241/我的版本已經發展到比這更多地涉及,但這代表了基本結構體。到目前爲止,它一直很有效,但我想通過在狀態轉換時設置的一些變量或通過回調或某物訪問某些上下文來訪問調用狀態和當前狀態。 – 2009-11-22 06:36:01

+1

重複http://stackoverflow.com/questions/938518/c-c-goto – qrdl 2009-11-22 07:52:52

回答

3

唯一正式支持的事情,你可以在C標籤做的是goto它。正如你所注意到的,你不能把它的地址或存儲在一個變量或其他任何東西中。所以,我不會說「不這樣做」,而是說「你不能那樣做」。

看起來你必須找到不同的解決方案。也許彙編語言,如果這是性能關鍵?

+3

+1只是在彙編中,這就是我以前解決類似的問題。 – mrduclaw 2009-11-22 06:22:36

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 2009-11-22 07:00:32

56

C和C++標準不支持此功能。但是,GNU編譯器集合(GCC)包含一個非標準擴展,如this article中所述。基本上,他們添加了一個特殊的運算符「& &」,它將標籤的地址報告爲類型「void *」。詳情請參閱文章。

P.S.換句話說,在你的例子中,只使用「& &」而不是「&」,它可以在GCC上工作。
P.P.S.我知道你不想讓我說出來,但無論如何我會說,不要這樣做!

+8

+1爲PPS! – 2009-11-22 06:57:48

+0

+1也適用於PPS!我會加我自己的:不要這樣做! – 2009-11-22 08:11:17

+7

goto標籤地址非常適合編寫口譯員。 – 2013-11-05 17:22:21

0

根據this thread,標籤點不是一個標準,所以無論它們是否工作都取決於您使用的編譯器。

1

閱讀原文:setjmp.h - Wikipedia如前所述,使用setjmp/longjmp可以將一個jumppoint存儲在變量中並稍後跳回。

9

switch ... case聲明本質上是computed 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; 
    } 
} 

當然,如果你做了很多,你會想寫一些宏來包裝它。

這種技術,以及一些便利的宏,甚至可以用來實現coroutines in C

+1

不能保證'switch/case'將被實現爲計算的'goto'。很多時候,它被編譯爲一系列「if/else if/else if/...」,並且生成的程序集將測試每個值,而不是計算要跳轉到的單個地址。 – 2011-12-08 09:46:05

+1

@SamHocevar當然,你不能依賴於它將如何實現(雖然這種情況下,你正在使用一個沒有漏洞的小範圍,更有可能通過這種方式進行優化)。但是,儘管是否應用了優化,但由於傳遞行爲的原因,它在語義上等同於「goto」,它以您通過的值爲條件。行爲是相同的,實現隻影響性能。這似乎是OP的問題的一個相關答案,因爲他打算使用'goto's構建一個狀態機,'switch'就可以做到這一點。 – 2011-12-08 23:00:37

3

使用函數指針和while循環。不要製造一段代碼,其他人將不得不後悔修復你。

我認爲你試圖改變外部標籤的地址。函數指針將起作用。

12

按照C99標準,第6.8.6,爲goto語法是:

 
    gotoidentifier; 

所以,即使你可以採取一個標籤的地址,你無法轉到使用。

你可以用switch,這就好比一個計算goto,有同樣的效果結合了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; 
} 

如果你用這個不是模糊ç競賽其他任何東西,我將你打倒並傷害你。

+0

puts(#lbl)和puts(lbl)之間的區別是什麼? – 2009-11-22 07:31:08

+1

'#'是預處理器字符串化操作符(http://en.wikipedia.org/wiki/C_preprocessor#Quoting_macro_arguments)。它將標識符轉換爲字符串。 'puts(lbl)'不會編譯,因爲'lbl'不是'char *'。 – outis 2009-11-22 07:54:06

+0

相反,如果你運行它,它會編譯時會出現警告和崩潰。 – outis 2009-11-23 01:33:43

8

在非常非常非常非常舊的C語言版本中(想到恐龍漫遊地球的時間),被稱爲「C參考手冊」版本(指Dennis Ritchie編寫的document),標籤正式具有「爲int數組」(奇怪,但真),這意味着你可以聲明int *變量

int *target; 

和標籤的地址賦給變量

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

以後您可以使用該變量作爲01的操作數聲明

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

但是,在ANSI C中,此功能被拋出。在標準的現代C中,您無法獲得標籤的地址,也無法執行「參數化」goto。這種行爲應該用switch語句,指針函數和其他方法等來模擬。實際上,即使「C參考手冊」本身也會說「標籤變量通常是一個壞主意; switch語句幾乎總是不必要的「(見"14.4 Labels")。

2

我會注意到,這裏描述的功能(包括& &在GCC)是理想的實現C中的Forth語言解釋器,打擊所有「不這樣做」的論點出來的水 - 之間的配合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

難道你不能只是'抽出eax,標籤; mov label_ptr,eax'(intel語法),將指針存儲在變量中? – Calmarius 2014-10-16 08:45:07

+0

毫無疑問,它可以在彙編中實現(在這種情況下可能會被認爲更好)。在C中實現它的一個好處是編譯器會做一些優化。 – Fabel 2014-10-17 16:51:49

+0

這裏最好的答案之一,非常感謝,幫助我在一個逆向工程項目。 – lama12345 2016-03-30 05:03:08

0

您可以使用& &將變量分配給變量。 這是您的修改代碼。


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

    the_label: 


    if(i--) 
    goto *the_label_pointer; 


    return 0; 
} 
0

你可以這樣做的Fortran的計算機轉到與函數指針。

//全局變量在這裏

無效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)。 – 2016-10-03 08:09:10

相關問題