在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) 来实现程序发生段错误后跳过错误代码继续运行了 ,不过此代码仅限于单线程使用。