C 語言中的 try...catch

Muhammad Husnain 2023年10月12日
  1. C 語言中的 Try-Catch
  2. 在 C 語言中將 Finally 新增到 Try-Catch
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 提供的兩個函式,longjmpsetjmp,它們可以從 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 時,呼叫環境中的狀態被複制到處理器,並返回儲存在 longjmpval 引數中的值。

對於一個簡單的 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 塊通常在 trycatch 塊完成後執行。

無論是否丟擲異常,它都會執行。

我們將如何實現這一點?關鍵思想是使用 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 實現。

Muhammad Husnain avatar Muhammad Husnain avatar

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