c语⾔接⼝与实现--异常与断⾔(异常部分)的理解,含实例
1、 这部分对于初学者(包括我)来说是不太好理解的,我断断续续的看了⼏天时间才基本把“异常部分”看懂,把我个⼈的理解写下来,⼀是记录,⼆是希望能帮助到有同样困惑的⼈。
2、个⼈觉得⼈邮出版社2011年9⽉第1版在本章节中存在翻译错误,指出来⼤家⼀起看看,也可能是我错了。
3、本章节异常的处理机制是基于setjmp 和longjmp实现的,所以⼤家需要对setjmp使⽤有了解,如果不清楚请书中的例⼦
except_alloc.c,注释部分如果打开,则模拟allocate失败,调⽤跳转命令
#include <setjmp.h>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
int Allocation_handled = 0;
jmp_buf Allocate_Failed;
void * allocate(unsigned n)
{
void * new = malloc(n);
//new = NULL;
if(new)
return new;
if(Allocation_handled)
longjmp(Allocate_Failed, 1);
assert(0);
}
int main(void)
{
char *buff;
Allocation_handled = 1;
if(setjmp(Allocate_Failed))
{
fprintf(stderr, "couldn't allocate the buffer\n");
exit(EXIT_FAILURE);
}
buff = (char *)allocate(4096);
Allocation_handled = 0;
}
我们看setjmp函数的定义,函数原型如下
#include <setjmp.h>
int setjmp(jmp_buf env);
4、参数 env 即为保存上下⽂的 jmp_buf 结构体变量,如果直接调⽤该函数,返回值为 0; 若该函数从 longjmp 调⽤返回,返回值为⾮零,由 longjmp 函数提供。根据函数的返回值,我们就可以知道 setjmp 函数调⽤是第⼀次直接调⽤,还是由其它地⽅跳转过来的。
5、例⼦,if(setjmp(Allocate_Failed))执⾏后返回结果为0,跳过if的代码执⾏allocate,代码中为了模拟allocate失败,使⽤
new=NULL,程序执⾏到longjmp处,根据env=Allocate_Failed,跳到main函数的if(setjmp(Allocate_Failed))处执⾏,此时setjmp的返回值为longjmp(Allocate_Failed, 1)指定的1,所以if⾥⾯的语句执⾏,打印错误信息,exit退出函数。
6、接下来分析核⼼的except.h和except.c,实际c为了设计成和c++类似(还有java)的try catch结构,使⽤了宏定义将try catch等c语⾔中没有的关键字⽤宏定义的⽅式实现。
except.h
#ifndef EXCEPT_INCLUDED
#define EXCEPT_INCLUDED
#include <setjmp.h>
#define T Except_T
// 具体的错误原因或者错误标志,⽤于捕获错误时进⾏⽐对,const char* 字符串
// 书中给出例⼦ Except_T Allocate_Failed = {"Allocation failed"};
// 书中给出例⼦ Except_T Allocate_Failed = {"Allocation failed"};
typedef struct T
{
const char * reason;
}T;
// 此处的设计有点怪,其实如果换⼀种常⽤⽅式⼤家估计更好理解
/*
typedef struct Except_Frame{
struct Except_Frame *prev;
jmp_buf env;
const char *file;
int line;
const T *exception;
}Except_Frame;
这样是否更符合⼤家的使⽤习惯?
*/
typedef struct Except_Frame Except_Frame;
struct Except_Frame{
struct Except_Frame *prev;
jmp_buf env;
const char *file;
int line;
const T *exception;
};
/*
Except_Frame结构体包含 *prev,⼀个逆向的单向链表,通过链表的结尾单元来添加和删除;
书中将其视为栈是更加科学的描述,只需记住栈顶单元
jmp_buf env是上下⽂环境变量,即longjmp跳转的寻址⽬标;
const char *file 出错的⽂件,int line出错的⾏,const T*exception出错的具体信息,
后⾯三个变量是根据需要来设计的,你也可以有⾃⼰的变量设计,
⽐如 exception可以设计为int型的错误id
*/
/*
⽤枚举类型来定义程序执⾏中的错误处理(跳转)标志的⼏种状态,
Except_entered 必须为0,它等于第⼀次调⽤setjmp的返回值
Except_raised 表⽰错误产⽣,即执⾏过程中出现了错误
Except_handled 表⽰捕获的错误已处理
Except_finalized 表⽰异常处理结束
*/
enum
{
Except_entered = 0,
Except_raised,
Except_handled,
Except_finalized
};
// 外部定义的栈顶结构体,具体在except.c中定义
extern Except_Frame * Except_stack;
// 错误捕获函数,包含longjmp的调⽤,程序执⾏过程中出现异常,需要调⽤此函数来触发异常
void Except_raise(const T *e , const char *file, int line);
// 捕获异常的封装宏定义,加⼊了__FILE__和__LINE__,allocate中可以使⽤此函数替代
#define RAISE(e)  Except_raise( &(e), __FILE__, __LINE__)
// 如果RAISE捕获的异常未被处理,则执⾏RERAISE再次处理,直到异常栈Except_stack的栈顶为空NULL
// 宏定义中明确使⽤了Except_frame变量,所以必须与下⾯的#define TRY等⼀起使⽤,
// 否则程序报错:Except_frame undefined
#define RERAISE  Except_raise( ption, \
Except_frame.file, Except_frame.line)
// 通过宏定义的⽅式实现TRY EXCEPT(x) ELSE FINALLY END_TRY的⾃定义关键字
// 后⾯先讲⼀下整体的结构,然后再对每个宏定义的进⾏说明,书中也是这样进⾏的,如果我讲的不细的地⽅请对照书中的内容来看#define TRY do { \
#define TRY do { \
volatile int Except_flag;  \
struct Except_Frame Except_frame; \
Except_frame.prev = Except_stack; \
Except_stack = &Except_frame;  \
Except_flag = setjmp(v);  \
if (Except_flag == Except_entered)  {
#define EXCEPT(e) \
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
}else if (ption == &(e)) { \
Except_flag = Except_handled;
#define ELSE \
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
}else{ \
Except_flag = Except_handled;
#define FINALLY  \
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
}  \
{ \
if (Except_flag == Except_entered) \
Except_flag = Except_finalized;
#define END_TRY  \
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
} if (Except_flag == Except_raised) RERAISE; \
}while(0)
#define RETURN switch ( Except_stack = Except_stack-prev, 0) default: return
#undef T
#endif
下⾯对except.h的宏做重点介绍
7、TRY可以和后⾯⼏个任意组合,TRY必须要且是打头,END_TRY必须要且在结尾 书中列了⼏种结构,如TRY-EXCEPT TRY-FINALLY,do {}while(0) 是宏定义的常见⽤法,具体可以百度了解TRY…END-TRY核⼼结构是c语⾔的条件语句,判断准则是Except_flag的值
为了TRY EXCEPT(e) ELSE FINALLY 和END_TRY能够组合,这些定义总是将if的两个{}分开,简单⼀
点说更好理解
*******************
TRY = do{xxxx
if (xxx)
{
xxx
*******************
EXCEPT(e) = }else if (xxx)
{
xxx
********************
ELSE = }else
{
xxx
********************
FINALLY = } {
xxx
********************
END_TRY = } xxx
}while(0)
********************
这样⼦TRY打头 END_TRY结尾,中间⽆论插⼊EXCEPT(e) ELSE 或者FINALLY都可以是的if的{}闭环,
形成了if ..else if …else…的结构(else if 允许多个),其中FINALLY由于是必执⾏项,所以其代码程序只是⽤{}做了包含。
TRY的定义
#define TRY do { \
volatile int Except_flag;  \
struct Except_Frame Except_frame; \
Except_frame.prev = Except_stack; \
Except_stack = &Except_frame;  \
Except_flag = setjmp(v);  \ (1)
if (Except_flag == Except_entered)  {
Except_flag:定义异常状态标志 取值范围为之前enum枚举类型定义的值,此处定义为volatile ⾃动变量,告诉优化器不优化,此变量会被频繁修改,每次都要重新获取值,不能⽤优化过后的值。
Except_frame: 创建新的异常结构体变量
Except_frame.prev = Except_stack;
Except_stack = &Except_frame; : 这两⾏结合起来将新建的异常结构体变量设置为栈顶
Except_flag = setjmp(v); :设置跳转点,将返回的结果赋值给Except_flag,(具体上下⽂的环境属性保存在
v中)
if (Except_flag == Except_entered) { : 第⼀个if分⽀,第⼀次执⾏setjmp时返回的是0,因此条件成⽴,进⼊执⾏⽤户程序,书中指代为S; S执⾏如果⽆异常则需要跳转到FINALLY(如果有)或者END_TRY, 如果S执⾏有异常需要调⽤RAISE(e)跳转回setjmp,根据返回值和异常信息判断进⾏不同的else 分⽀。
EXCEPT(e)的定义
switch的用法c语言#define EXCEPT(e) \
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
}else if (ption == &(e)) { \
Except_flag = Except_handled;
if (Except_flag == Except_entered) Except_stack = Except_stack->prev;:如果类型是TRY-EXCEPT,那么TRY S执⾏后⽆异常,则执⾏此⾏代码 ⽆错误产⽣,将此前压栈的异常结构体变量弹出丢弃;如果有异常,那么程序从S调回到TRY中的1处,在S中longjmp会将setjmp的返回值设置为Except_raised
}else if(ption == &(e)) :若Except_flag为Except_raised,判断ption与给定的e错误信息是否⼀致,不⼀致程序跳过进⼊下⼀个判断,可能是EXCEPT(e) 或者ELSE 或FINALLY 或 END_TRY
Except_flag = Except_handled; :如果上⾯的判断错误⼀致,则进⼊到“{”⾥⾯执⾏,将Except_flag 设置为Except_handled
ELSE定义
#define ELSE \
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
}else{ \
Except_flag = Except_handled;
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; : 如果是TRY-ELSE结构,TRY S ELSE S1则此处和EXCEPT(e)作⽤⼀致,如果TRY S的 S执⾏⽆错误,则执⾏此⾏代码,将栈顶异常弹出丢弃,否则将执⾏else 后的代码,先将
Except_flag = Except_handled;:Except_flag设置为Except_handled状态,表⽰异常已捕获且处理。执⾏S1程序
FINALLY定义
#define FINALLY  \
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
}\
{\
if (Except_flag == Except_entered) \
Except_flag = Except_finalized;
Except_stack = Except_stack->prev; : 执⾏到此处证明上⾯如果未产⽣异常,则异常栈顶弹出并丢弃
if (Except_flag == Except_entered) Except_flag = Except_finalized; :作⽤和前⾯⼀样; TRY S FINALLY S1
对于FINALLY的解释,此处⼈邮出版社 2011年第⼀版的翻译,个⼈觉得容易误解,在P37页, 翻译后的截图如下
英⽂原版如下
抱歉,原来以为是翻译错了,后来对⽐后发现是我理解错了,“在S1执⾏后,导致S1**执⾏的异常**将被再次引发(re-raised)”,少看了个“的”字,误认为是导致S1的执⾏异常,所以原来觉得奇怪S1有啥异常需要处理。不过⼈邮的这个版本在本章另外⼀个地⽅确实翻译的会让⼈误解,后⾯会指出来。
END_TRY
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; : 不解释和前⾯⼀样的作⽤
if (Except_flag == Except_raised) RERAISE; : 如果条件成⽴,证明之前的异常没有被处理,此处通过RERAISE再次引发,如果处理不了,最后会在RERAISE中异常退出,本书中的例⼦使⽤的是abort函数退出.
RETURN定义
咋⼀看switch()⾥⾯怎么有”,”,还有两个“参数”,实际这个是“,”号运算符,优先级最低,从左往右执⾏,先执⾏出栈操作,执⾏0的switch判断,书中指出由于TRY-ELSE-END_TRY等结构是宏定义,如果直接使⽤return编译会报错,所以需要进⾏封装 可以⽤个⼩程序来验证⼀下这种使⽤⽅法#define END_TRY  \
if  (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
} if  (Except_flag == Except_raised) RERAISE; \
}while (0)
#define RETURN  switch ( Except_stack = Except_stack -prev , 0) default: return

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。