2013-02-21 78 views
4

這主要是理論上的問題,因爲它沒有多大用處。可以1返回調用2返回?

考慮這種情況:

function a() { 
    return; 
} 

function b(){ 
    a(); 
} 

你可以調用從一個孩子在父母功能的回報?

現在,在這種情況下,您可以簡單地做return a();,那會發生,但我們假設您不希望執行回報。

我知道,當它翻譯成彙編時,這沒有任何意義,在這種情況下,你可以使用goto,但我們都知道這有多危險。

我的邏輯表明,如果你可以從子循環執行一個continue來調用父進程的繼續,這應該是相同的,但循環不會影響堆棧,因此繼續工作是有意義的。

我想知道是否有任何方法來處理這種情況witjout使用事件或oop方法?

+2

在C中,你不能「從子循環中執行」繼續「,這將會調用父進程的繼續」。你只能「繼續」(和「打破」)最內層循環。 – unwind 2013-02-21 11:46:22

+1

@unwind哦...它可能在PHP中,所以我認爲... – 2013-02-21 11:50:53

回答

1

傳統的C解決方案是longjmp函數,它可以以任意方式在堆棧上跳轉。請注意,總是有人明智地不使用它,並且在很大程度上由異常處理取得成功。

+1

不幸的是,'longjmp'比'goto'更危險,因爲作者最初將'goto'描述爲危險。 – phoeagon 2013-02-21 11:38:50

+0

有'2迴歸'的意思嗎? – 2013-02-21 11:40:47

+0

我同意phoeagon,如果不使用goto的原因是安全的,那麼setjmp/longjmp也不能使用。它們可能是整個C語言中最危險的特徵。 – Lundin 2013-02-21 11:57:10

0

您可以使用宏而不是函數。

#define a() ... return ... 

用例是斷言,沒有完全發佈版本中刪除,但中止功能:

#define REQUIRE(x) do { assert((x)); if (!(x)) return; } while (0) 

您也可以破解彙編有所收穫的的StackFrame呼叫功能和使用返回地址從那裏:

void return2(){ 
    void * frame; 
    #if (defined(x86_64) || defined(__x86_64__)) 
    __asm__(
    "pop %rbp\n"  //skip over stack frame of return2 
    "mov %rsp, %rbp\n" 
    "pop %rax\n" 
    "pop %rbp\n"  //skip over stack frame of caller 
    "mov %rsp, %rbp\n" 
    "pop %rax\n" 
); 

    #else 
    #error only implmented for amd64... 
    #endif 
} 

然後

void a(){ 
    printf("a 0\n"); 
    return2(); 
    printf("a 1\n"); 
} 

void b(){ 
    printf("b 0\n"); 
    a(); 
    printf("b 1\n"); 
} 

int main(int argc, char* argv[]) 
{ 
    printf("main 0\n"); 
    b(); 
    printf("main 1\n"); 
    return 0; 
} 

打印

main 0 
b 0 
a 0 
main 1 

這是所有最危險的溶液(和失敗,如果GCC內聯的東西或更高優化級別上刪除的StackFrame。但是你可以添加一個檢查指令的檢查,如果它們被優化了)

+0

這似乎是一個合理的解決方案,但其他答案更好地回答了問題。非常感謝你! – 2013-02-21 11:52:16

+0

我從SO瞭解到,由於調用和返回之間的不平衡,這種方法會破壞返回地址預測邏輯。順便說一句,這是奇怪的英特爾有Enter N指令產生N級呼叫幀,但沒有相應的離開N ... – 2013-02-21 12:49:42

+1

返回地址預測?只要它不崩潰,我很高興 – BeniBela 2013-02-21 12:53:49

0

如果你在Windows上使用MSVC,你可以使用異常(Strucured Exception Handling,SEH)來實現類似的功能。正如thiton所說,在其他平臺上,你可以使用setjmp/longjmp。

隨着SEH,你可以做類似如下(還沒有嘗試過,因爲我沒有窗戶與Visual Studio準備)​​:

#include "stdio.h" 
#include "Windows.h" 

void func_b() { 
    printf("In func_b()\n"); 
    // return safely to main 
    RaiseException(1, EXCEPTION_NONCONTINUABLE, 0, NULL); 
    printf("At end of func_b()\n"); 
} 

void func_a() { 
    printf("In func_a()\n"); 
    func_b(); 
    printf("At end of func_a()\n"); 
} 

void main() { 
    printf("In func_a()\n"); 
    __try { 
     func_a(); 
    } 
    __except (GetExceptionCode() == 1) { 
     printf ("Early return to main()\n"); 
    } 
    printf("At end of main()\n"); 
} 

的電話的RaiseException控制的原因往上走棧,直到在main()中捕獲異常。這不是真的「返回^ 2」,因爲調用函數(main)必須一起玩。一般來說,你還需要配合你想要跳過的功能(這裏是func_a),因爲它們可能會做些事情並需要清理。只是說「從func_b返回,並停止func_a正在做的事情並從中返回」,這可能是非常危險的。如果您使用的例外,但是,你可以用你的代碼中的try/finally從句func_a:

FILE* f; 
__try { 
    f = fopen("file.txt", "r"); 
    func_b(); 
} 
__finally { 
    fclose(f); 
    printf("Cleanup for func_a()\n"); 
} 

這當然是在原生地支持異常的語言好得多(C++,Python和Java的,...) ,並且不要把它作爲一個專有的擴展插件。

請注意,有些人認爲對控制流使用異常是一種不好的做法,並且說異常應該保留用於真正異常事件(如IO錯誤)。有很多情況下它是有意義的(例如,你正在解析一些東西,並且深入瞭解堆棧,你必須以不同方式回放和解析某些東西,你可以拋出一個自定義的異常)。總的來說,我會說盡量不要太聰明,儘量不要做會讓你的程序讀者感到困惑的事情。看起來你需要使用一些這樣的技巧,通常有一種方法可以對程序進行重組,以便對語言來說很自然。或者,也許你使用的語言不是該問題的好選擇。