C++语言提供对处理异常情况的内部支持,异常情况即是所知道的“异常”,可能在你的程序执行期间出现。
try、throw和catch语句已被加到C++语言中去实现异常处理。有了C++异常处理,你的程序可以向更高的执行上下文传递意想不到的事件,这些上下文能更好地从这些异常事件中恢复过来。这些异常由正常控制流外的代码进行处理。Microsoft C++编译器朝着C++进化中的标准去实现基于ISO WG21/ANSI X3J16工作文件的C++异常处理模式。
语法
try块:
try复合语句 处理器表
处理器表:
处理器 处理器表opt
处理器:
catch(异常说明) 复合语句
异常说明:
类型指示符表 说明符
类型指示符表 抽象说明符
类型指示符表
...throw-表达式:
throw 赋值表达式opt
try子句后的复合语句是代码的保护段。throw表达式“丢弃”(凸起)一个异常,catch子句后的复合语句是异常处理器,“捕获”(处理)由throw表达式丢弃的异常。异常说明语句指示子句处理的异常的类型,类型可以是任何有效的数据类型,包括C++的类。如果异常说明语句是一个省略号(...),catch子句处理任何类型的异常,包括C的异常。这样的处理器必须是其try块的最后一个处理器。
throw的操作数语法上与return语句的操作数相似。注意:Microsoft C++不支持函数throw特征机制,如ANSI C++草案的15.5节所描述的。此外,它也不支持ANSI C++草案的15节中描述的function-try-block。执行过程如下:
1. 控制通过正常的顺序执行到达try语句,保护段(在try块内)被执行。
2. 如果在保护段执行期间没有引起异常,跟在try块后的catch子句不执行。从异常被丢弃的try块后跟随的最后一个catch子句后面的语句继续执行下去。
3. 如果在保护段执行期间或在保护段调用的任何例行程序中(直接或间接的调用)有异常被丢弃,则从通过throw操作数创建的对象中创建一个异常对象(这隐含指可能包含一个拷贝构造函数)。在此点,编译器在能够处理丢弃类型的异常的更高执行上下文中寻找一个catch子句(或一个能处理任何类型异常的catch处理器)。catch处理程序按其在try块后出现的顺序被检查。如果没有找到合适的处理器,则下一个动态封闭的try块被检查。此处理继续下去直到最外层封闭try块被检查完。
4. 如果匹配的处理器未找到,或如果在不自动分行时出现异常,但在处理器得到控制之前预定义的运行函数terminate被调用。如果一个异常发生在丢弃异常之后,则在循环展开开始之前调用terminate。
5. 如果一个匹配的catch处理器被找到,且它通过值进行捕获,则其形参通过拷贝异常对象进行初始化。如果它通过引用进行捕获,则参量被初始化为指向异常对象,在形参被初始化之后,“循环展开栈”的过程开始。这包括对那些在与catch处理器相对应的try块开始和异常丢弃地点之间创建的(但尚未析构的)所有自动对象的析构。析构以与构造相反的顺序进行。catch处理器被执行,且程序恢复到跟随在最后的处理器之后的执行(即不是catch处理器的第一条语句或构造)。控制仅能通过一个丢弃的异常输入一个catch处理器,而永远不能通过goto语句或switch语句中的case标号。
以下是一个try块和其相应的catch处理器的简单例子,此例子检测使用new运算符的存储器分配操作的失败。如果new成功了,则catch处理器永不执行:
#include <iostream.h>
int main()
{
char *buf;
try
{
buf=new char[512];
if(buf==0)
throw "Memory allocation failure!";
}
catch (char *str)
{
cout << "Exception raised: " << str <<′\n′;
}
//...
return 0;
}
throw表达式的操作数指示一个char*类型的异常正被丢弃。它由表示有捕获char*类型的一个异常的能力的catch处理器进行处理。在存储器分配失败事件中,这是从前面例子得到的输出:
Exception raised: Memory allocation failure!
C++异常处理的真正能力不仅在于其处理各种不同类型的异常的能力,还在于在堆栈循环展开期间为异常丢弃前构造的所有局部对象自动调用析构函数的能力。
以下例子演示了使用带析构语义的类的C++异常处理:
#include <iostream.h>
void MyFunc(void);
class CTest
{
public:
CTest() {};
~CTest() {};
const char *ShowReason() const { return "Exception in CTest class.";}
};
class CDtorDemo
{
public:
CDtorDemo();
~CDtorDemo();
};
CDtorDemo::CDtorDemo()
{
cout << "Constructing CDtorDemo.\n";
}
CDtorDemo::~CDtorDemo()
{
cout << "Destructing CDtorDemo.\n";
}
void MyFunc()
{
CDtorDemo D;
cout << "In MyFunc(). Throwing CTest exception.\n";
throw CTest();
}
int main()
{
cout << "In main.\n";
try
{
cout << "In try block, calling MyFunc().\n";
MyFunc();
}
catch (CTest E)
{
cout << "In catch handler.\n";
cout << "Caught CTest exception type:";
cout << E.ShowReason() << "\n";
}
catch (char *str)
{
cout << "Canght some other exception:" << str << "\n";
}
cout << "Back in main. Execution resumes here.\n";
return 0;
}
以下是上面例子的输出:
In main.In try block, calling MyFunc()
.Constructing CDtorDemo.
In MyFunc(). Throwing CTest exception.
Destructing CDtorDemo.
In catch handler.
Caught CTest exception type; Exception in CTest class.
Back in main. Execution resumes here.
注意在此例中,异常参量(catch子句的参量)在两个catch处理器中都被说明:
catch(CTest E)
//...
catch(char *str)
//...
你不需要说明此参量;在很多情况下可能通知处理器有某个特定类型的异常已经产生就足够了。但是如果你在异常说明中没有说明一个异常对象,你将无法访问catch处理程序子句中的那个对象。
一个不带操作数的throw表达式把当前正被处理的异常再次丢弃,这样一个表达式仅仅应该出现在一个catch处理器中或从catch处理器内部被调用的函数中,再次丢弃的异常对象是源异常对象(不是拷贝)。例如:
try
{
throw CSomeOtherException();
}
catch(...) //处理所有异常
{
//对异常作出响应(也许仅仅是部分的)
//...
throw; //将异常传给某个其它处理器
}
/*********************************************************/
setjmp和longjmp
#include
int setjmp(jmp_buf envbuf)
宏函数setjmp()在缓冲区envbuf中保存系统堆栈里的内容,供longjmp()以后使用,setjmp()必须使用头文件setjmp.h。
调用setjmp()宏时,返回值为0,然而longjmp()把一个变原传递给setjmp(),该值(恒不为0)就是调用longjmp()后出现的setjmp()的值。
#include
void longjmp(jmp_buf envbuf,int status);
函数longjmp()使程序在最近一次调用setjmp()出重新执行。setjmp()和longjmp()提供了一种在函数间调转的手段,必须使用头部文件setjmp.h。
函数longjmp()通过把堆栈复位成envbuf中描述的状态进行操作,envbuf的设置是由预先调用setjmp()生成的。这样使程序的执行在setjmp()调用后的下一个语句从新开始,使计算机认为从未离开调用setjmp()的函数。从效果上看,longjmp()函数似乎“绕”过了时间和空间(内存)回到程序的原点,不必执行正常的函数返回过程。
缓冲区envbuf具有中定义的buf_jmp类型,它必须调用longjmp()前通过调用setjmp()来设置好。
值status变成setjmp()的返回值,由此确定长调转的来处。不允许的唯一值是0,0是程序直接调用函数setjmp()时由该函数返回的,不是间接通过执行函数longjmp()返回的。
longjmp()函数最常用于在一个错误发生时,从一组深层嵌套的实用程序中返回。
例子
这个例子输出“1 2 3“
#include
jmp_buf ebuf;
void f2(void);
int main(void)
{
int i;
printf("1");
i=setjmp(ebuf);
if(i==0)
{
f2();
printf("This will not be printed.");
}
printf("%d",i);
return 0;
}
void f2(void)
{
printf("2");
longjmp(ebuf,3);
}
c语言的异常处理
利用setjmp和longjmp处理异常
c语言种可以利用setjmp和longjmp来实现程序的直接跳转,这种机制并不适用于在程序开发中设计逻辑流程,但用来实现异常处理机制则是非常的适用。
比如写一个实现内存分配的函数,可以这样:
void *allocate(unsigned n)
{
void *new=malloc();
if (new)
return new;
if (Allocation_handled) //如果new为NULL,则执行到此判断
longjmp(Allocate_Failed,1); //跳转
assert(0);
}
而在调用时,可以先设好跳转的“目的地”,再调用allocate:
char *buf;
Allocation_handled=1;
if ( setjmp(Allocate_Failed) )
{
fprintf(stderr,"Couldn't allocate the buffer! ");
exit(1);
}
buf = allocate(1000000);
Allocation_handled=0
为了使用方便可以把以上setjmp和longjmp做一个宏定义,这里就不细说了。但总的来说这个异常处理机制是不如c++的那么方便,但对于c语言来说也算不错了。
该贴由chengpeng.gong转至本版2014-9-16 15:29:38