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

C++ 中使用 setjmp/longjmp 的陷阱與對策

     編輯:關於C語言

為何 MSDN 警告:「setjmp and longjmp do not support C++ object semantics」?

C++ 標準庫提供了完善的異常處理機制(try/catch/throw),但在某些場景下(如整合 C 庫、跨語言呼叫),開發者仍可能被迫使用 C 風格的 setjmp/longjmp。然而,這對組合在 C++ 中極難「和睦相處」,核心原因在於物件生命週期管理機制的根本差異。

經典案例:析構函數不被呼叫

觀察以下兩段程式碼的執行結果差異:

案例一:物件在 setjmp 後建立

void main() {
    jmp_buf mark;
    int jmpret = setjmp(mark);
    if (jmpret == 0) {
        MyTest myobj;  // 建立物件
        test();        // 內部呼叫 longjmp
    } else {
        printf("捕獲到異常\n");
    }
}

輸出:

構造一個 MyTest 類型的對象
析構銷毀一個 MyTest 類型的對象
捕獲到一個異常

看似正常,析構函數被正確呼叫。

案例二:物件在 longjmp 所在函數內建立

void test1() {
    MyTest myobj;      // 位置稍微移動
    longjmp(mark, 1);
}

輸出:

構造一個 MyTest 類型的對象
捕獲到一個異常

析構函數未被呼叫! 物件資源洩漏,若涉及鎖、檔案控制代碼等,將導致死鎖或系統崩潰。

根本原因分析

  • C++ 異常處理:展開堆疊時,編譯器生成的清理碼會依序呼叫所有已建構物件的析構函數(RAII)。
  • setjmp/longjmp:單純跳轉指令指標(EIP/RIP)並恢復暫存器,**完全繞過堆疊展開機制**,不執行任何清理碼。
  • 物件建構位置決定命運:只有在 setjmp 與 longjmp 之間、且編譯器能追蹤生命週期的作用域內建構的物件,才可能(視編譯器實現)獲得析構機會。跨函數、動態分配、或編譯器優化後,析構極大概率缺失。

最佳實踐建議

  1. 優先使用 C++ 原生異常處理(try/catch),確保 RAII 生效。
  2. 若必須混用:
    • 在 setjmp 點之前完成所有 C++ 物件建構,避免在 longjmp 跳轉路徑上有活躍的 C++ 物件。
    • 使用 std::unique_ptr 或 std::shared_ptr 管理資源,並在 longjmp 目標點手動清理(極不推薦,易出錯)。
    • 將 C 庫介面封裝為 C++ 類,內部隔離 setjmp/longjmp,對外拋出 C++ 異常。
  3. 編譯器層面:部分編譯器(如 GCC)提供 -fnon-call-exceptions 等選項嘗試支援,但不可移植,生產環境慎用。

結論:setjmp/longjmp 在 C++ 中屬於「核武器級」危險操作,除非極度確定無 C++ 物件生命週期介入,否則堅決避免使用。

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