Visual C++中的异常处理浅析.docx
- 文档编号:6618247
- 上传时间:2023-01-08
- 格式:DOCX
- 页数:25
- 大小:102.04KB
Visual C++中的异常处理浅析.docx
《Visual C++中的异常处理浅析.docx》由会员分享,可在线阅读,更多相关《Visual C++中的异常处理浅析.docx(25页珍藏版)》请在冰豆网上搜索。
VisualC++中的异常处理浅析
VisualC++中的异常处理浅析
VisualC++提供了对C语言、C++语言及MFC的支持,因而其涉及到的异常(exception)处理也包含了这三种类型,即C语言、C++语言和MFC的异常处理。
除此之外,微软对C和C++的异常处理进行了扩展,提出了结构化异常处理(SEH)的概念,它支持C和C++(与之相比,MFC异常处理仅支持C++)。
一个典型的异常处理包含如下几个步骤:
(1)程序执行时发生错误;
(2)以一个异常对象(最简单的是一个整数)记录错误的原因及相关信息;
(3)程序检测到这个错误(读取异常对象);
(4)程序决定如何处理错误;
(5)进行错误处理,并在此后恢复/终止程序的执行。
C、C++、MFC及SEH在这几个步骤中表现出了不同的特点。
本文将对这四种异常处理进行介绍,并对它们进行对比分析。
本文例程的调试平台为VisualC++6.0,操作系统为WindowsXP,所有程序均调试通过。
在进入正式的讲解之前,先说几句废话。
许多的编程新手对异常处理视而不见,程序里很少考虑异常情况。
一部分人甚至根本就不考虑,以为程序总是能以正确的途径运行。
譬如我们有的程序设计者调用fopen打开一个文件后,立马就开始进行读写操作,根本就不考虑文件是否正常打开了。
这种习惯一定要改掉,纵使你再不愿意!
这是软件健壮性的需要!
异常处理不是浪费时间!
1.C语言异常处理
1.1异常终止
标准C库提供了abort()和exit()两个函数,它们可以强行终止程序的运行,其声明处于
这两个函数本身不能检测异常,但在C程序发生异常后经常使用这两个函数进行程序终止。
下面的这个例子描述了exit()的行为:
#include
#include
intmain(void)
{
exit(EXIT_SUCCESS);
printf("程序不会执行到这里\n");
return0;
}
在这个例子中,main函数一开始就执行了exit函数(此函数原型为voidexit(int)),因此,程序不会输出"程序不会执行到这里"。
程序中的exit(EXIT_SUCCESS)表示程序正常结束,与之对应的exit(EXIT_FAILURE)表示程序执行错误,只能强行终止。
EXIT_SUCCESS、EXIT_FAILURE分别定义为0和1。
对于exit函数,我们可以利用atexit函数为exit事件"挂接"另外的函数,这种"挂接"有点类似Windows编程中的"钩子"(Hook)。
譬如:
#include
#include
staticvoidatExitFunc(void)
{
printf("atexit挂接的函数\n");
}
intmain(void)
{
atexit(atExitFunc);
exit(EXIT_SUCCESS);
printf("程序不会执行到这里\n");
return0;
}
程序输出"atexit挂接的函数"后即终止。
来看下面的程序,我们不调用exit函数,看看atexit挂接的函数会否执行:
#include
#include
staticvoidatExitFunc(void)
{
printf("atexit挂接的函数\n");
}
intmain(void)
{
atexit(atExitFunc);
//exit(EXIT_SUCCESS);
printf("不调用exit函数\n");
return0;
}
程序输出:
不调用exit函数
atexit挂接的函数
这说明,即便是我们不调用exit函数,当程序本身退出时,atexit挂接的函数仍然会被执行。
atexit可以被多次执行,并挂接多个函数,这些函数的执行顺序为后挂接的先执行,例如:
#include
#include
staticvoidatExitFunc1(void)
{
printf("atexit挂接的函数1\n");
}
staticvoidatExitFunc2(void)
{
printf("atexit挂接的函数2\n");
}
staticvoidatExitFunc3(void)
{
printf("atexit挂接的函数3\n");
}
intmain(void)
{
atexit(atExitFunc1);
atexit(atExitFunc2);
atexit(atExitFunc3);
return0;
}
输出的结果是:
atexit挂接的函数3
atexit挂接的函数2
atexit挂接的函数1
在VisualC++中,如果以abort函数(此函数不带参数,原型为voidabort(void))终止程序,则会在debug模式运行时弹出如图1所示的对话框。
图1以abort函数终止程序
1.2断言(assert)
assert宏在C语言程序的调试中发挥着重要的作用,它用于检测不会发生的情况,表明一旦发生了这样的情况,程序就实际上执行错误了,例如strcpy函数:
char*strcpy(char*strDest,constchar*strSrc)
{
char*address=strDest;
assert((strDest!
=NULL)&&(strSrc!
=NULL));
while((*strDest++=*strSrc++)!
=’\0’)
;
returnaddress;
}
其中包含断言assert((strDest!
=NULL)&&(strSrc!
=NULL)),它的意思是源和目的字符串的地址都不能为空,一旦为空,程序实际上就执行错误了,会引发一个abort。
assert宏的定义为:
#ifdefNDEBUG
#defineassert(exp)((void)0)
#else
#ifdef__cplusplus
extern"C"
{
#endif
_CRTIMPvoid__cdecl_assert(void*,void*,unsigned);
#ifdef__cplusplus
}
#endif
#defineassert(exp)(void)((exp)||(_assert(#exp,__FILE__,__LINE__),0))
#endif/*NDEBUG*/
如果程序不在debug模式下,assert宏实际上什么都不做;而在debug模式下,实际上是对_assert()函数的调用,此函数将输出发生错误的文件名、代码行、条件表达式。
例如下列程序:
#include
#include
#include
char*myStrcpy(char*strDest,constchar*strSrc)
{
char*address=strDest;
assert((strDest!
=NULL)&&(strSrc!
=NULL));
while((*strDest++=*strSrc++)!
=’\0’);
returnaddress;
}
intmain(void)
{
myStrcpy(NULL,NULL);
return0;
}
在此程序中,为了避免我们的strcpy与C库中的strcpy重名,将其改为myStrcpy。
程序的输出如图2:
图2assert的输出
失败的断言也会弹出如图1所示的对话框,这是因为_assert()函数中也调用了abort()函数。
一定要记住的是assert本质上是一个宏,而不是一个函数,因而不能把带有副作用的表达式放入assert的"参数"中。
1.3errno
errno在C程序中是一个全局变量,这个变量由C运行时库函数设置,用户程序需要在程序发生异常时检测之。
C运行库中主要在math.h和stdio.h头文件声明的函数中使用了errno,前者用于检测数学运算的合法性,后者用于检测I/O操作中(主要是文件)的错误,例如:
#include
#include
#include
intmain(void)
{
errno=0;
if(NULL==fopen("d:
\\1.txt","rb"))
{
printf("%d",errno);
}
else
{
printf("%d",errno);
}
return0;
}
在此程序中,如果文件打开失败(fopen返回NULL),证明发生了异常。
我们读取error可以获知错误的原因,如果D盘根目录下不存在"1.txt"文件,将输出2,表示文件不存在;在文件存在并正确打开的情况下,将执行到else语句,输出0,证明errno没有被设置。
VisualC++提供了两种版本的C运行时库。
-个版本供单线程应用程序调用,另一个版本供多线程应用程序调用。
多线程运行时库与单线程运行时库的一个重大差别就是对于类似errno的全局变量,每个线程单独设置了一个。
因此,对于多线程的程序,我们应该使用多线程C运行时库,才能获得正确的error值。
另外,在使用errno之前,我们最好将其设置为0,即执行errno=0的赋值语句。
1.4其它
除了上述异常处理方式外,在C语言中还支持非局部跳转(使用setjmp和longjmp)、信号(使用signal、raise)、返回错误值或回传错误值给参数等方式进行一定能力的异常处理,但是其使用不如1.1~1.3节所介绍方式常用,我们不必过细研究。
从以上分析可知,C语言的异常处理是简单而不全面的。
与C++的异常处理比起来,C语言异常处理相形见绌,它就像娘胎里的雏婴。
2.C++语言异常处理
2.1C++异常处理语法
感谢C++语言的后期改造者们,他们在标准C++语言中专门集成了异常处理的相关语法(与之不同的是,所有的C标准库异常体系都需要运行库的支持,它不是语言内核支持的)。
当然,异常处理被加到程序设计语言中,也是程序语言发展和逐步完善的必然结果。
我们看到,C++不是唯一集成异常处理的语言。
C++的异常处理结构为:
try
{
//可能引发异常的代码
}
catch(type_1e)
{
//type_1类型异常处理
}
catch(type_2e)
{
//type_2类型异常处理
}
catch(...)//会捕获所有未被捕获的异常,必须最后出现
{
}
而异常的抛出方式为使用throw(typee),try、catch和throw都是C++为处理异常而添加的关键字。
看看这个例子:
#include
//定义Point结构体(类)
typedefstructtagPoint
{
intx;
inty;
}Point;
//扔出int异常的函数
staticvoidf(intn)
{
throw1;
}
//扔出Point异常的函数
staticvoidf(Pointpoint)
{
Pointp;
p.x=0;
p.y=0;
throwp;
}
intmain()
{
Pointpoint;
point.x=0;
point.y=0;
try
{
f(point);//抛出Point异常
//f
(1);//抛出int异常
}
catch(inte)
{
printf("捕获到int异常:
%d\n",e);
}
catch(Pointe)
{
printf("捕获到Point异常:
(%d,%d)\n",e.x,e.y);
}
return0;
}
函数f定义了两个版本:
f(int)和f(Point),分别抛出int和Point异常。
当main函数的try{…}中调用f(point)时和f
(1)时,分别输出:
捕获到Point异常:
(0,0)
捕获到int异常:
1
在C++中,throw抛出异常的特点有:
(1)可以抛出基本数据类型异常,如int和char等;
(2)可以抛出复杂数据类型异常,如结构体(在C++中结构体也是类)和类;
(3)C++的异常处理必须由调用者主动检查。
一旦抛出异常,而程序不捕获的话,那么abort()函数就会被调用,弹出如图1所示的对话框,程序被终止;
(4)可以在函数头后加throw([type-ID-list])给出异常规格,声明其能抛出什么类型的异常。
type-ID-list是一个可选项,其中包括了一个或多个类型的名字,它们之间以逗号分隔。
如果函数没有异常规格指定,则可以抛出任意类型的异常。
2.2标准异常
下面给出了C++提供的一些标准异常:
namespacestd
{
//exception派生
classlogic_error;//逻辑错误,在程序运行前可以检测出来
//logic_error派生
classdomain_error;//违反了前置条件
classinvalid_argument;//指出函数的一个无效参数
classlength_error;//指出有一个超过类型size_t的最大可表现值长度的对象的企图
classout_of_range;//参数越界
classbad_cast;//在运行时类型识别中有一个无效的dynamic_cast表达式
classbad_typeid;//报告在表达试typeid(*p)中有一个空指针p
//exception派生
classruntime_error;//运行时错误,仅在程序运行中检测到
//runtime_error派生
classrange_error;//违反后置条件
classoverflow_error;//报告一个算术溢出
classbad_alloc;//存储分配错误
}
请注意观察上述类的层次结构,可以看出,标准异常都派生自一个公共的基类exception。
基类包含必要的多态性函数提供异常描述,可以被重载。
下面是exception类的原型:
classexception
{
public:
exception()throw();
exception(constexception&rhs)throw();
exception&operator=(constexception&rhs)throw();
virtual~exception()throw();
virtualconstchar*what()constthrow();
};
其中的一个重要函数为what(),它返回一个表示异常的字符串指针。
下面我们从exception类派生一个自己的类:
#include
#include
usingnamespacestd;
classmyexception:
publicexception
{
public:
myexception():
exception("一个重载exception的例子")
{}
};
intmain()
{
try
{
throwmyexception();
}
catch(exception&r)//捕获异常
{
cout<<"捕获到异常:
"< } return0; } 程序运行,输出: 捕获到异常: 一个重载exception的例子 一般的,我们直接以基类捕获异常,例如,本例中使用了 catch(exception&r) 然后根据基类的多态性进行处理,这是因为基类中的what函数是虚函数。 2.3异常处理函数 在标准C++中,还定义了数个异常处理的相关函数和类型(包含在头文件 namespacestd { //EH类型 classbad_exception; classexception; typedefvoid(*terminate_handler)(); typedefvoid(*unexpected_handler)(); //函数 terminate_handlerset_terminate(terminate_handler)throw(); unexpected_handlerset_unexpected(unexpected_handler)throw(); voidterminate(); voidunexpected(); booluncaught_exception(); } 其中的terminate相关函数与未被捕获的异常有关,如果一种异常没有被指定catch模块,则将导致terminate()函数被调用,terminate()函数中会调用ahort()函数来终止程序。 可以通过set_terminate(terminate_handler)函数为terminate()专门指定要调用的函数,例如: #include #include usingnamespacestd; //定义Point结构体(类) typedefstructtagPoint { intx; inty; }Point; //扔出Point异常的函数 staticvoidf() { Pointp; p.x=0; p.y=0; throwp; } //set_terminate将指定的函数 voidterminateFunc() { printf("set_terminate指定的函数\n"); } intmain() { set_terminate(terminateFunc); try { f();//抛出Point异常 } catch(int)//捕获int异常 { printf("捕获到int异常"); } //Point将不能被捕获到,引发terminateFunc函数被执行 return0; } 这个程序将在控制台上输出"set_terminate指定的函数"字符串,因为Point类型的异常没有被捕获到。 当然,它也会弹出图1所示对话框(因为调用了abort()函数)。 上述给出的仅仅是一个set_terminate指定函数的例子。 在实际工程中,往往使用set_terminate指定的函数进行一些清除性的工作,其后再调用exit(int)函数终止程序。 这样,abort()函数就不会被调用了,也不会输出图1所示对话框。 关于标准C++的异常处理,还包含一些比较复杂的技巧和内容,我们可以查阅《moreeffectiveC++》的条款9~条款15。 3.MFC异常处理 MFC中异常处理的语法和语义构建在标准C++异常处理语法和语义的基础之上,其解决方案为: MFC异常处理=MFC异常处理类+宏 3.1宏 MFC定义了TRY、CATCH(及AND_CATCH、END_CATCH)和THROW(及THROW_LAST)等用于异常处理的宏,其本质上也是标准C++的try、catch和throw的进一步强化,由这些宏的定义可知: #ifndef_AFX_OLD_EXCEPTIONS #defineTRY{AFX_EXCEPTION_LINK_afxExceptionLink;try{ #defineCATCH(class,e)}catch(class*e)\ {ASSERT(e->IsKindOf(RUNTIME_CLASS(class)));\ _afxExceptionLink.m_pException=e; #defineAND_CATCH(class,e)}catch(class*e)\ {ASSERT(e->IsKindOf(RUNTIME_CLASS(class)));\ _afxExceptionLink.m_pException=e; #defineEND_CATCH}} #defineTHROW(e)throwe #defineTHROW_LAST()(AfxThrowLastCleanup(),throw) //Advancedmacrosforsmallercode #defineCATCH_ALL(e)}catch(CException*e)\ {{ASSERT(e->IsKindOf(RUNTIME_CLASS(CException)));\ _afxExceptionLink.m_pException=e; #defineAND_CATCH_ALL(e)}catch(CException*e)\ {{ASSERT(e->IsKindOf(RUNTIME_CLASS(CException)));\ _afxExceptionLink.m_pException=e; #defineEND_CATCH_ALL}}} #defineEND_TRY}catch(CException*e)\ {ASSERT(e->IsKindOf(RUNTIME_CLASS(CException)));\ _afxExceptionLink.m_pException=e;}} 这些宏在使用语法上,有如下特点: (1)用TRY块包含可能产生异常的代码; (2)用CATCH块检测并处理异常。 要注意的是,CATCH块捕获到的不是异常对象,而是指向异常对象的指针。 此外,MFC靠动态类型来辨别异常对象; (3)可以在一个TRY块上捆绑多个异常处理捕获块,第一次捕获使用宏CATCH,以后的使用AND_CATCH,而END_CATCH则用来结束异常捕获队列; (4)在异常处理程序内部,可以用THROW_LAST再次抛出最近一次捕获的
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Visual C+中的异常处理浅析 C+ 中的 异常 处理 浅析
![提示](https://static.bdocx.com/images/bang_tan.gif)