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
分享到:
相关推荐
这种机制通常用于错误处理和异常恢复,尤其是在需要从多层嵌套的函数调用中恢复时。本文将详细介绍setjmp和longjmp的工作原理、使用方法以及在实际编程中的应用。 setjmp和longjmp提供了一种强大的错误处理和异常...
这样的代码显得非常混乱,也不容易管理,我一直在寻找能跟c++异常机制类似的功能,如果有这样的功能,那么c语言的异常处理不是也很容易打理了么? 由于c的工程当中一般错误都有专有的错误列表,所以在这边,...
为提高程序的健壮性和用户友好度,有效的异常处理机制不可或缺。 #### 异常分类 在Java中,异常主要分为两大类: 1. **`Throwable`**:这是所有异常的基类,包括Error和Exception两个子类。 2. **`Exception`**:...
C++异常处理是C++编程语言中处理程序运行中发生的错误和异常情况的一种机制。它提供了在程序中非正常情况发生时,能够以一种结构化的方式处理这些错误的能力,从而避免程序崩溃,并可以给用户提供更清晰的错误信息。...
因此,程序应包含错误检测和处理机制,以防止程序异常终止。 8. **内存管理**:虽然在这个简单的场景中,内存管理可能不是主要关注点,但在更复杂的应用中,合理使用动态内存分配(如`malloc()`和`free()`)可以...
4. **错误处理**:良好的库设计应包含适当的错误处理机制,如返回错误码或抛出异常,以便于调用者识别并处理问题。 5. **文档和示例**:清晰的API文档和示例代码可以帮助用户更好地理解和使用库。 6. **可扩展性**...
10. **异常处理**:C语言没有内置的异常处理机制,但可以通过`setjmp`和`longjmp`函数来实现类似于异常处理的功能。 在《C语言函数速查手册》中,读者可以找到上述各种函数特性的详细解释,以及大量的示例代码,...
8. **异常处理**:虽然C语言没有内置的异常处理机制,但可以通过设置错误码或使用setjmp/longjmp实现类似功能。示例可能会介绍这些方法的使用。 9. **多线程编程**:在现代系统中,多线程编程是必不可少的。C语言...
- C语言没有内置的异常处理机制,通常需要程序员通过条件判断来处理错误情况。 11. **内存管理**: - 动态内存分配`malloc()`和`free()`,虽然在这些程序中未直接涉及,但了解它们对于更高级的C编程非常重要。 ...
案例可能包含错误检查和异常处理的实例,帮助你学习如何在代码中添加适当的错误检测和修复机制。 8. **项目管理与版本控制**: 虽然这不是C语言的直接部分,但好的项目应包含版本控制,如Git的使用。通过案例,你...
8. **错误处理**:当用户输入非法数据或无法找到24点解决方案时,程序应能正确地捕获并处理这些异常情况,这涉及到错误处理和异常处理机制。 9. **内存管理**:虽然在这个简单的游戏中内存管理可能并不复杂,但了解...
C语言没有内置的异常处理机制,但通过返回值和错误码,我们可以检测并处理运行时错误。学会正确地检查错误并采取相应的措施,可以提升程序的稳定性和可靠性。 10. **编程实践**: 实践是学习C语言的关键,通过...
9. **错误处理和异常处理**:良好的错误处理机制是保证系统稳定性的关键,学习如何设计和实现有效的错误处理代码。 10. **硬件接口编程**:掌握如何通过C语言驱动GPIO、I2C、SPI、UART等常见外设,实现与硬件的通信...
9. **错误处理**:在编写程序时,需要考虑异常情况,如用户输入非法、内存分配失败等,通过适当的错误处理机制确保程序的健壮性。 10. **结构化编程**:贪吃蛇游戏的实现需要遵循结构化编程的原则,保证代码的清晰...
同时,理解和运用C语言的错误处理机制,如使用`if`语句进行条件判断,以及在必要时使用`try-catch`进行异常处理,也是提升程序稳定性和健壮性的关键。 总之,C语言程序设计是安徽专升本计算机专业考试的重要组成...
8. **异常处理**:虽然C语言没有内置的异常处理机制,但可以通过设置错误处理函数来模拟异常处理,例如使用`setjmp()`和`longjmp()`。 9. **函数指针**:了解如何声明和使用函数指针,以及如何将函数作为参数传递给...
- 在第三个程序中,对于除以零的情况(图4所示),程序没有处理这个异常,实际开发中应添加异常处理机制,例如检查除数是否为零。 7. 输出格式化 - `printf`函数可以输出格式化的字符串,如`printf("\n%d%c%d=%.0...
虽然C语言本身不提供像其他高级语言那样的异常处理机制,但可以使用setjmp和longjmp函数进行非标准的错误处理。 总的来说,《C语言教程全套》涵盖了C语言从基础到进阶的所有核心内容,通过学习和实践,读者不仅可以...