HOWTO · C
C 語言中的 try...catch
本文演示了一個在 C 語言中實現 try-catch 行為的示例,預設情況下,它不支援 try-catch/異常處理機制。
Try-Catch 機制在 Python、C++ 和 JavaScript 等許多程式語言中都很常見。一般結構如下。
try {
/*
Insert some lines of code that will probably give you errors
*/
} catch {
/*
Write some code to handle the errors you're getting.
*/
}
它們允許你編寫程式碼而無需測試每個語句。如果在 try 塊中執行的程式遇到異常,則將異常傳遞給 catch 塊。
如果異常與某些異常型別匹配,則執行 catch 塊內的程式碼。否則,異常將傳遞迴 try 塊。
C 語言中的 Try-Catch
C 不支援異常處理。至少,它沒有任何內建機制。
本指南將演示在 C 語言中提供 try-catch 功能的可能解決方案。應該注意,該解決方案不一定是完整的。
如果沒有在遍歷堆疊時釋放記憶體的機制,異常處理系統是不完整和安全的,並且 C 沒有垃圾收集器。我們可能還需要包含上下文管理器來釋放記憶體。
該解決方案不打算提供完整而廣泛的 try-catch 機制。這個概念在技術上可以用來處理一些異常。
我們將逐步構建解決方案,對程式碼進行更新。我們將使用 C 提供的兩個函式,longjmp 和 setjmp,它們可以從 setjmp.h 標頭檔案中獲得。
我們將仔細研究這兩個函式的定義。
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
setjmp 接受一個 jmp_buf 型別的變數。直接呼叫此函式時,它返回 0。
longjmp 接受兩個變數,當使用相同的 jmp_buf 變數呼叫 longjmp 時,setjmp 函式返回與 longjmp 的第二個引數(val)相同的值。
這裡的 env 變數本質上是呼叫環境,代表了暫存器的狀態和函式呼叫時在程式碼中的位置。當呼叫 longjmp 時,呼叫環境中的狀態被複制到處理器,並返回儲存在 longjmp 的 val 引數中的值。
對於一個簡單的 Try-Catch 塊,想法是將 Try 語句對映到 if 語句,然後 Catch 語句將成為條件的 else。在這裡,我們可以巧妙地利用 setjmp 可以返回不同值的事實。
如果函式返回 0,那麼我們知道唯一執行的程式碼是 TRY 塊中的程式碼。如果函式返回任何其他內容,我們需要以與開始時相同的狀態進入我們的 CATCH 塊。
當我們 THROW 異常時,我們可以呼叫 longjmp 函式。
正如你將在下面的程式碼中看到的,我們還需要關閉 TRY 塊。我們建立了一個 ENDTRY 函式,它提供了 do-while 塊的結束部分。
這也有助於我們在同一個塊中建立多個 TRY 語句。應該注意的是,它們不能巢狀,因為我們將重用 buf_state 變數。
下面是這個實現的一個例子。
#include <setjmp.h>
#include <stdio.h>
#define TRY \
do { \
jmp_buf buf_state; \
if (!setjmp(buf_state)) {
#define CATCH \
} \
else {
#define ENDTRY \
} \
} \
while (0)
#define THROW longjmp(buf_state, 1)
int main() {
TRY {
printf("Testing Try statement \n");
THROW;
printf(
"Statement should not appear, as the THROW block has already thrown "
"the exception \n");
}
CATCH { printf("Got Exception \n"); }
ENDTRY;
return 0;
}
輸出:
Testing Try statement
Got Exception
對於實際系統,這還不夠。我們需要有不同型別的異常。
上面的例子只支援一種異常。再一次,我們可以使用 setjmp 的不同返回值。
代替使用 if-else,我們將使用 switch-case 來切換它。
設計如下:TRY 語句將使用 switch 語句,CATCH 將是一個帶有引數的巨集,表示異常型別。每個 CATCH 語句的條件是它必須使用 break 關閉前一個 case。
#include <setjmp.h>
#include <stdio.h>
#define TRY \
do { \
jmp_buf buf_state; \
switch (setjmp(buf_state)) { \
case 0:
#define CATCH(x) \
break; \
case x:
#define ENDTRY \
} \
} \
while (0)
#define THROW(x) longjmp(buf_state, x)
#define EXCEPTION1 (1)
#define EXCEPTION2 (2)
#define EXCEPTION3 (3)
int main() {
TRY {
printf("Inside Try statement \n");
THROW(EXCEPTION2);
printf("This does not appear as exception has already been called \n");
}
CATCH(EXCEPTION1) { printf("Exception 1 called \n"); }
CATCH(EXCEPTION2) { printf("Exception 2 called \n"); }
CATCH(EXCEPTION3) { printf("Exception 3 called \n"); }
ENDTRY;
return 0;
}
輸出:
Inside Try statement
Exception 2 called
在 C 語言中將 Finally 新增到 Try-Catch
我們需要為完整的功能性 Try-Catch 實現新增一個 FINALLY 塊。finally 塊通常在 try 和 catch 塊完成後執行。
無論是否丟擲異常,它都會執行。
我們將如何實現這一點?關鍵思想是使用 switch 案例的 default 案例來實現 FINALLY 塊。
但是,如果在正常情況下呼叫了異常,則 switch-case 將不會執行 default 情況。
我們將使用類似於 Duff’s Device 的機制。我們基本上將把 switch-case 語句與 do-while 語句交織在一起。
它的邏輯是這樣的。
switch (an expression) {
case 0:
while (1) {
// code for case 0
break;
case 1:
// code for case 1
break;
}
default:
// code for default case
}
我們使用了 C 語言中最具爭議的特性之一:在每個 case 標籤之前開關不會自動斷開。在這裡,當呼叫 break 時,while 語句巢狀在 switch-case 中;它退出 while 迴圈並繼續遍歷案例。
在我們的程式碼上下文中(如下所示),switch 案例是我們所有的異常,default 案例在我們的 FINALLY 塊中。自然,它會落入預設情況,因為異常程式碼已經被執行。
這顯示在下面的程式碼中。
#include <setjmp.h>
#include <stdio.h>
#define TRY \
do { \
jmp_buf buf_state; \
switch (setjmp(buf_state)) { \
case 0: \
while (1) {
#define CATCH(x) \
break; \
case x:
#define ENDTRY \
} \
} \
while (0)
#define THROW(x) longjmp(buf_state, x)
#define FINALLY \
break; \
} \
default:
#define EXCEPTION1 (1)
#define EXCEPTION2 (2)
#define EXCEPTION3 (3)
int main() {
TRY {
printf("Inside Try statement \n");
THROW(EXCEPTION2);
printf("This does not appear as exception has already been called \n");
}
CATCH(EXCEPTION1) { printf("Exception 1 called \n"); }
CATCH(EXCEPTION2) { printf("Exception 2 called \n"); }
CATCH(EXCEPTION3) { printf("Exception 3 called \n"); }
FINALLY { printf("This will always be called! \n"); }
ENDTRY;
return 0;
}
輸出:
Inside Try statement
Exception 2 called
This will always be called!
用 C 語言製作 try-catch 系統的指南到此結束。當然,這裡可能存在記憶體問題和一些限制(例如缺乏對巢狀 try-catch 系統的支援),但這是一個 C 語言中的功能性 try-catch 實現。