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语言使用的异常类型、函数和宏*
**
*湖北省公安县统计局:maozefa2007.12于辽宁大连*
**
*************************************************************************/
#ifndef__EXCEPT_C_H
#define__EXCEPT_C_H
#include<setjmp.h>
/*定义全部异常类型,可按需要定义任何异常类型,供ON_EXCEPT宏使用*/
#defineEXCEPT_ALL0
/*************************************************************************
**
*定义异常宏:*
**
*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*
**
*************************************************************************/
#defineTRYexcept_Set();
if(!except_SetNum(setjmp(*except_Buf())))
{
#defineRaise(type,msg)except_Raise(type,msg,__FILE__,__LINE__)
#defineRaiseMessage(msg)Raise(EXCEPT_ALL,msg)
#defineReRaise()except_ReRaise()
#defineON_EXCEPT(type)
}
elseif(except_On(type))
{
#defineEXCEPTON_EXCEPT(EXCEPT_ALL)
#defineFINALLY}
{
#defineEND_TRY}
except_end();
/*异常结构*/
typedefstruct__Exception
{
inttype;/*异常类型*/
char*message;/*消息*/
char*soufile;/*源文件*/
intlineNum;/*产生异常的行号*/
}Exception;
//获取当前异常消息
char*except_Message(void);
//获取当前异常结构
Exception*except_Exception(void);
//以下函数为内部使用
voidexcept_Set(void);
voidexcept_Raise(inttype,constchar*message,char*file,intline);
voidexcept_ReRaise(void);
intexcept_On(inttype);
voidexcept_end(void);
jmp_buf*except_Buf(void);
intexcept_SetNum(intNum);
#endif/*__EXCEPT_C_H*/
#include<malloc.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include"except_c.h"
#defineMESSAGE_FORMAT"%s(%d):"
enum{evEnter,evRaise,evExcept=2};
typedefstruct__Exception_Event
{
struct__Exception_Event*prev;
jmp_bufeBuf;
intevNum;
Exceptionexception;
}Exception_Event;
staticException_Event*except_ptr=NULL;
staticchar*except_msg;
staticcharexcept_msg_size=0;
voidexcept_Set(void)
{
Exception_Event*ev;
ev=(Exception_Event*)malloc(sizeof(Exception_Event));
ev->prev=except_ptr;
except_ptr=ev;
}
voidexcept_Clear(void)
{
Exception_Event*ev;
if(except_ptr)
{
ev=except_ptr;
except_ptr=except_ptr->prev;
free(ev);
}
}
voidexcept_Raise(inttype,constchar*message,char*file,intline)
{
intlen=0,size;
#ifndefNDEBUG
charbuf[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);
}
#ifndefNDEBUG
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();
}
}
voidexcept_ReRaise(void)
{
Exceptione;
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();
}
}
}
intexcept_On(inttype)
{
if(except_ptr->evNum==evRaise&&
(type==EXCEPT_ALL||type==except_ptr->exception.type))
{
except_ptr->evNum=evExcept;
return1;
}
return0;
}
voidexcept_end(void)
{
if(except_ptr->evNum==evRaise)
except_ReRaise();
except_Clear();
}
jmp_buf*except_Buf(void)
{
return&except_ptr->eBuf;
}
char*except_Message(void)
{
returnexcept_msg;
}
Exception*except_Exception(void)
{
return&except_ptr->exception;
}
intexcept_SetNum(intNum)
{
except_ptr->evNum=Num;
returnexcept_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"
#pragmahdrstop
//---------------------------------------------------------------------------
#defineEXCEPT_FILE_IO-1
voidFileCopy(char*Source,char*Dest)
{
FILE*fo,*fi;
intch;
chars[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
}
#pragmaargsused
intmain(intargc,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");
return0;
}
//---------------------------------------------------------------------------
下面,我们一步步来测试该例子(为了显示结果,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>
#pragmahdrstop
//---------------------------------------------------------------------------
voidFPE_Handler(intsig,intnum)
{
charerr[][32]=
{
"Interruptonoverflow ",
"Integerdividebyzero ",
"",
"invalidoperation ",
"",
"dividebyzero ",
"arithmeticoverflow ",
"arithmeticunderflow ",
"precisionloss ",
"stackoverflow ",
};
_fpreset();
if(num>=FPE_INTOVFLOW&&num<=FPE_STACKFAULT)
Raise(num,err[num-FPE_INTOVFLOW]);
else
Raise(num,"Otherfloatingpointerror ");
}
#pragmaargsused
intmain(intargc,char*argv[])
{
floatn=0;
TRY
if(signal(SIGFPE,FPE_Handler)==SIG_ERR)
RaiseMessage("Couldn'tsetSIGFPE ");
n=2/n;
ON_EXCEPT(FPE_ZERODIVIDE)
fprintf(stderr,"处理浮点数被零除错误:%s",except_Message());
EXCEPT
fprintf(stderr,"处理全部错误:%s",except_Message());
END_TRY
system("pause");
return0;
}
//---------------------------------------------------------------------------
该例子安装了一个浮点数错误处理过程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
分享到:
相关推荐
这样的代码显得非常混乱,也不容易管理,我一直在寻找能跟c++异常机制类似的功能,如果有这样的功能,那么c语言的异常处理不是也很容易打理了么? 由于c的工程当中一般错误都有专有的错误列表,所以在这边,...
6.3.3 MFC中的异常处理 255 6.4 处理内存分配错误 256 6.5 函数重载 257 6.5.1 函数重载的概念 258 6.5.2 引用类型和重载选择 260 6.5.3 何时重载函数 260 6.6 函数模板 261 6.7 使用decltype操作符 263 ...
17.1C语言的出错处理 17.2抛出异常 17.3异常捕获 17.3.1try块 17.3.2异常处理器 17.3.3异常规格说明 17.3.4更好的异常规格说明 17.3.5捕获所有异常 17.3.6异常的重新抛出 17.3.7未被捕获的异常 17.4清除 17.5构造...
第一章:数据结构和算法 1.1 解压序列赋值给多个变量 1.2 解压可迭代对象赋值给多个变量 1.3 保留最后N个元素 1.4 查找最大或最小的N个元素 ... 15.20 处理C语言中的可迭代对象 15.21 诊断分析代码错误
示例描述:本章学习Java的异常处理。 demoException_1.java 异常示例1 demoException_2.java 异常示例2 demoException_3.java 异常示例3 demoException_4.java 异常示例4 demoException_5.java 异常示例5 ...
50.JAVA语言如何进行异常处理,关键字:thorws,throw,try,catch,finally 51.Object类(或者其子类)的finalize()方法在什么情况下被调用? 52.一个“.java”原文件中是否可以包括多个类(不是内部类)? 53.掌握...
07_异常机制基本思想梳理 08_栈解旋unwinding 09_异常接口声明 10_异常类型_异常变量的生命周期上 11_异常类型_异常变量的生命周期下_传智扫地僧 12_中午知识点梳理 13_异常的层次结构_传智扫地僧 14_标准异常库 15_...