1. 程式人生 > 其它 >中raise丟擲異常_C語言拾遺:使用setjmp實現異常機制

中raise丟擲異常_C語言拾遺:使用setjmp實現異常機制

技術標籤:中raise丟擲異常

之前總結了一下 setjmp 的使用:

JuJu:C語言拾遺:setjmp​zhuanlan.zhihu.com

我個人在開發中實際上沒用過 setjmp 這個東西,但它畢竟存在在那裡。C語言標準庫的函式用指頭數的過來,所以想搞明白它。

這篇筆記打算總結一下,怎麼在C語言中實現異常。

異常是一個執行時錯誤,在C語言中,可以用assert來處理執行時錯誤,但它會直接abort掉程式。

面對異常,可以有三種選擇:

一種是不管它,那麼它就會abort程式。

一種是捕獲它,然後做特殊處理。

還有一種是在異常處理程式碼中重新丟擲它,讓更外層的異常處理程式碼處理它。

比如:

RAISE(Mem_failed);

這裡直接拋了一個異常物件 Mem_failed ,沒有異常處理程式碼,那麼程式輸出錯誤資訊,abort。

又比如:

TRY {
    RAISE(Mem_failed);
}
EXCEPT(Mem_failed) {
    /* catch */
}
END_TRY;

這裡捕獲了異常物件 Mem_failed ,那麼程式繼續往下執行。

異常物件可以簡單地定義成一個全域性的物件:

typedef struct ExceptObj {
    const char* name;
} ExceptObj ;

ExceptObj Mem_failed = { "Out of memory" };

TRY是程式碼的起始點,在RAISE一個異常的時候,要跳回此處。用一個數據結構表示它,起名叫異常幀:

typedef struct ExceptFrame {
    jmp_buf env;
    ExceptObj *except;
    const char *file;
    int line;
    int status;
} ExceptFrame;

env 是setjmp 和longjmp使用的,status是longjmp的返回值,從而表達一個異常情況,取值範圍定義成:

enum { EXCEPT_ENTER, EXCEPT_RAISE, EXCEPT_HANDLE };

file 和 line 在RAISE異常時,被設定成此時的檔名和行號。

現在程式碼可以寫成這樣:

ExceptFrame frame;
frame.status = setjmp(frame.env);
if (frame.status == EXCEPT_ENTER) {
    // our normal code, raise an exception
    frame.file = __FILE__;
    frame.line = __LINE__;
    frame.except = &Mem_failed;
    longjmp(frame.env, EXCEPT_RAISE);
}
else if (&Mem_failed == frame.except) {
    frame.status = EXCEPT_HANDLE;
    // our handle code
}

// uncaught, abort
if (frame.status == EXCEPT_RAISE) {
    fprintf(stderr, "an exception %s occured at %s:%dn", frame.except->name, frame.file, frame.line);
    fflush(stderr);
    abort();
}

else if 分支捕獲了異常,並設定好了處理狀態。如果沒有,那麼最後就會被abort。

完整的測試程式碼如:

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

enum { EXCEPT_ENTER, EXCEPT_RAISE, EXCEPT_HANDLE };

typedef struct ExceptObj {
    const char* name;
} ExceptObj ;

ExceptObj Mem_failed = { "Out of memory" };

typedef struct ExceptFrame {
    jmp_buf env;
    ExceptObj *except;
    const char *file;
    int line;
    int status;
} ExceptFrame;

int main()
{
    ExceptFrame frame;
    frame.status = setjmp(frame.env);
    if (frame.status == EXCEPT_ENTER) {
        // our normal code, raise an exception
        frame.file = __FILE__;
        frame.line = __LINE__;
        frame.except = &Mem_failed;
        longjmp(frame.env, EXCEPT_RAISE);
    }   
    /*else if (&Mem_failed == frame.except) {
        frame.status = EXCEPT_HANDLE;
        // our handle code
    }*/

    // uncaught, abort
    if (frame.status == EXCEPT_RAISE) {
        fprintf(stderr, "an exception %s occured at %s:%dn", frame.except->name, frame.file, frame.line);
        fflush(stderr);
        abort();
    }   

    printf("Happy enddingn");

    return 0;
}

但這樣的處理只能是玩具,沒有實用價值,因為程式碼編寫的很繁瑣,而且不能 RERAISE ,也沒有清理機制。

這裡有一個比較完善的程式碼可供參考,由於使用了巨集造成了大量扭曲程式碼,就不做相關筆記了。

demon90s/CLib​github.com ba67ffd8868c6ca3af0d8bc217ff392a.png