首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

C 語言非局部跳轉:setjmp/longjmp 原理、用法與異常處理模式

     編輯:關於C

背景:goto 的局限與解決方案

C 的 goto 僅能在函數內部跳轉。跨函數跳轉(如深層調用鏈中遇到錯誤直接返回主循環)需用 setjmp/longjmp,它們分別充當「標記點」與「非局部 goto」。

API 簽名與語義

#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 返回

核心機制深度解析

  1. 環境保存:setjmp 將 EBP, ESP, EIP, EBX, ESI, EDI 等寄存器存入 jmp_buf
  2. 棧幀完整性:longjmp 直接恢復 ESP/EBP,中間函數的棧幀被「拋棄」,析構函數/清理代碼不會執行
  3. 返回值傳遞:longjmp 的 val 成為 setjmp 的返回值,區分正常返回與跳轉返回
  4. 生命週期要求:jmp_buf 所在函數必須尚未返回(棧幀存在),否則行為未定義

典型應用場景:C 風格異常處理

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++ 異常的關係

C++ try/catch/throw 在編譯器層面實現了類似機制,但增加了棧展開(調用析構函數)、類型匹配、異常對象傳遞。現代 C++ 禁用 setjmp/longjmp,因其繞過 RAII 導致資源洩漏。

避坑指南

  • 不要跳過變長數組 (VLA) 或 alloca 分配的棧內存 — 棧指針恢復會導致內存洩漏/損壞
  • 不要在信號處理函數中 longjmp — 除非用 siglongjmp 並阻塞信號
  • 易失性變量:setjmp 與 longjmp 之間修改的局部變量,若未聲明 volatile,值可能因寄存器優化而不可預測
  • 編譯器警告:現代 IDE (Xcode, VS, CLion) 會標記 longjmp 後代碼為不可達,需注意

總結

setjmp/longjmp 是 C 語言實現非局部控制流的基礎設施,理解其棧幀操作本質,對掌握 C++ 異常、協程、信號處理、嵌入式錯誤恢復等高級主題至關重要。實際工程中,優先使用結構化錯誤處理(返回碼、錯誤碼線程局部存儲),僅在極簡環境或底層框架中謹慎使用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved