linux c语言编程,使用setjmp和longjmp函数自制类似python的try-catch模块捕获异常,建立栈数据结构,实现多级嵌套使用(2)
发表于: 2018-10-23 20:09:07 | 已被阅读: 21 | 分类于: Linux笔记
- 覆盖了原有的信号处理函数。
- 信号处理函数不能传递参数,因此只能使用全局变量,但是这样一来,模拟 try 功能就不能支持嵌套使用了。
本节将尽力解决这两个问题。下面再贴一下上一节的代码。
#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
jmp_buf mark;
void sigHandle(int sig)
{
siglongjmp(mark, -1);
}
int main()
{
int ret = 0, a = 0;
signal(SIGFPE, sigHandle);
ret = sigsetjmp(mark, 1);
if(0==ret){
a = 8/0;
printf("ret == 0 is true\n");
}else{
printf("ERROR: division by zero\n");
}
return 0;
}
为什么会覆盖原有的信号处理函数
这个问题真的非常简单,假设之前程序定义和绑定了其他的
为什么用全局变量保存现场(env ),为什么不能支持嵌套调用?
一句话,这是因为 linux 的信号处理函数不接受额外的参数,也无法接收出信号外的其他参数。
请看信号处理函数
...
ret = sigsetjmp(mark, 1);
if(0==ret){
ret2 = sigsetjmp(mark, 1); // 嵌套保存现场
a = 8/0;
printf("ret == 0 is true\n");
}else{
printf("ERROR: division by zero\n");
}
...
第一次保存的现场,被第二次的
ret2 = sigsetjmp(mark, 1); // 嵌套保存现场
这并不是我们期望的。
解决办法
知道了问题的症结所在,就可以解决它了。
- 记录原信号处理函数,在捕获异常结束后,再改回去。这样就解决了 C 语言模拟 try 功能会覆盖原信号处理函数的问题。
- 建立多元全局变量,将每次
sigsetjmp
保存的现场环境分开保存,就有望解决因现场被覆盖而不能嵌套调用的问题。
经过上面的分析,现知道不仅要保存现场环境,还需要保存其他信息,所以,可以如下建立结构体(LCC是我的名字缩写,没别的意思):
typedef struct __LCC_TRY
{
jmp_buf mark;
int markRet;
int sig;
struct sigaction oldAct;
}LCC_TRY;
mark 和 markRet 用于记录
因为要支持多级嵌套使用,所以
linux c 语言利用循环数组建立环形栈
不多话,直接放出代码:
#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include "lcc_try.h"
// ****************
LCC_STACK g_lcc_try_stack = {0}; // = {0} 就相当于 init 了
void lcc_stack_init(LCC_STACK* s)
{
s->top = 0;
s->pushTimes = 0;
memset(s->tryInfo, 0, sizeof(LCC_STACK));
}
int lcc_stack_push(LCC_STACK* s, LCC_TRY* iTryInfo)
{
memcpy(&(s->tryInfo[(s->top++)%LCC_TRY_MAX_NEST]), iTryInfo, sizeof(LCC_TRY));
s->pushTimes = (s->pushTimes<LCC_TRY_MAX_NEST)?(s->pushTimes+1):LCC_TRY_MAX_NEST;
return 1;
}
int lcc_stack_pop(LCC_STACK* s, LCC_TRY* oTryInfo)
{
if( 0<s->pushTimes )
s->pushTimes--;
else{
printf("ERROR: pop empty stack\n");
exit(-1);
}
memcpy(oTryInfo, &(s->tryInfo[(--s->top)%LCC_TRY_MAX_NEST]), sizeof(LCC_TRY));
return 1;
}
void __lcc_SigHandle(int sig)
{
LCC_TRY tmp = {0};
lcc_stack_pop(&g_lcc_try_stack, &tmp);
siglongjmp(tmp.mark, -1);
}
int lcc_stack_set_try(LCC_STACK* s, LCC_TRY* iTryInfo)
{
memcpy(&(s->tryInfo[(s->top)%LCC_TRY_MAX_NEST]), iTryInfo, sizeof(LCC_TRY));
return 0;
}
int lcc_stack_get_try(LCC_STACK* s, LCC_TRY* oTryInfo)
{
memcpy(oTryInfo, &(s->tryInfo[(s->top)%LCC_TRY_MAX_NEST]), sizeof(LCC_TRY));
return 1;
}
代码非常简单,也比较粗糙,抛砖引玉了。建立的
typedef struct __LCC_STACK
{
LCC_TRY tryInfo[LCC_TRY_MAX_NEST];
int top;
unsigned int pushTimes;
}LCC_STACK;
C语言模拟python的try-catch功能
既然使用C语言模拟python的
// 这里两个细节,一是利用函数形参做局部变量。一是利用 if(a&&b)的 a,b 执行顺序和执行个数。
#define lcc_try(tryInfo, sigs) \
sigaction((sigs), NULL, &((tryInfo).oldAct)); \
signal((sigs), __lcc_SigHandle); \
(tryInfo).sig = (sigs); \
(tryInfo).markRet = sigsetjmp((tryInfo).mark, 1); \
if(0!=(tryInfo).markRet){ \
lcc_stack_get_try(&g_lcc_try_stack, &(tryInfo)); \
(tryInfo).markRet = -1;} \
if( ( \
(lcc_stack_push(&g_lcc_try_stack, &(tryInfo))) \
)&& \
(0==(tryInfo).markRet) )
#define lcc_catch(tryInfo) \
else if( (lcc_stack_pop(&g_lcc_try_stack, &(tryInfo)))&& \
(lcc_stack_push(&g_lcc_try_stack, &(tryInfo)))&& \
(0!=(tryInfo).markRet) )
#define lcc_try_end(tryInfo) \
lcc_stack_pop(&g_lcc_try_stack, &(tryInfo)); \
signal((tryInfo).sig, (tryInfo).oldAct.sa_handler);
测试
经过宏定义的封装,终于有点像 try 功能了。下面放出一段测试代码:
#include <stdio.h>
#include <signal.h>
#include "pthread.h"
#include "unistd.h"
#include "lcc_try.h"
void* thread(void* param)
{
LCC_TRY tryInfo;
char* p = NULL;
lcc_try(tryInfo, SIGSEGV){
printf("thread running...\n");
*p = 1;
}lcc_catch(tryInfo){
printf("thread ERROR\n");
}lcc_try_end(tryInfo);
}
void defHandle(int sig)
{
printf("recieved SIGSEGV!\n");
raise(9);
}
int main()
{
char* buf = NULL;
int tmp = 0;
LCC_TRY tryInfo;
signal(SIGSEGV, defHandle); // 设定默认的信号处理函数
putchar('\n');
lcc_try(tryInfo, SIGSEGV){
printf("main running...\n");
lcc_try(tryInfo, SIGFPE){
printf("inner 1 running...\n");
tmp = 8/0;
}lcc_catch(tryInfo){
printf("inner 1 ERROR\n");
}lcc_try_end(tryInfo);
*buf = NULL;
}lcc_catch(tryInfo){
printf("main ERROR\n");
}lcc_try_end(tryInfo);
pthread_t pid;
pthread_create(&pid,NULL,thread,NULL);
sleep(1);
printf("finished\n\n");
*buf = 1; // 测试是否恢复为原来的信号处理函数
return 0;
}
编译,执行之,得到结果如下:
$ ./a.out
main running...
inner 1 running...
inner 1 ERROR
main ERROR
thread running...
thread ERROR
finished
recieved SIGSEGV!
Killed
可以看出,我们成功了。嵌套的异常被成功捕获了,线程里的异常也被捕获了。
还有问题吗
当然是有的,既然使用了全局变量,那么就应该考虑在多线程,多进程的情况下的竞争问题。
相信能解决,这篇文章就到这里了。