在Linux中編程的時候 有時候 try catch 可能滿足不了我們的需求。因為碰到類似數組越界 ,非法內存訪問之類的 ,這樣的錯誤無法捕獲。下面我們介紹一種使用捕獲信號實現的異常 用來保證諸如段錯誤之類的錯誤發生時程序不會崩潰,而是跳過代碼繼續執行。首先我們來看看發生段錯誤之后系統的處理。
發生段錯誤后系統會拋出 SIGSEGV 信號 ,之后 調用默認的信號處理函數 ,產生core文件 ,然后關閉程序 。
那有沒有一種辦法可以保證程序不會死掉呢,當然是有的 。首先我們想到的是 截獲改信號,調用自己的信號處理函數 。
讓我們來看看signal 這個函數 。
1 #include <signal.h> 2 #include <setjmp.h> 3 #include <stdarg.h> 4 #include <stdlib.h> 5 #include <stdio.h> 6 //信號處理函數 7 void recvSignal(int sig) 8 { 9 printf("received signal %d !!!\n",sig); 10 } 11 int main(int argc,char** argv) 12 { 13 //給信號注冊一個處理函數 14 signal(SIGSEGV, recvSignal); 15 int* s = 0; 16 (*s) = 1; 17 //以上兩句用來產生 一個 傳說中的段錯誤 18 while(1) 19 { 20 sleep(1); 21 printf("sleep 1 \n"); 22 } 23 return 0; 24 }
int setjmp(jmp_buf env); 這個函數 將上下文 ,就是cpu和內存的信息保存到env中 (不用去理解 jmp_buf,就當我們平時用的buff好了),然后調用 void longjmp(jmp_buf env, int val); 的時候 跳轉到使用env中的信息 ,恢復上下文 。如果是第一回調用setjmp 它會返回 0,如果是在 從longjmp 跳轉過來的 ,那就返回 longjmp的參數 val,根據setjmp的返回值 我們就可以決定執行可能發生錯誤的代碼還是直接跳過這段代碼 。知道了原理之后 我們可能就會這樣寫
1 #include <signal.h> 2 #include <setjmp.h> 3 #include <stdarg.h> 4 #include <stdlib.h> 5 #include <stdio.h> 6 jmp_buf env; 7 //信號處理函數 8 void recvSignal(int sig) 9 { 10 printf("received signal %d !!!\n",sig); 11 longjmp(env,1); 12 } 13 int main(int argc,char** argv) 14 { 15 16 //保存一下上下文 17 int r = setjmp(env); 18 if( r == 0) 19 { 20 //初次執行 ,那么可以執行 可能會發生錯誤的代碼 21 //給信號注冊一個處理函數 22 signal(SIGSEGV, recvSignal); 23 printf("excute this code!!"); 24 int* s = 0; 25 (*s) = 1; 26 } 27 else 28 { 29 //是由longjmp 跳轉回來的 30 printf("jump this code !!"); 31 } 32 while(1) 33 { 34 sleep(1); 35 printf("sleep 1 \n"); 36 } 37 return 0; 38 }
編譯 ,執行 產生 SIGSEGV 信號 ,然后在信號函數 里邊跳轉 到 int r = setjmp(env); 這一行 ,之后 直接略過了 可能發生錯誤的這段代碼 ,跳轉生效,可是這種方式還有一個bug,我們看看下面的代碼
1 #include <signal.h> 2 #include <setjmp.h> 3 #include <stdarg.h> 4 #include <stdlib.h> 5 #include <stdio.h> 6 jmp_buf env; 7 //信號處理函數 8 void recvSignal(int sig) 9 { 10 printf("received signal %d !!!\n",sig); 11 longjmp(env,1); 12 } 13 int main(int argc,char** argv) 14 { 15 16 for(int i = 0; i < 2; i++) 17 { 18 //保存一下上下文 19 int r = setjmp(env); 20 if( r == 0) 21 { 22 //初次執行 ,那么可以執行 可能會發生錯誤的代碼 23 //給信號注冊一個處理函數 24 signal(SIGSEGV, recvSignal); 25 printf("excute this code!!"); 26 int* s = 0; 27 (*s) = 1; 28 } 29 else 30 { 31 //是由longjmp 跳轉回來的 32 printf("jump this code !!"); 33 } 34 sleep(5); 35 } 36 37 while(1) 38 { 39 sleep(1); 40 printf("sleep 1 \n"); 41 } 42 return 0; 43 }
1 #include <signal.h> 2 #include <setjmp.h> 3 #include <stdarg.h> 4 #include <stdlib.h> 5 #include <stdio.h> 6 // jmp_buf env; 7 //信號處理函數 8 void recvSignal(int sig) 9 { 10 printf("received signal %d !!!\n",sig); 11 siglongjmp(env,1); 12 } 13 int main(int argc,char** argv) 14 { 15 16 for(int i = 0; i < 2; i++) 17 { 18 //保存一下上下文 19 int r = sigsetjmp(env,1); 20 if( r == 0) 21 { 22 //初次執行 ,那么可以執行 可能會發生錯誤的代碼 23 //給信號注冊一個處理函數 24 signal(SIGSEGV, recvSignal); 25 printf("excute this code!!"); 26 int* s = 0; 27 (*s) = 1; 28 } 29 else 30 { 31 //是由longjmp 跳轉回來的 32 printf("jump this code !!"); 33 } 34 sleep(5); 35 } 36 37 while(1) 38 { 39 sleep(1); 40 printf("sleep 1 \n"); 41 } 42 return 0; 43 }
編譯后 運行 。按照我們的需求 第二次進入for循環時, 發生段錯誤后程序不會死掉 ,而是會跳過這段代碼了繼續往下走 。下面我做了一個簡單的封裝 ,在錯誤發生時,我打印出了 錯誤信息 ,然后跳過錯誤的代碼
1 /* 2 ** file name CException.h 3 */ 4 #ifndef _CEXCEPTION_H_ 5 #define _CEXCEPTION_H_ 6 #include <setjmp.h> 7 #include <stdlib.h> 8 #include <stdarg.h> 9 #include <execinfo.h> 10 #include <stdio.h> 11 #include <signal.h> 12 #include <iostream> 13 #include <string.h> 14 typedef struct Except_frame 15 { 16 jmp_buf env; 17 int flag; 18 void clear() 19 { 20 flag = 0; 21 bzero(env,sizeof(env)); 22 } 23 bool isDef() 24 { 25 return flag; 26 } 27 Except_frame() 28 { 29 clear(); 30 } 31 }Except_frame; 32 extern Except_frame* except_stack; 33 extern void errorDump(); 34 extern void recvSignal(int sig); 35 Except_frame* except_stack = new Except_frame; 36 void errorDump() 37 { 38 const int maxLevel = 200; 39 void* buffer[maxLevel]; 40 int level = backtrace(buffer, maxLevel); 41 const int SIZE_T = 1024; 42 char cmd[SIZE_T] = "addr2line -C -f -e "; 43 char* prog = cmd + strlen(cmd); 44 readlink("/proc/self/exe", prog, sizeof(cmd) - (prog-cmd)-1); 45 FILE* fp = popen(cmd, "w"); 46 if (!fp) 47 { 48 perror("popen"); 49 return; 50 } 51 for (int i = 0; i < level; ++i) 52 { 53 fprintf(fp, "%p\n", buffer[i]); 54 } 55 fclose(fp); 56 } 57 58 void recvSignal(int sig) 59 { 60 printf("received signal %d !!!\n",sig); 61 errorDump(); 62 siglongjmp(except_stack->env,1); 63 } 64 #define TRY \ 65 except_stack->flag = sigsetjmp(except_stack->env,1);\ 66 if(!except_stack->isDef()) \ 67 { \ 68 signal(SIGSEGV,recvSignal); \ 69 printf("start use TRY\n"); 70 #define END_TRY \ 71 }\ 72 else\ 73 {\ 74 except_stack->clear();\ 75 }\ 76 printf("stop use TRY\n"); 77 #define RETURN_NULL \ 78 } \ 79 else \ 80 { \ 81 except_stack->clear();\ 82 }\ 83 return NULL; 84 #define RETURN_PARAM { \ 85 except_stack->clear();\ 86 }\ 87 return x; 88 #define EXIT_ZERO \ 89 }\ 90 else \ 91 { \ 92 except_stack->clear();\ 93 }\ 94 exit(0); 95 #endif
另外建一個文件 ,
1 #include "CException.h" 2 int main(int argc,char** argv) 3 { 4 //可以如下使用 5 TRY 6 int*s = 0; 7 (int*s) = 1; 8 END_TRY 9 //使用這兩個宏包含可能發生的錯誤代碼 ,當然可以根據需求 使用 10 //RETURN_NULL 11 //RETURN_PARAM(0) 12 //EXIT_ZERO 這三個宏 13 return 0; 14 }
這個時候我們就能使用TRY 和 END_TRY,RETURM_NULL,RETURN_PARAM(param) 來實現程序發生段錯誤后跳過錯誤代碼繼續運行了 ,不過此代碼僅限於單線程使用。
from:http://blog.csdn.net/work_msh/article/details/8470277