libzdb源码学习之二:异常模块

来源:互联网 发布:应用架构 数据架构 编辑:程序博客网 时间:2024/05/21 06:46

一 程序错误分类

在《c语言接口与实现》一书中,将程序的错误分为三类:用户错误、运行时错误和异常。

1、用户错误是预期发生的,主要由错误的用户输入导致。如命名不存在的文件、输入非法的系统格式等。通常,必须要求处理用户错误的函数能返回错误码。

这类错误是系统的一个普通组成部分。

2、运行时错误非预期的,表现为程序出现了bug。因程序无法从这种错误中恢复,而必须优雅地结束。在c语言中这类错误一般通过断言来捕获处理。

3、异常介于用户错误与程序bug之间。异常是比较罕见的错误,但从异常中恢复是有可能的。异常不会频繁发生,因此发生异常的函数通常不返回错误码。

一些异常反映了机器的能力,如算术运算上益和下益;其他异常表明操作系统检测到的状况,如写文件时遇到写入错误、有限资源用尽也可能产所异常,

如应用程序内存不足。


二 异常机制的实现

      异常由应用程序产生,由恢复代码处理(如果能恢复)。异常的作用域是动态的:当一个异常被引发时,它由最近实例化的处理程序处理。

在c语言中,可通过setjmp和longjmp函数来建立结构化的异常处理机制。即,setjmp实例化一个处理程序,而longjmp引发一个异常。

      libzdb库的异常模块实现与《c语言接口与实现》中的实现基本相同,主要区别在于libzdb应用了线程私有数据,能用于多线程环境。

2.1 相关结构

//Exception.h#define T Exception_T#define ThreadData_T pthread_key_t#define ThreadData_set(key, value) pthread_setspecific((key), (value))#define ThreadData_get(key) pthread_getspecific((key))//定义异常typedef struct T {    const char *name;}T;#define EXCEPTION_MESSAGE_LENGTH 512typedef struct Exception_Frame Exception_Frame;struct Exception_Frame {    int line;    jmp_buf env;    const char *func;    const char *file;    const T *exception;    Exception_Frame *prev;    char message[EXCEPTION_MESSAGE_LENGTH + 1];//异常消息};enum {    Exception_entered = 0,    Exception_thrown,    Exception_handled,    Exception_finalized};extern ThreadData_T Exception_stack;//线程私有数据

2.2 初始化

/* -------------------------------------------------------- Privat methods */static void init_once(void) {ThreadData_create(Exception_stack);}/* ----------------------------------------------------- Protected methods */void Exception_init(void) {pthread_once(&once_control, init_once);}

ThreadData_create为一个宏,实则调用了创建线程私有数据键的函数pthread_key_create。其定义如下:

#define ThreadData_create(key) wrapper(pthread_key_create(&(key), NULL))

2.3 实现

      该异常模块实现了一套用于异常的标准化宏。TRY、CATCH、ELSE、FINALLY 、END_TRY、THROW、RETHROW。


/** * Throws an exception.  * @param e The Exception to throw * @param cause The cause. A NULL value is permitted, and  * indicates that the cause is unknown. * @hideinitializer *//* * 关于变量宏与##的使用,为了应对没有可变参数的情况 */#define THROW(e, cause, ...) \        Exception_throw(&(e), __func__, __FILE__, __LINE__, cause, ##__VA_ARGS__, NULL)/** * Re-throws an exception. In a CATCH or ELSE block clients can use RETHROW * to re-throw the Exception * @hideinitializer */#define RETHROW Exception_throw(Exception_frame.exception, \        Exception_frame.func, Exception_frame.file, Exception_frame.line, NULL)/** * Clients <b>must</b> use this macro instead of C return statements * inside a try-block * @hideinitializer */#define RETURN switch((pop_Exception_stack,0)) default:return/** * Defines a block of code that can potentially throw an exception * @hideinitializer */#define TRY do { \volatile int Exception_flag; \        Exception_Frame Exception_frame; \        Exception_frame.message[0] = 0; \        Exception_frame.prev = (Exception_Frame*)ThreadData_get(Exception_stack); \        ThreadData_set(Exception_stack, &Exception_frame); \        Exception_flag = setjmp(Exception_frame.env); \        if (Exception_flag == Exception_entered) {/** * Defines a block containing code for handling an exception thrown in  * the TRY block. * @param e The Exception to handle * @hideinitializer */#define CATCH(e) \                if (Exception_flag == Exception_entered) pop_Exception_stack; \        } else if (Exception_frame.exception == &(e)) { \                Exception_flag = Exception_handled; /** * Defines a block containing code for handling any exception thrown in  * the TRY block. An ELSE block catches any exception type not already  * catched in a previous CATCH block. * @hideinitializer */#define ELSE \                if (Exception_flag == Exception_entered) pop_Exception_stack; \        } else { \                Exception_flag = Exception_handled;/** * Defines a block of code that is subsequently executed whether an  * exception is thrown or not * @hideinitializer */#define FINALLY \                if (Exception_flag == Exception_entered) pop_Exception_stack; \        } { \                if (Exception_flag == Exception_entered) \                        Exception_flag = Exception_finalized;/** * Ends a TRY-CATCH block * @hideinitializer */#define END_TRY \                if (Exception_flag == Exception_entered) pop_Exception_stack; \        } if (Exception_flag == Exception_thrown) RETHROW; \        } while (0)
2.4 Exception_throw的实现

void Exception_throw(const T *e, const char *func, const char *file, int line, const char *cause, ...) {va_list ap;Exception_Frame *p = ThreadData_get(Exception_stack); //获取异常栈顶的一个异常帧assert(e);if (p) {//设置当前异常帧的异常信息p->exception = e;p->func = func;p->file = file;p->line = line;if (cause) {va_start(ap, cause);vsnprintf(p->message, EXCEPTION_MESSAGE_LENGTH, cause, ap);va_end(ap);}pop_Exception_stack;//从异常栈中弹出该异常帧longjmp(p->env, Exception_thrown);//跳转到该异常的处理程序} else if (cause) {char message[EXCEPTION_MESSAGE_LENGTH + 1];va_start(ap, cause);vsnprintf(message, EXCEPTION_MESSAGE_LENGTH, cause, ap);va_end(ap);ABORT("%s: %s\n raised in %s at %s:%d\n", e->name, message,func ? func : "?", file ? file : "?", line);} else {ABORT("%s: 0x%p\n raised in %s at %s:%d\n", e->name, e,func ? func : "?", file ? file : "?", line);}}

三 原理剖析

        下面结合一个实例来分析该异常模块实现的原理。

        printf("=> Test6: TRY-CATCH-FINALLY and volatile\n");        {                volatile int i = 0;                TRY                        i++;                        THROW(C, "C");                CATCH(C)                        assert(i == 1);                        i++;                FINALLY                        printf("\tResult: ok\n");                        assert(i == 2);                END_TRY;        }        printf("=> Test6: OK\n\n");
上述代码,进行宏的扩展后,其结构如下:

       do {volatile int Exception_flag;Exception_Frame Exception_frame;Exception_frame.message[0] = 0;Exception_frame.prev = (Exception_Frame*) pthread_getspecific((Exception_stack));pthread_setspecific((Exception_stack), (&Exception_frame));Exception_flag = setjmp(Exception_frame.env);if (Exception_flag == Exception_entered) {//----------------------------------------------//try block//----------------------------------------------THROW(C, "C");//抛异常if (Exception_flag == Exception_entered)pop_Exception_stack;} else if (Exception_frame.exception == &(e)) {Exception_flag = Exception_handled;//----------------------------------------------//catch block//----------------------------------------------if (Exception_flag == Exception_entered)pop_Exception_stack;}{if (Exception_flag == Exception_entered)Exception_flag = Exception_finalized;//----------------------------------------------//finally block//----------------------------------------------if (Exception_flag == Exception_entered)pop_Exception_stack;}if (Exception_flag == Exception_thrown)//RETHROW;Exception_throw(Exception_frame.exception, Exception_frame.func,Exception_frame.file, Exception_frame.line, NULL);} while (0);

       注册过程:

             TRY 在一个新的作用域中定义个异常帧变量,并将该帧添加到异常栈中。每个异常帧通过一个前向指针pre,形成一个单向链表。

                       同时通过setjmp注册该帧。

             THROW 抛出一个已定义的异常,并调用Exception_throw函数,设置当前异常帧的异常信息,func、frame、line等。

                      然后,从异常栈中删除该帧,并通过Exception_thrown标志调用longjmp跳转到setjmp。

             CATCH 通过判断抛出的异常是否为指定的捕获异常,若是进行相应的处理。

下图是注册与抛出异常帧的栈的变化情况。




0 0
原创粉丝点击