最近在面試時被考官問了一題,如何用C實作出意外處理的機制?

在其他語言,exception都有其實作的辦法,例如C++中的try-catch語句,但是在C中並沒有實作這樣功能,因此必須使用一些小手段來達成這個目的,我們寫的函式庫交於他人使用時,不至於一點錯誤就當掉。

方法一: 傳入error變數記錄錯誤狀況

我們要在傳入function的變數中,多傳遞一個error變數,儲存錯誤代碼,當function執行結束後,我們檢查這個變數,再對應相對的處理方案。

一下我們用除法作為一個示範,當除數為0時我們要拋出意外,而其他時候則正常執行。

實作function

    
int div(int dividend, int divisor, int *err) {
    if(divisor==0) {    // 發生錯誤
        *err = 1;       // 錯誤代碼
        return;
    }
    else {              // 正常執行
        *err = 0;
        return dividend/divisor;
    }
}

使用此function

    
int err = 0;
int dividend = 20;
int divisor = 0;
int result = div(dividend, divisor, &err);  // 執行這個function
switch(err) {
    case 0:
        // 正常執行時的情況
        break;
    case 1:
        // 除數為0的錯誤處理
        break;
}

方法二: 使用setjmp與longjmp方法

在C中的goto僅能在函式內部進行跳轉,如果想要跳轉到函式外部則必須使用setjmp與longjmp的組合,使用setjmp設定錨點,再利用longjmp進行跳轉的動作,我們可以使用這個強制跳轉的特性來做到意外處理的機制。

首先要知道什麼是一個callstack

    
void layer1(int i) {
    return layer2(i)*2;
}

void layer2(int i) {
    return 100/i;
}

int main() {
    layer1(x);
}

如上面的程式碼,我們依序的執行main->layer1->layer2,因此call stack長這樣 Image 注意的是當程式碼跳轉的時候記憶體的洩漏問題,例如我們在layer2突然跳回main,則layer1的記憶體就洩露了,因此我們可以依賴jmp_buf變數來幫我們自動處理這個問題。

因此一個含有意外處理的程式應該會長這樣

    
#include <stdio.h>
#include <setjmp.h>

jmp_buf jmpbuffer; // 用來儲存程式跳轉期間的資訊

void layer1(int i) {
    return layer2(i)*2;
}

void layer2(int i) {
    if(i==0) longjmp(jmpbuffer, 1); // 發生錯誤時,跳轉回main
    return 100/i;
}

int main() {
    int jmpVal = setjmp(jmpbuffer);
    if(jmpVal==0) {
        // 第一次執行時
        layer1(0);
    }
    else (jmpVal==1) {
        // 發生意外狀況時的處理
        fprintf(stderr, "divisor can't equal to 0\n");
    }
    // 繼續執行下面的程式
}

References:
1. How to throw an exception in C?