终于放假了,而我的csdn博客也是长期没有整理。最近我常常考虑异常的问题,所以今天的时候我探究了一下c++究竟是怎么实现异常的。写完这篇之后,我感觉我对异常的了解更深了。
C++的Exception的中文翻译“异常”决定了它的主要用途必然是用于错误处理;确切的说,是为处理当前模块无法处理的情况而把处理权交给上一层模块。不过,当然不能被这个词语所局限。抛出Exception的不一定是异常的状况。有的时候我们会遇到这种情况:一个函数可能需要根据不同情况返回多种不同类型的值、或者一个函数可能需要或可能不需要返回值。利用Exception可以优雅地解决这些问题。标准情况只有一个返回方法(return关键字),而Exception可以提供另一种更加灵活的返回方法。
之所以Exception适用于错误处理,是因为错误本身是不同于函数本身返回值类型的返回值。
/*******************************************
<Author>Bowen YU</Author>
<Describe>
Bank System: My Program for CPP Examination
Demonstrating an uncommon use of exception
</Describe>
<Todo></Todo>
*******************************************/
#include "Account.h"
#include "Ui.h"
#include <iostream>
using namespace std;
int main(void)
{
try{
for(;;)
{
display_Main();
dologic_Main();
}
}catch(ExceptionQuit)
{
}
return 0;
}
以上代码是我在C++考试写的一个命令行交互程序,本来需要一个标志exit来标记是否退出,并且每次for循环的时候都需要判断是否exit。我写的这段代码则避免了繁琐,写得很简洁。
/*******************************************
<Author>Bowen YU</Author>
<Describe>
A Simple Program Demonstrating The Use of Exception
</Describe>
<Todo></Todo>
*******************************************/
class ExceptionSimple { };
int f(bool p)
{
if(p)
{
throwExceptionSimple();
}
else
{
return0;
}
}
int main(void)
{
try
{
f(false);
f(true);
}
catch(ExceptionSimplees)
{
__asm__("nop;nop; nop;");
}
return 0;
}
其中main的汇编代码,注意到LEHB0与LEHE0包含了try块里面的代码。
LEHB0:
call __Z1fb //f(0);
movl $1, (%esp)
call __Z1fb //f(1);
LEHE0:
L7:
movl $0, %eax //return 0;
leave
ret
f的汇编代码,cxa_allocate_exception(1)给抛出的对象分配内存,而cxa_throw(0,&_ZTI15ExceptionSimple,%eax)抛出异常。
pushl %ebp
movl %esp,%ebp
subl $40,%esp
movl 8(%ebp),%eax
movb %al,-12(%ebp)
cmpb $0,-12(%ebp)
je L2
movl $1,(%esp)
call cxa_allocate_exception
movl $0, 8(%esp)
movl $ZTI15ExceptionSimple,4(%esp)
movl %eax,(%esp)
call cxa_throw
L2:
movl $0,%eax
leave
ret
异常处理代码
L8:
cmpl $1,%edx
je L6
movl %eax,(%esp)
call Unwind_Resume
L6:
movl %eax,(%esp)
call cxa_begin_catch
nop; nop;nop;
call cxa_end_catch
jmp L7
L7:
movl $0,%eax //return 0; of main()
leave
ret
Exception Table
.section .gcc_except_table,"w"
.align 4
…
.uleb128LEHB0-LFB1
.uleb128LEHE0-LEHB0
.uleb128L8-LFB1
…
_ ZTS15ExceptionSimple记录了异常名,是个字符串。
而_ZTI15ExceptionSimple是个结构体,分别包含一个int和一个char*。其中int型的__ZTVN10__cxxabiv117__class_type_infoE+8应该可以推测类的信息。
__ZTI15ExceptionSimple:
.long __ZTVN10__cxxabiv117__class_type_infoE+8
.long __ZTS15ExceptionSimple
__ZTS15ExceptionSimple:
.ascii"15ExceptionSimple\0"
由上面所给出的材料可以得出的一些结论:
1. MinGW实现异常机制的时候引入了“异常表”(Exception Table),抛出异常之后会在异常表内寻找异常处理程序的位置。并且这里被抛出的对象在运行期不仅仅具有值,还有表示其类型的唯一标识符和显示名(见ZTS15ExceptionSimple),与通常时候类型信息仅仅在编译期存在的观念不一样(与此类比的是RTTI(运行时类型))。因此决定了C++中throw可以抛出不同类型的对象并且可以根据其类型选择异常处理程序。
2. 异常一个重量级的特性,比一般的return要付出更多的性能代价。不要在性能敏感的地方使用异常,也不要把经常发生的情况交给异常处理程序。
俞博文 2013-1-21