C异常处理机制:setjmp和longjmp

setjmp()和longjum()是通过操纵过程活动记录实现的。它是C语言所独有的。它们部分你不了C语言有限的转移能力。这个两个函数协同工作,如下所示:
    *setjmp(jmp_buf j)必须首先被调用。它表示“使用变量j记录现在的位置。函数返回零。”
    *longjmp(jmp_buf j,int i)可以接着被调用。它表示“回到j所记录的位置,让它看上去像是从原来的setjmp()函数返回一样。但是函数返回i,使代码知道它实际上是通过longjmp()返回的。“坳口不?
    *当使用longjmp()时,j的内容被销毁。
    setjmp保存了一份程序的计数器和当前的栈顶指针。如果喜欢也可以保存一些初始值。longjmp恢复这些值,有效的转移控制并把状态重置回保存状态 的时候。这被称做“展开堆栈(unwinding stack)",因为你从堆栈中展开过程活动记录,直到取得保存在其中的值。尽管longjmp会导致转移,但它和goto又有不同,区别如下:
    *goto语句不能跳出C语言当前的函数(这也是“longjmp”取名的由来,它可以跳的很远,甚至可以跳到其他文件的函数中)。
    *用longjmp只能跳回到曾经到过的地方。在setjmp的地方仍留有一个过程活动记录。从这个角度讲,longjmp更像是“从何处阿里(come from)“而不是”往哪里去(go to)”。longjmp接受一个额外的整型参数并返回它的值,这可以知道是由longjmp转移到这里的还是从上条语句执行后自然而然来的这里的。
    下面的代码显示了setjmp()和longjmp()一例。

#include <stdio.h>
#include <setjmp.h>
 jmp_buf buf;
  
banana()
 {
         printf("%s","in banana() \n");
         longjmp(buf,1);
         printf("%s","you will never see this \n");
 }
  
 int main()
{
         if(setjmp(buf))
         {
             printf("%s","back in main\n");
         }
         else
         {
             printf("%s","first time throught\n");
             banana();
         }
 }

 


    输出结果如下:
    first time throught
    in banana()
    back in main
    需要注意的地方是:保证局部变量在longjmp过程中一直保持它的值的唯一可靠方法是把它声明为volatile(这使用于那些值在setjmp执行和longjmp返回之间会改变的变量)
    setjmp/longjmp最大的用途是错误恢复。只要还没有从函数中返回,一旦发现一个不可恢复的错误,可以把控制转移到主输入循环,并从那里重新开 始。有些人使用setjmp/longjmp从一串无数的函数调用中立即返回。还有些人用它们防范潜在的危险代码。
    setjmp/longjmp在C++中演变为更普通的异常处理机制"catch"和"throw"。


http://www.cnblogs.com/doctorqbw/archive/2012/05/10/2495195.html


哇塞,C语言有try catch吗?当然没有。倒。。可能有人说了,那你野鬼说没有的东西做什么。 

    这里需要重申一下,所谓正向设计下问题检测的开发方法。正向设计时,在错误检测和问题修复的方法是指:

      根据源码分析,在源码中加插检测代码的方式,验证对代码的理解和预判是否正确。

     而反向跟踪是根据机器执行动作,反向理解逻辑的运行状态,例如GDB。两者很多方面都很像,但存在一个最主要的区别在于,你是在先验的让程序运行判断是否符合先验,还是在程序的运行中理解代码的逻辑。

    例如正向设计的问题判断,如果通过try catch来处理,则表示你已经估计到这里是个潜在可能的错误,而通过运行来验证你的判断是否成立。错误的理解和判断是在对源码的分析上,反之,通过GDB或者其他IDE加断点的跟踪,属于反向检测运行状态来判断逻辑可能哪里出错。 
    可惜try catch在C标准里面并没有。这里,就通过指针访问的段错误,来设计一个类似try catch的小玩意。希望新手能通过这个写出其他的try catch。 
    首先我们要动点C标准里,signal和setjmp的东西,以及GNU C的 glibc函数sigsetjmp,siglongjmp。记得要注意哪些是标准里的,哪些不是,这对以后的平台移植很用作用,哪怕你不移植,但万一别人要移植,因为你没有区分好,导致别人到处找和平台相关的代码,最后这个代码别人用不了,你的代码又有何价值。 
    先说一下signal这个函数。这函数的作用就是类似对中断向量表的重载,其实是个重定位工作。中断向量表是什么意思,很简单,就是有一个表,表里面有一堆函数地址,如果对应的中断发生了,就跳转到该中断对应的中断向量表的对应存储区域的函数里(希望看这句话你别跟着念,眼睛也别花,哈)。如果不重定位这些函数,则会启用默认的函数。例如给打印个消息,然后让进程停止。如果通过signal 对这个中断向量表进行”重载“,则可以进入你指定的函数。而不会进入默认的方式。 
    简单举例吧,正常的程序,你在bash上执行 ctrl+C,会让程序停止。实际是怎么发生的呢? 
    1、键盘发现你按键了,则会发出一个信号,除了按键值本身。该信号是告诉CPU,我有事了,CPU的硬件如果不对这个信号视而不见的话,就会告诉OS,哦,有个硬中断发生。 
    2、此时,OS就对应的发出个软中断,linux下就是 SIGINT。 
    3、如果对应你当前进程的自己的”中断向量表“是默认情况,则会OS启动一个函数,这个函数会发出一个kill的工作,要求将你这个进程结束。 
    4、OS此时发现这个动作。就把你的进程给卡擦了。 
    而你如果用signal“重载”这个中断,则此时原先默认函数不会执行,你会发现,你的程序该做什么还是做什么,除非你的新函数仍然要求kill掉你这个进程。 
    那么对于段错误,也存在一个中断,是由谁产生的? MMU,硬件产生的。OS获取这个错误之后,就会到对应进程的“中断向量表”找你是否重载过这个对应的中断响应函数,当然这中间还有些其他工作比如中断屏蔽方面的检查工作等,我就不展开了。由此,如果你写了一个新的函数,则可以不用退出,可以处理些自己的想做的事情。 
    但是很讨厌的一个问题,如果是你的进程出错,而且出错的理由是对一个不正确的内存空间进行读写操作,无论你执行多少次,这个错误仍然存在。因为你的代码产生个由MMU发出的段错误的中断,此时你的代码被强行挂起来,就是说现在轮不到你玩了,然后OS处理对应的中断响应,就是你写的代码,而写的那个函数执行完毕后,如果不kill或者其他动作,你的代码还会被再次执行。那么你的进程会怎么执行呢?在你上次错的地方继续执行。。。。这就郁闷大发了。因为再次产生个错误,此时又会进入你的中断响应函数。 
    于是乎,你会发现,你的屏幕在不停的打印东西,如果你的那个函数内有条printf想提醒你,进入了这个函数了。 
    此时,我们就需要动点手段了。这里要谈一下setjmp ,和longjmp,最终会用到sigsetjmp,siglongjmp,注意,这两玩意不是C标准的,glibc支持,其他的一些C环境也支持,我不一一列出来了。可以查资料。 
    先说setjmp, longjmp。上下文这个词在OS里,特别进程管理部分经常提到,什么意思呢?简单说就是现场环境,不过只是CPU里面的,包括指令的位置(其实也是在寄存器里),常规寄存器里的内容(也包括堆栈指针寄存器),和外部存储器就是内存没有关系。 
    继续举例吧。 
    假设,剧场正在上演一个话剧,还没结束呢。结果通知,立刻清场,为什么,领导要来开会,你别问为什么,领导就是领导,优先级高,此时你怎么办,那就和观众商量一下,我们把现场记录下来,台词说哪也记录下来,等领导开完会咱们接着看,此时剧场的情况清点记录完毕,并清理干净,等领导开会,会开完了,再根据被打断时的场景进行恢复,则此时观众可以连贯的继续看下去。 
    另一个例子就是香港的赌王片,赌到高潮的时候,无论是正方的叛徒还是反方的卧底,无论是用刀还是用枪,反正把男一号给搞伤了,怎么办?封牌局,拿个罩子,把桌子罩起来,谁也别想改动这些牌的内容。等男二号上时在继续赌,牌是什么情况,肯定没有变过,无论中间穿插了多少其他镜头。假设刚好这个时候有跑龙套的要用桌子吃午饭,剧组同意了,把桌子给他用,但是桌子上面的东西则原封不动的挪到别的地方。等男二号来,再把跑龙套的赶走,恢复成前面的牌局。 
    这里剧场场景的记录并切换成领导的会议桌,以及桌子上的牌局挪动,让给跑龙套的吃午饭,都是叫做上下文切换。setjmp的作用就是保存当前进入setjmp函数时的环境。同时setjmp返回个值为0。以区别longjmp跳转到当前位置。类似函数调用函数,父函数需要保存的现场工作,否则子函数退出时,父函数也没办法正常继续工作啊。是不是。当然 setjmp所保存的东西必函数调用时保存的要多一些。其实setjmp没什么特色。我自己都写过对应汇编以实现特定硬件上的需求。就是一堆mov,把寄存器的值存到指定的位置再返回0。 
    而longjmp的意思是,可以在你的代码任意的位置,只要setjmp执行过以后的地方,直接跳,跳哪呢?就是跳到setjmp调用时的位置,这个跳哪的信息从哪得来的,就是setjmp的参数指向的一个BUF,你在这个BUF里面保存了当前地址。因此,如果多次setjmp同一个buf,则在跳到最后一次,如果每次setjmp了不同的BUF,那么哪个BUF作为longjmp的参数,就是跳到对应setjmp的位置。此时等同于setjmp被返回,只不过返回值不为0,由此判断是longjmp过来的。继续举例子。 
    导演说,第N个镜头。。。然后就开始演,演了一半,穿帮了,导演说,停!此时就是longjmp,longjmp去哪?和你演的这段都没关系。直接到当前这个镜头的开始位置,为什么当前镜头可以拍N多次,就是因为你在镜头开始位置做了一个setjmp。 

    现在说下sigsetjmp siglongjmp。 
    sigsetjmp ,siglongjmp比setjmp ,longjmp的组合多了个中断屏蔽信息的存储。此时siglongjmp可以恢复到sigsetjmp出现时的中断屏蔽情况。在后面给出的代码的test10函数中,特地做了一个setjmp ,longjmp的方式,你会发现,第二次出错,并不会进入normal_longjmp函数。因此此时的中断配置等信息并没有对应记录下来,属于出错后的情况。 
     OK。现在说说setjmp longjmp有什么好处。 
    前面说了。signal对SIGSEGV这个中断的响应函数修改后,函数退出,会再次执行MMU发生错误的代码位置,因此我们要确保函数跳过当前出错的位置,执行到我们希望跳过的代码。类似C++的try catch那样,我们希望有try catch。如下 
    char *p = "1234"; 
    TRY 
    p[2] = '1';//这显然是错的嘛 
    CATCH ;// 
    则我们可以 
?
1
2
3
if ( setjmp (buf) == 0){
     p[2] = '1' ;
}


    OK了,为什么这样就可以了呢?因为如果不是longjmp过来的,setjmp始终返回0,则此时必然会执行p[2],如果p[2]不会产生SIGSEGV的错误,就不会执行longjmp,由此一切照旧,该做什么做什么。如果p[2] = '1'错误,则会发出中断信号 SIGSEGV,而假设我们把 下面SIGNAL_SEGV_DONE这个函数先前用 signal重载过,则此时发生的错误,会导致进入 SIGNAL_SEGV_DONE。如下 
?
1
2
3
4
void SIGNAL_SEGV_DONE( int signum){
     printf ( "SEG ERROR !\n" );
     longjmp (buf,1);
}


    注意这里第2个参数是返回的值,就是跳转到setjmp的位置,等同于setjmp返回的值,此时等于上面if的条件不成立,则等于跳出了{}。 
    而如果新手还是想不同,这个1怎么就被返回到setjmp的地方,而且像函数返回一样呢?我就说两个事情。
    1、函数的返回值是放在指定寄存器里的,比如ARM是放在r0里的,子函数把要return的值放在r0 里,返回父函数,则父函数对子函数的返回值直接可以从r0里取得(或者不取,如果不存在返回,或者暂时不需要利用这个函数的返回值) 
    2、一个函数调用另一个函数,没什么深奥的技巧。就是在返回时把寄存器,包括指令寄存器等等恢复成调用前的情况。唯一是指令寄存器还要再加一下,跳过函数调用的那条指令,然后一个跳转,就回到父函数了。
    下面给出代码,我是基于malloc_free上面进行的添加,你可以对比第九部分的代码差异。需要非常明确注意的以下几点 
    1、这里使用的方式,并不完全等同C++的TRY CATCH.但是机理是一样的。和GDB里面的断点中断也是一样的,只不过后者使用了SEGTRAP 这个中断信号 
    2、signal在注册函数时只需要一次,你别傻傻的如我的DEMO一样,放在检测的函数里。 
    3、我这里是为了尽可能只用头文件,所以用了static jmp_buf SIGSEGV_buf;做成全局变量也没有关系。 
    4、如下,对SIGSEGV_BEGIN 和 SIGSEGV_END的使用一定要加宏判断。我暂时没有想到比较好的解决方案,能把两个宏之间的代码描述能自动预编译剔除掉。 
?
1
2
3
4
5
6
#ifdef __MMDB_FLAG__
SIGSEGV_BEGIN(test) //there will be one func ,static void sigsegv_done_test(int signum){
     printf ( "you have a seciton error !\n" );
    
SIGSEGV_END()
#endif

以上代码,等同于 
?
1
2
3
4
5
6
7
8
static void sigsegv_done_test( int signum){
     if (signum != SIGSEGV){
         return ;
     }
     //you todo ....
     printf ( "you have a section error !\n" );
     siglongjmp(SIGSEGV_buf,1);
}
    test8是一个不进行中断函数注册的例子,你会发现什么事情都没有发生,和以前一样,因为没注册嘛。 
    test9是个标准的处理方案。 
    test10,说过了,让你区分longjmp,setjmp 和siglongjmp sigsetjmp的区别。 
    你可以通过执行 
    bin/test_malloc_free_main 10 

    的方式调用test10,其他雷同,如下是代码清单。

   malloc_free.h

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#ifndef _malloc_free_H_
#define _malloc_free_H_
#include <stdlib.h>
#define ALLOC_PAGE_SIZE 4096 //mininum malloc unit sizes ,not change
#define ALLOC_PAGE_MASK (ALLOC_PAGE_SIZE-1)
#define PAGE_SIZE_ALIGN(n) ((n) + ALLOC_PAGE_MASK) & (~(ALLOC_PAGE_MASK))
#define MALLOC_NUM_UNIT_SIZE (ALLOC_PAGE_SIZE / sizeof(void*))
#define MAX_MALLOC_UNIT_NUM 64 //not more than 4096*8
#define MAX_MALLOC_NUM  (MALLOC_NUM_UNIT_SIZE * MAX_MALLOC_UNIT_NUM) // max malloc times ,you can change
#define __MMDB_FLAG__
#ifndef __MMDB_FLAG__
//c_malloc c_free means malloc by check,not calloc,not type cmalloc!!!!!
#define c_malloc(a) malloc(a)
#define c_free(a) free(a)
#define MALLOC_FREE_INIT(...) do{}while(0)
#define _TYPE_INDEX_MALLOC_FREE(...)
#define _TYPE_COUNT_MALLOC_FREE(...)
#define CHECK_PTR_RANGE(...) (1)
#define GET_MALLOC_INDEX(...) do{}while (0)
#define CHECK_PTR_RANGE_ER(...) do{}while(0)
#if 0
#error "if not define __MMDB_FLAG__ ,this define how to done "
#define SIGSEGV_BEGIN(...) 
#define SIGSEGV_END(...) 
 
#endif
 
#define SIGNAL_SEGV(...) 
#define TRY_SEGV(...)
#define CATCH_SEGV(...)
 
#else
#include <setjmp.h>
#include <signal.h>
static jmp_buf SIGSEGV_buf; //every C file have one
#define SIGSEGV_FUNC(name) sigsegv_done_##name
#define SIGSEGV_BEGIN(name) static void SIGSEGV_FUNC(name)(int signum){ if (signum != SIGSEGV) {return;}
#define SIGSEGV_END() siglongjmp(SIGSEGV_buf,1);}
#define SIGNAL_SEGV(name) do {signal(SIGSEGV,SIGSEGV_FUNC(name));}while (0)  //no return no need check
#define TRY_SEGV() if (sigsetjmp(SIGSEGV_buf,1) == 0){
#define CATCH_SEGV(...) }
#define MALLOC_FREE_INIT malloc_free_init
#define _TYPE_COUNT_MALLOC_FREE(name) unsigned long name  = 0;
#define _TYPE_INDEX_MALLOC_FREE(name) void ** name;
#define CHECK_PTR_RANGE(p,indexP) ((indexP[0] <= (void *)(p)) && (indexP[1] > (void*)(p)))
#define GET_MALLOC_INDEX(p,indexP) do{indexP = get_malloc_index(p);}while (0)
#define CHECK_PTR_RANGE_ER(p,indexP,n,NAME) do {if (CHECK_PTR_RANGE(p,indexP)){n++;}else{set_check_ptr_range_error_exit(p,indexP,n,NAME);}} while (0)
 
//ins_inc_file
 
//ins_typedef_def
 
//ins_def
 
//ins_func_declare
void memory_free_init( void *);
void **get_malloc_index( void *ptr); //not used in code ,please used GET_MALLOC_INDEX define
void set_check_ptr_range_error_exit( void *p, void **index,unsigned long n, const char *str); //not used in code ,please used CHECK_PTR_RANGE_ER
void *c_malloc( size_t size);
void c_free( void *ptr);
 
#endif
 
 
#endif //_malloc_free_H_

malloc_free.c没有任何变化

一下是test_malloc_free_main.c的清单

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include "malloc_free.h"
#include <stdio.h>
 
typedef void (* TEST_FUNC)( void );
#ifdef __MMDB_FLAG__
SIGSEGV_BEGIN(test) //there will be one func ,static void sigsegv_done_test(int signum){
     printf ( "you have a seciton error !\n" );
     
SIGSEGV_END()
#endif
static void test8( void ){
     //test write to zero point
     int *p = 0;
//  SIGNAL_SEGV(test);
     TRY_SEGV();
     p[0] = 0;
     CATCH_SEGV();
}
static void test9( void ){
     //test write to zero point
     int *p = 0;
     SIGNAL_SEGV(test);
     TRY_SEGV();
     p[0] = 0;
     CATCH_SEGV();
}
 
#ifdef __MMDB_FLAG__
jmp_buf buf1;
#endif
void normal_longjmp( int signum){
     printf ( "there is normal_longjmp func!\n" );
     longjmp (buf1,1);
}
static void test10( void ){
#ifdef __MMDB_FLAG__
 
     int *p = 0;
     signal (SIGSEGV,normal_longjmp);
     printf ( "test10!\n" );
     if ( setjmp (buf1) == 0){
         p[0] = 0;
     } else { // section error ,jmp from normal_longjmp func
         printf ( "we come back from normal_longjmp func!\n" );
         p[0] = 0;
     }
#endif 
}
static void test0( void ){
     void *p1 = 0;
     char *p2 = 0;
     printf ( "test0\n" );
     p1 =( char *)c_malloc(4); //*4);
     p2 = ( char *)c_malloc(6);
     c_free(p1);
     c_free(p2);
     return ; //normal check
}
static void test1( void ){
     char *p1,*p2;
     p1 = ( char *)c_malloc(5);
     p2 = ( char *)c_malloc(6);
     c_free(p1);
     //cfree(p2);
     return ; //free lack check
}
static void test2( void ){
     char *p1,*p2,*p3;
     p1 = ( char *)c_malloc(5);
     p3 = p2 = ( char *)c_malloc(6);
     c_free(p1);
     c_free(p2);
     c_free(p3);
     return ; //free more check
}
static void test3( void ){
     char **pp = ( char **)c_malloc( sizeof ( char *)*MAX_MALLOC_NUM + 1);
     int i;
     for (i = 0 ; i <= MAX_MALLOC_NUM ; i++){
         pp[i] = ( char *)c_malloc(2);
     }
     return ; //alloc more check
}
static void test4( void ){
     char *p1,*p2,*p3;
     p1 = ( char *)c_malloc(5);
     p2 = ( char *)c_malloc(6);
     p3 = ( char *)c_malloc(6);
     c_free(p1);
     c_free(p2);
     c_free(p2);
     return ; //free twin check  
}
static void test5( void ){
     char *p1,*p2;
     p1 = ( char *)c_malloc(5);
     p2 = ( char *)c_malloc(6);
     c_free(p1);
     c_free(p2+3);
     return ; //free shift check 
}
static void test6( void ){
     char *p1,*p2;
     p1 = ( char *)c_malloc(5);
     p2 = ( char *)c_malloc(6);
     c_free(p1);
     c_free(0);
     return ; //free zero check  
}
 
static void test7( void ){
     char *p1,*p2;
     _TYPE_COUNT_MALLOC_FREE(ptr_count);
     _TYPE_INDEX_MALLOC_FREE(pindex);   
     int i;
 
     p1 = ( char *)c_malloc(5);
     GET_MALLOC_INDEX(p1,pindex);
     p2 = p1+ 3;
     for (i = 3 ; i< 10 ; i++,p2++){
         if (CHECK_PTR_RANGE(p2,pindex)){
             printf ( "p2 bias is %d , check ok\n" ,i);
         } else {
             printf ( "error bias is %d!\n" ,i);
         }
     }
     p2 = p1;
     for (i = 0 ; i< 10 ; i++,p2++){
         CHECK_PTR_RANGE_ER(p2,pindex,ptr_count, "test7 func p2" );
     }  
     
     c_free(p1);
     //c_free(0);
     return ; //free zero check  
}
 
#define TEST_MASK 15
TEST_FUNC test_a[TEST_MASK+1]= {test0,test1,test2,test3,test4,test5,test6,test7
,test8,test9,test10,test8,test8,test8,test8,test8};
     
 
int main( int argc, char *argv[]){
     int mode;
 
     printf ( "hello test_malloc_free_main now run...\n" );
     MALLOC_FREE_INIT(0);
     if (argc < 2){
         printf ( "need parameters!\n" );
         return 1;
     }
     mode = atol (argv[1]); //argv[1][0] - '0';
     test_a[mode & TEST_MASK]();
     
     printf ( "hello test_malloc_free_main now exit...\n" );
     return 0;
}

希望新手不要怀疑,为什么我刚学习C,就得碰longjmp和信号方面的知识。没办法啊,本来想先说IPC的,进程之间的通信,采用socket。但是如果你尝试write一个内容,而对应管道实际已经被关闭,会导致你的write 对应的进程自动退出。上面这些方法不用,我们很难继续下去。

新手你就准备开始多读读计算机组成原理,和操作系统的知识吧。谁让你没事找事要学C呢?活该!!!我没别的话可说了。

http://www.oschina.net/question/249672_50022


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值