中raise丟擲異常_C語言拾遺:使用setjmp實現異常機制
阿新 • • 發佈:2021-01-23
技術標籤:中raise丟擲異常
之前總結了一下 setjmp 的使用:
JuJu:C語言拾遺:setjmpzhuanlan.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/CLibgithub.com