可嵌套的C语言异常处理机制



原文:http://blog.csdn.net/maozefa/article/details/1965755

C是一门古老的、面向过程的语言,相对于它的运行高效率,其开发效率是较低的,所以长期以来,C就主要被定位在系统软件的开发上,特别是在现代各种可视化编程环境下,C的应用领域也越来越窄,虽然其原因有很多,但是,相对其它现代高级语言而言,其原始的异常处理功能不能不说是低效开发的主要原因之一,如果有一套较完善的异常功能,再配上一套好的常用功能库,应该能提高其开发效率。

        在现代语言中,异常机制包括两个方面,即抛出异常和处理异常。在C语言程序中,异常类型一般都是靠函数的返回值和一些全局变量(如stdio.h的errno变量)来确定的,这大概算是“异常抛出”吧;处理异常最简单的办法就是终止程序,如使用exit、abort函数,虽然还有套较完备的异常处理函数setjmp和longjmp,但是,C的标准库、一般的应用库都没有应用它们,所以我们也只能在自己的开发部分有限地运用一下,而且,很多不太精通C的人,还没法把它们运用好。我在一些C语言书和网上看到了不少用setjmp和longjmp函数开发C异常机制的文章,有些构思还是很好的,但设计得很粗糙,没能达到推广应用的境界。

        我也花了几天功夫研究了一番,利用setjmp和longjmp函数,仿造Delphi的异常机制搞了几个“宏”,虽还不那么完美,但已经初具雏形,在我有限的测试中,和Delphi的异常机制完全相似,当然,这里说的“相似”是我测试的较上层的内容,而Delphi的VCL从最低层的代码就与异常紧密结合在一起,异常机制已经是VCL的不可缺少的重要组成部分,这一点C++也没得比(当然内核用VCL的BCB又另当别论)。现将代码发布在这里,望朋友们多提建议来完善它,即使不能达到应用的目的,也可通过这种研究,加深对异常机制的理解。

/** ***********************************************************************
*                                                                        *
* except_c.h                                                             *
*                                                                        *
* 定义C语言使用的异常类型、函数和宏                                      *
*                                                                        *
* 湖北省公安县统计局:maozefa 2007.12 于辽宁大连                         *
*                                                                        *
************************************************************************
*/

#ifndef __EXCEPT_C_H
#define    __EXCEPT_C_H

#include 
< setjmp.h >

/*  定义全部异常类型,可按需要定义任何异常类型,供ON_EXCEPT宏使用  */
#define    EXCEPT_ALL        
0

/** ***********************************************************************
*                                                                        *
* 定义异常宏:                                                           *
*                                                                        *
* 1、Raise(type, msg):抛出type异常,msg为异常信息                       *
* 2、RaiseMessage(msg):抛出异常,相当于Raise(EXCEPT_ALL, msg)           *
* 3、ReRaise():重新抛出以前的异常                                       *
*                                                                        *
* 4、异常响应。对可能出现的异常进行处理(无异常时,处理代码不执行):      *
*                                                                        *
*     TRY                                                                  *
*        正常代码                                                         *
*    ON_EXCEPT(type)                                                      *
*        可选项。处理type异常的代码,可在EXCEPT前连续使用                 *
*    EXCEPT                                                               *
*       可选项。所有异常处理代码,相当于ON_EXCEPT(EXCEPT_ALL)            *
*   END_TRY                                                              *
*                                                                        *
* 5、异常保护。无论是否出现异常,均执行的保护性质代码,如资源释放:      *
*                                                                        *
*    TRY                                                                  *
*        正常代码                                                         *
*    FINALLY                                                              *
*        保护性质代码                                                     *
*   END_TRY                                                              *
*                                                                        *
* 6、套异常可嵌套使用,但不能混用,如:                                  *
*                                                                        *
*    TRY                                                                  *
*        代码块1                                                          *
*        TRY                                                              *
*           代码块2                                                      *
*        FINALLY                                                          *
*            保护性质代码                                                 *
*        END_TRY                                                          *
*    EXCEPT                                                               *
*        异常处理代码                                                     *
*    END_TRY                                                              *
*                                                                        *
************************************************************************
*/

#define    TRY            except_Set();    
                    
if  ( ! except_SetNum(setjmp( * except_Buf())))    
                    {

#define    Raise(type, msg)    except_Raise(type, msg, __FILE__, __LINE__)

#define    RaiseMessage(msg)    Raise(EXCEPT_ALL, msg)

#define ReRaise()    except_ReRaise()

#define    ON_EXCEPT(type)    
                    }   
                    
else   if  (except_On(type))    
                    {

#define    EXCEPT        ON_EXCEPT(EXCEPT_ALL)

#define FINALLY     }        
                    {         

#define    END_TRY        }    
                    except_end();

/*  异常结构  */
typedef struct __Exception
{
    
int     type;       /*  异常类型  */
    
char   * message;  /*  消息  */
    
char   * soufile;  /*  源文件  */
    
int  lineNum;    /*  产生异常的行号  */
}Exception;

//  获取当前异常消息
char *  except_Message( void );
//  获取当前异常结构
Exception  * except_Exception( void );

//  以下函数为内部使用
void  except_Set( void );
void  except_Raise( int  type,  const   char   * message,  char   * file,  int  line);
void  except_ReRaise( void );
int  except_On( int  type);
void  except_end( void );
jmp_buf
*  except_Buf( void );
int  except_SetNum( int  Num);

#endif  
/*  __EXCEPT_C_H  */

 

#include  < malloc.h >
#include 
< string.h >
#include 
< stdio.h >
#include 
< stdlib.h >
#include 
" except_c.h "

#define    MESSAGE_FORMAT    
" %s(%d):  "

enum  {evEnter, evRaise, evExcept  =   2 };

typedef struct __Exception_Event
{
    struct __Exception_Event 
* prev;
    jmp_buf eBuf;
    
int  evNum;
    Exception exception;
}Exception_Event;

static  Exception_Event  * except_ptr  =  NULL;
static   char   * except_msg;
static   char  except_msg_size  =   0 ;

void  except_Set( void )
{
    Exception_Event 
* ev;
    ev 
=  (Exception_Event * )malloc(sizeof(Exception_Event));
    ev
-> prev  =  except_ptr;
    except_ptr 
=  ev;
}

void  except_Clear( void )
{
    Exception_Event 
* ev;
    
if  (except_ptr)
    {
        ev 
=  except_ptr;
        except_ptr 
=  except_ptr -> prev;
        free(ev);
    }
}

void  except_Raise( int  type,  const   char   * message,  char   * file,  int  line)
{
    
int  len  =   0 , size;
#ifndef    NDEBUG
    
char  buf[ 100 ];
    sprintf(buf, MESSAGE_FORMAT, file, line);
    len 
=  strlen(buf);
#endif
    size 
=  strlen(message)  +  len  +   1 ;
    
if  (except_msg_size  <  size)
    {
        
if  (except_msg_size  >   0 )
            free(except_msg);
        except_msg_size 
=  size;
        except_msg 
=  ( char * )malloc(except_msg_size);
    }
#ifndef    NDEBUG
    strcpy(except_msg, buf);
    strcat(except_msg, message);
#
else
    strcpy(except_msg, message);
#endif
    
if  (except_ptr)
    {
        except_ptr
-> exception.type  =  type;
        except_ptr
-> exception.message  =   & except_msg[len];
        except_ptr
-> exception.soufile  =  file;
        except_ptr
-> exception.lineNum  =  line;
        longjmp(except_ptr
-> eBuf, evRaise);
    }
    
else
    {
        fprintf(stderr, except_msg);
        abort();
    }
}

void  except_ReRaise( void )
{
    Exception e;
    
if  (except_ptr)
    {
        e 
=  except_ptr -> exception;
        
if  (except_ptr -> prev)
        {
            except_Clear();
            except_ptr
-> exception  =  e;
            longjmp(except_ptr
-> eBuf, evRaise);
        }
        
else
        {
            fprintf(stderr, except_msg);
            abort();
        }
    }
}

int  except_On( int  type)
{
    
if  (except_ptr -> evNum  ==  evRaise  &&
        (type 
==  EXCEPT_ALL  ||  type  ==  except_ptr -> exception.type))
    {
        except_ptr
-> evNum  =  evExcept;
        
return   1 ;
    }
    
return   0 ;
}

void  except_end( void )
{
    
if  (except_ptr -> evNum  ==  evRaise)
        except_ReRaise();
    except_Clear();
}

jmp_buf 
* except_Buf( void )
{
    
return   & except_ptr -> eBuf;
}

char   * except_Message( void )
{
    
return  except_msg;
}

Exception 
* except_Exception( void )
{
    
return   & except_ptr -> exception;
}

int  except_SetNum( int  Num)
{
    except_ptr
-> evNum  =  Num;
    
return  except_ptr -> evNum;
}

         有关异常的使用在except_c.h文件中已经说得很清楚了,不再阐述,后面再举例说明。下面简单说该异常机制的原理:

        先说说不嵌套的情况,我们知道setjmp和longjmp必须配合使用,首先调用一次setjmp,用一个jmp_buf类型变量(假定a_buf)保存了当前现场,即计算机的各个寄存器状态,此时setjmp返回值为0,如果在程序代码某些地方用同一变量a_buf调用longjmp函数,那么,由a_buf保存的现场得以恢复,计算机将跳转到设置现场变量a_buf的setjmp函数前,导致该函数再次被调用,并返回由longjmp传递的值(依靠该值,我们得以区分约定的异常类型或者出处);如果在abuf被设置后,又调用setjmp设置了一个b_buf现场,显然,用该现场变量调用longjmp函数,只能恢复到b_buf设置的地方,这样就形成了互不干扰的嵌套异常,假如内层异常机制出现异常,得到处理后,上层异常机制不能捕获到错误,就跳出了这多层异常机制,相当于作了异常相应;如果内层异常机制出现异常,没能够得到适当处理,那么,只需将异常信息传递到上一层异常机制(重新抛出异常),并清除内层异常标志,如此嵌套循环,直到某个异常处理环节进行处理,或者作最后终止程序的处理。

        在具体实现时,用一个向上的链表结构Exception_Event的静态变量except_ptr组成异常嵌套的基础,每次调用TRY宏,都重新设置链表尾,碰到END_TRY宏时,如果没有异常发生,或者异常发生后作了处理,那么,清除本层的Exception_Event数据,使链尾指向上一层,如果发生异常没作处理,或者处理后重新抛出异常,那么,END_TRY宏将异常信息向上层移交后,并清除本层数据,使链尾指向上一层。

  上面说的是设置异常和处理异常的情况,再简单说说抛出异常Raise宏,Raise宏调用了except_Raise函数,函数中,如果Exception_Event结构的静态变量except_ptr不为NULL,也就是设置了异常机制后,将异常信息写到该变量中,否则,显示错误信息后,调用abort终止程序,所以,在自己写的库函数中,可以无顾虑的使用该宏作异常抛出,即使调用库函数的代码没用TRY,Raise也只相当于assert宏而已。

        下面给一个演示例子:

// ---------------------------------------------------------------------------

#include 
< stdio.h >
#include 
< stdlib.h >
#include 
" except_c.h "

#pragma hdrstop

// ---------------------------------------------------------------------------

#define    EXCEPT_FILE_IO    
- 1

void  FileCopy( char   * Source,  char   * Dest)
{
    FILE 
* fo,  * fi;
    
int     ch;
    
char  s[ 256 ];
    printf(
" 打开源文件... " );
    
if  ((fi  =  fopen(Source,  " rb " ))  ==  NULL)
        Raise(EXCEPT_FILE_IO, 
" 源文件未找到 " );
    TRY
        printf(
" 建立目标文件... " );
        
if  ((fo  =  fopen(Dest,  " wb " ))  ==  NULL)
            Raise(EXCEPT_FILE_IO, 
" 建立目标文件失败 " );
        TRY
            printf(
" 开始拷贝文件... " );
//             RaiseMessage("拷贝文件错误 ");
             while  ( 1 )
            {
                ch 
=  fgetc(fi);
                
if  (ch  ==  EOF  &&  ferror(fi))
                    Raise(EXCEPT_FILE_IO, 
" 读文件错误 " );
                
if  (feof(fi))
                    
break ;
                
if  (fputc(ch, fo)  ==  EOF  &&  ferror(fo))
                    Raise(EXCEPT_FILE_IO, 
" 写文件错误 " );
            }
        FINALLY
            printf(
" 关闭目标文件... " );
            fclose(fo);
        END_TRY
    FINALLY
        printf(
" 关闭源文件... " );
        fclose(fi);
    END_TRY
}

#pragma argsused
int  main( int  argc,  char *  argv[])
{
    TRY
        
if  (argc  <   3 )
        {
            printf(
" 程序功能:拷贝文件格式:%s 源文件 目标文件 " , argv[ 0 ]);
            RaiseMessage(
" 程序参数错误! " );
        }
        FileCopy(argv[
1 ], argv[ 2 ]);
    ON_EXCEPT(EXCEPT_FILE_IO)
        fprintf(stderr, 
" 处理文件类型错误:% s " , except_Message());
    EXCEPT
        fprintf(stderr, 
" 处理全部错误:% s " , except_Message());
    END_TRY

    system(
" pause " );
    
return   0 ;
}
// ---------------------------------------------------------------------------

        下面,我们一步步来测试该例子(为了显示结果,FileCopy函数中每个步骤都作了显示):

        1、该控制台例子程序要求带参数运行,实现文件拷贝。在主函数中,有个TRY结构,首先检查程序参数个数,如果小于3,抛出异常,该异常会被下面的EXCEPT宏捕获处理,显示信息为:

        处理全部错误:ExceptMain(55):程序参数错误!

       2、给定正确的源文件和目标文件,上面的异常不存在了,主函数调用FileCopy函数,此时运行正常,结果为:

        3、修改源文件为错误的路径,FileCopy函数中产生异常,这个异常是在TRY之前抛出的,所以直接被主函数中异常结构中ON_EXCEPT(EXCEPT_FILE_IO)捕获,运行结果为:

        4、改回正确的源文件路径,把已经形成的目标文件改为只读属性,这时产生异常如下显示,表示目标文件不能建立,这时已经打开的源文件应该关闭,因此导致源文件关闭的FINALLY宏正确执行了,异常被主函数的TRY结构捕获:

        5、取消目标文件只读属性,将例子代码中FileCopy函数中被注释的语句RaiseMessage("拷贝文件错误/n");打开,以模拟产生拷贝过程错误,此时产生异常后,应同时关闭源文件和目标文件,结果,2个FINALLY都正确执行,异常被主函数的TRY结构捕获:

        至此,该例子全部测试完毕。该例子实际有3层TRY嵌套,FileCopy函数中是2层TRY...FINALLY异常结构,主函数则是TRY...EXCEPT结构,从测试结果看,完全达到了目的,用Delphi类似的例子运行结果完全一样!

        前面已经说了,该异常机制处理自己写的代码应该问题不大,但是对于标准库的错误能捕获吗?我想,有些硬件异常应该是能捕获的,如浮点数错误,下面用个例子演示:

// ---------------------------------------------------------------------------

#include 
< stdio.h >
#include 
< stdlib.h >
#include 
" except_c.h "
#include 
< signal.h >
#include 
< float .h >

#pragma hdrstop

// ---------------------------------------------------------------------------

void  FPE_Handler( int  sig,  int  num)
{
    
char  err[][ 32 =
    {
        
" Interrupt on overflow " ,
        
" Integer divide by zero " ,
        
"" ,
        
" invalid operation " ,
        
"" ,
        
" divide by zero " ,
        
" arithmetic overflow " ,
        
" arithmetic underflow " ,
        
" precision loss " ,
        
" stack overflow " ,
    };
    _fpreset();
    
if  (num  >=  FPE_INTOVFLOW  &&  num  <=  FPE_STACKFAULT)
        Raise(num, err[num 
-  FPE_INTOVFLOW]);
    
else
        Raise(num, 
" Other floating point error " );
}


#pragma argsused
int  main( int  argc,  char *  argv[])
{
    float
 n  =   0 ;
    TRY
        
if  (signal(SIGFPE, FPE_Handler)  ==  SIG_ERR)
            RaiseMessage(
" Couldn't set SIGFPE " );
        n 
=   2   /  n;
    ON_EXCEPT(FPE_ZERODIVIDE)
        fprintf(stderr, 
" 处理浮点数被零除错误:% s " , except_Message());
    EXCEPT
        fprintf(stderr, 
" 处理全部错误:% s " , except_Message());
    END_TRY

    system(
" pause " );
    
return   0 ;
}
// ---------------------------------------------------------------------------

        该例子安装了一个浮点数错误处理过程FPE_Handler,并在过程中使用了Raise抛出异常;主函数中,我们人为的制造了一个浮点数被零除的的错误n = 2 / n(n = 0.0),FPE_Handler函数用Raise抛出了该异常,异常被主函数内TRY结构的ON_EXCEPT(FPE_ZERODIVIDE)捕获并处理,显示为“divide by zero“错误;如果把float改为int,则错误类型不再是FPE_ZERODIVIDE,所以,异常被EXCEPT捕获,显示为“Integer divide by zero”错误;如果不用TRY机制,则程序会立即终止,从而失去处理机会。

        本文的异常机制作为一种探讨和学习,无论从构思、设计和代码都不可避免的存在问题,要实用还需要大家的建议,如异常结构类型能否灵活一点;异常抛出时,消息可否提供格式化;其他标准异常怎样捕获和规范定义等,都是需要解决的问题。

        本文例子使用BCB2007编译,如有错误和建议请来信:maozefa@hotmail.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值