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"};
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 &