C 語言中的 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
實現。
Husnain is a professional Software Engineer and a researcher who loves to learn, build, write, and teach. Having worked various jobs in the IT industry, he especially enjoys finding ways to express complex ideas in simple ways through his content. In his free time, Husnain unwinds by thinking about tech fiction to solve problems around him.
LinkedIn