C 語言本身沒有原生的異常處理機制(如 C++ 的 try/catch),但可以通過 setjmp()(標記 “異常捕獲點”)和 longjmp()(拋出 “異常”)模擬異常處理流程。核心思路是:
- 用
setjmp()標記try塊的起始位置; - 用
longjmp()模擬throw拋出異常; - 通過
setjmp()的返回值區分 “正常執行” 和 “不同異常類型”。
一、核心設計思路
二、完整實現代碼
c
運行
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <string.h>
// ===================== 異常處理核心定義 =====================
// 定義異常類型(枚舉)
typedef enum {
EXCEPTION_NONE, // 無異常
EXCEPTION_DIV_ZERO, // 除零異常
EXCEPTION_NULL_PTR, // 空指針異常
EXCEPTION_OUT_OF_RANGE// 越界異常
} ExceptionType;
// 全局異常上下文(也可封裝為結構體,支持嵌套異常)
static jmp_buf exception_env;
// 存儲當前異常類型
static ExceptionType current_exception = EXCEPTION_NONE;
// 模擬 try 塊:標記異常捕獲點,初始化異常類型
#define try \
current_exception = EXCEPTION_NONE; \
if (setjmp(exception_env) == 0)
// 模擬 catch 塊:匹配異常類型
#define catch(except_type) \
else if (current_exception == except_type)
// 模擬 throw 語句:拋出指定類型的異常
#define throw(except_type) \
do { \
current_exception = except_type; \
longjmp(exception_env, 1); \
} while (0)
// 模擬 finally 塊(無論是否異常都執行)
#define finally \
if (1)
// ===================== 測試用函數 =====================
// 除法函數:觸發除零異常
int divide(int a, int b) {
if (b == 0) {
throw(EXCEPTION_DIV_ZERO); // 拋出除零異常
}
return a / b;
}
// 數組訪問函數:觸發空指針/越界異常
int get_array_val(int *arr, int size, int index) {
if (arr == NULL) {
throw(EXCEPTION_NULL_PTR); // 拋出空指針異常
}
if (index < 0 || index >= size) {
throw(EXCEPTION_OUT_OF_RANGE); // 拋出越界異常
}
return arr[index];
}
// ===================== 主函數測試 =====================
int main() {
// 測試1:除零異常
printf("=== Test 1: Division by zero ===\n");
try {
int res = divide(10, 0);
printf("Division result: %d\n", res); // 不會執行
} catch (EXCEPTION_DIV_ZERO) {
printf("Caught exception: Division by zero\n");
} finally {
printf("Finally block executed (Test 1)\n");
}
printf("\n");
// 測試2:空指針異常
printf("=== Test 2: Null pointer ===\n");
int *null_arr = NULL;
try {
int val = get_array_val(null_arr, 5, 2);
printf("Array value: %d\n", val); // 不會執行
} catch (EXCEPTION_NULL_PTR) {
printf("Caught exception: Null pointer\n");
} finally {
printf("Finally block executed (Test 2)\n");
}
printf("\n");
// 測試3:數組越界異常
printf("=== Test 3: Out of range ===\n");
int arr[] = {1, 2, 3, 4, 5};
try {
int val = get_array_val(arr, 5, 10); // 索引10越界
printf("Array value: %d\n", val); // 不會執行
} catch (EXCEPTION_OUT_OF_RANGE) {
printf("Caught exception: Out of range\n");
} finally {
printf("Finally block executed (Test 3)\n");
}
printf("\n");
// 測試4:無異常場景
printf("=== Test 4: No exception ===\n");
try {
int res = divide(20, 4);
printf("Division result: %d\n", res); // 正常執行
int val = get_array_val(arr, 5, 2);
printf("Array value: %d\n", val); // 正常執行
} catch (EXCEPTION_DIV_ZERO) {
printf("Caught exception: Division by zero\n");
} catch (EXCEPTION_NULL_PTR) {
printf("Caught exception: Null pointer\n");
} catch (EXCEPTION_OUT_OF_RANGE) {
printf("Caught exception: Out of range\n");
} finally {
printf("Finally block executed (Test 4)\n");
}
return 0;
}
三、輸出結果
plaintext
=== Test 1: Division by zero ===
Caught exception: Division by zero
Finally block executed (Test 1)
=== Test 2: Null pointer ===
Caught exception: Null pointer
Finally block executed (Test 2)
=== Test 3: Out of range ===
Caught exception: Out of range
Finally block executed (Test 3)
=== Test 4: No exception ===
Division result: 5
Array value: 3
Finally block executed (Test 4)
四、關鍵細節解析
1. 宏封裝的語義對應
|
自定義宏
|
對應 C++ 語義
|
作用
|
|
|
|
標記異常捕獲點, |
|
|
|
匹配 |
|
|
|
設置異常類型,跳回 |
|
|
|
無論是否異常,必執行的代碼塊
|
2. 異常上下文的存儲
- 示例中用全局變量
exception_env(jmp_buf類型)存儲跳轉上下文,current_exception存儲當前異常類型; - 若需支持嵌套異常(如
try塊內嵌套try),需將上下文封裝為棧結構(如jmp_buf數組 + 棧指針),避免全局變量覆蓋。
3. 資源清理(關鍵!)
c
運行
// 帶資源清理的示例
void test_resource() {
FILE *fp = NULL;
try {
fp = fopen("test.txt", "r");
if (fp == NULL) {
throw(EXCEPTION_NULL_PTR);
}
// 模擬文件操作異常
throw(EXCEPTION_OUT_OF_RANGE);
} catch (EXCEPTION_NULL_PTR) {
printf("Caught: File open failed\n");
} catch (EXCEPTION_OUT_OF_RANGE) {
printf("Caught: File operation error\n");
} finally {
// 無論是否異常,關閉文件
if (fp != NULL) {
fclose(fp);
printf("Resource cleaned: File closed\n");
}
}
}
五、擴展:支持嵌套異常
c
運行
#include <stdio.h>
#include <setjmp.h>
#define MAX_EXCEPTION_DEPTH 10 // 最大嵌套深度
jmp_buf exception_stack[MAX_EXCEPTION_DEPTH];
int exception_stack_ptr = -1; // 棧指針
ExceptionType current_exception;
// 重定義宏,支持嵌套
#define try \
do { \
if (exception_stack_ptr + 1 >= MAX_EXCEPTION_DEPTH) { \
fprintf(stderr, "Exception stack overflow\n"); \
exit(1); \
} \
exception_stack_ptr++; \
current_exception = EXCEPTION_NONE; \
if (setjmp(exception_stack[exception_stack_ptr]) == 0) {
#define catch(except_type) \
} else if (current_exception == except_type) {
#define throw(except_type) \
current_exception = except_type; \
longjmp(exception_stack[exception_stack_ptr], 1); \
} while (0)
#define finally \
} else {
#define end_try \
exception_stack_ptr--; \
} while (0)
// 嵌套測試
void nested_try() {
try {
try {
throw(EXCEPTION_DIV_ZERO);
} catch (EXCEPTION_DIV_ZERO) {
printf("Inner catch: Division by zero\n");
throw(EXCEPTION_NULL_PTR); // 向上拋出新異常
} end_try;
} catch (EXCEPTION_NULL_PTR) {
printf("Outer catch: Null pointer\n");
} end_try;
}
六、侷限性與注意事項
七、適用場景
- 嵌入式系統 / 內核開發(無 C++ 異常機制,需輕量級錯誤處理);
- 遺留 C 項目的錯誤處理重構(避免逐層返回錯誤碼);
- 簡單程序的異常模擬(無需引入 C++ 編譯環境)。
八、與 C++ 異常的對比
|
特性
|
setjmp/longjmp 模擬
|
C++ 原生異常
|
|
資源自動清理
|
不支持(需手動)
|
支持(析構函數)
|
|
異常類型繼承
|
不支持
|
支持(多態捕獲)
|
|
嵌套異常
|
需手動實現棧
|
原生支持
|
|
調試友好性
|
差(棧斷裂)
|
好(保留調用棧)
|
|
性能
|
略快(無額外開銷)
|
略慢(棧展開開銷)
|