C本身沒有異常機制。有時程式執行失敗時,系統會發出訊號進行處理。最常見的就是發生非法記憶體訪問時報段錯誤,有時候我們並不希望這時程式就終止退出了,而希望發生這種情況時給出自己的處理過程讓程式繼續運行下去。當然找到訪問錯誤的原因並改正更加重要,來個捕獲處理卻不失為一種救火的方法。
實現的一個思路是使用setjmp/longjmp組合,當捕捉到一個訊號時,進入訊號捕捉函數,處理後繼續執行。然而使用這對組合後,此時當前訊號被自動地加到進程的訊號屏蔽字中,這阻止了後來產生的這種訊號中斷該訊號處理常式。如果用longjmp跳出訊號處理常式,訊號屏蔽字不一定被恢複(各個發行版處理策略不同),從而再次執行到這裡時我們自訂的處理過程失效。
舉例
來個非法訪問記憶體的程式。試圖訪問低地址的記憶體空間,os是不會允許的
1 #include<stdio.h> 2 #include<stdlib.h> 3 4 #include <setjmp.h> 5 #include <signal.h> 6 jmp_buf Jump_Buffer; 7 8 #define BLOCK "block" 9 10 static void sig_segv(int signo) {11 signal(SIGSEGV, sig_segv);12 if (signo == SIGSEGV){13 printf("sigsegvvvvvvvv\n");14 longjmp(Jump_Buffer, 1);15 }16 }17 18 int fun(){19 int addr = 1;20 char name[10];21 if (signal(SIGSEGV, sig_segv) != sig_segv)22 signal(SIGSEGV, sig_segv);23 if(setjmp(Jump_Buffer) != 0){//這裡設定後longjmp將跳到這裡24 printf("exceptions\n");25 strcpy(name, BLOCK);26 }27 else{28 strncpy(name, (char*)addr, 10);29 }30 name[9] = '\0';31 32 printf("%d,%d,%s\n", sizeof(BLOCK), strlen(BLOCK), name);33 return 0;34 }35 36 int main(){37 fun();38 fun();39 40 return 0;41 }
預期我們希望執行strncpy出錯時能跳到預設的處理過程中,達到C++異常的效果。然而,第二次調用fun()卻仍然報段錯誤,如下
所幸的是,POSIX.1定義了另一對函數:sigsetjmp和siglongjmp。
sigsetjmp多了一個參數savemask,如果savemask非0,則sigsetjmp在env中儲存進程的當前訊號屏蔽字。調用siglongjmp時,如果帶非0savemask的sigsetjmp調用已經儲存了env,則siglongjmp從其中恢複儲存的訊號屏蔽字。這就解決了迴圈中調用setjmp,反覆調用longjmp時,僅第一次longjmp生效的問題。
修改如下
1 #include<stdio.h> 2 #include<stdlib.h> 3 4 #include <setjmp.h> 5 #include <signal.h> 6 jmp_buf Jump_Buffer; 7 8 #define BLOCK "block" 9 10 static void sig_segv(int signo) {11 signal(SIGSEGV, sig_segv);12 if (signo == SIGSEGV){13 printf("sigsegvvvvvvvv\n");14 siglongjmp(Jump_Buffer, 1);//使用了siglongjmp15 }16 }17 18 int fun(){19 int addr = 1;20 char name[10];21 if (signal(SIGSEGV, sig_segv) != sig_segv)22 signal(SIGSEGV, sig_segv);23 if(sigsetjmp(Jump_Buffer, 1) != 0){//這裡使用sigsetjmp,有第二個參數24 printf("exceptions\n");25 strcpy(name, BLOCK);26 }27 else{28 strncpy(name, (char*)addr, 10);29 }30 name[9] = '\0';31 32 printf("%d,%d,%s\n", sizeof(BLOCK), strlen(BLOCK), name);33 return 0;34 }35 36 int main(){37 fun();38 fun();39 40 return 0;41 }
執行結果如下
達到了預期的目標。