C 的 goto 僅能在函數內部跳轉。跨函數跳轉(如深層調用鏈中遇到錯誤直接返回主循環)需用 setjmp/longjmp,它們分別充當「標記點」與「非局部 goto」。
#include/* 在 jmp_buf 保存當前調用環境(棧指針、幀指針、寄存器等) 直接調用返回 0;longjmp 返回時返回非 0 值 */ int setjmp(jmp_buf env); /* 恢復 env 保存的環境,程序彷彿從對應 setjmp 返回 val 為 0 時 setjmp 返回 1,否則返回 val */ void longjmp(jmp_buf env, int val);
#include#include jmp_buf buf; // 全局或靜態存儲,生命週期覆蓋跳轉範圍 void test() { printf("進入 test()\n"); longjmp(buf, 1); // 跳回 setjmp 處 printf("返回 test() - 永不執行\n"); } int main() { if (setjmp(buf)) { // longjmp 返回時進入此分支 printf("main: 通過 longjmp 返回\n"); } else { // 首次直接調用進入此分支 printf("main: 正常執行 setjmp\n"); test(); } return 0; }
main: 正常執行 setjmp 進入 test() main: 通過 longjmp 返回
setjmp 將 EBP, ESP, EIP, EBX, ESI, EDI 等寄存器存入 jmp_buflongjmp 直接恢復 ESP/EBP,中間函數的棧幀被「拋棄」,析構函數/清理代碼不會執行longjmp 的 val 成為 setjmp 的返回值,區分正常返回與跳轉返回jmp_buf 所在函數必須尚未返回(棧幀存在),否則行為未定義jmp_buf exception_env;
void risky_operation() {
if (error_condition) longjmp(exception_env, ERROR_CODE);
}
int main() {
int err = setjmp(exception_env);
if (err) {
fprintf(stderr, "捕獲異常: %d\n", err);
return err;
}
risky_operation();
return 0;
}
C++ try/catch/throw 在編譯器層面實現了類似機制,但增加了棧展開(調用析構函數)、類型匹配、異常對象傳遞。現代 C++ 禁用 setjmp/longjmp,因其繞過 RAII 導致資源洩漏。
siglongjmp 並阻塞信號setjmp 與 longjmp 之間修改的局部變量,若未聲明 volatile,值可能因寄存器優化而不可預測longjmp 後代碼為不可達,需注意setjmp/longjmp 是 C 語言實現非局部控制流的基礎設施,理解其棧幀操作本質,對掌握 C++ 異常、協程、信號處理、嵌入式錯誤恢復等高級主題至關重要。實際工程中,優先使用結構化錯誤處理(返回碼、錯誤碼線程局部存儲),僅在極簡環境或底層框架中謹慎使用。