C++
之
try-except
转载自:
http://blog.csdn.net/chief1985/archive/2008/05/13/2443235.aspx
导读:
从本篇文章开始,将全面阐述
__try
,
__except
,
__finally
,
__leave
异常模型机制,它也即是
Windows
系列操作系统平台上提供的
SEH
模型。主人公阿愚将在这里与大家分享
SEH
的学习过程和经验总结。
SEH
有两项非常强大的功能。当然,首先是异常处理模型了,因此,这篇文章首先深入阐述
SEH
提供的异常处理模型。另外,
SEH
还有一个特别强大的功能,这将在下一篇文章中进行详细介绍。
try-except
入门
SEH
的异常处理模型主要由
try-except
语句来完成,它与标准
C++
所定义的异常处理模型非常类似,也都是可以定义出受监控的代码模块,以及定义异常处理模块等。还是老办法,看一个例子先,代码如下:
//seh-test.c
#include
void main()
{
puts(“hello”);
//
定义受监控的代码模块
__try
{
puts(“in try”);
}
//
定义异常处理模块
__except(1)
{
puts(“in except”);
}
puts(“world”);
}
呵呵!是不是很简单,而且与
C++
异常处理模型很相似。当然,为了与
C++
异常处理模型相区别,
VC
编译器对关键字做了少许变动。首先是在每个关键字加上两个下划线作为前缀,这样既保持了语义上的一致性,另外也尽最大可能来避免了关键字的有可能造成名字冲突而引起的麻烦等;其次,
C++
异常处理模型是使用
catch
关键字来定义异常处理模块,而
SEH
是采用
__except
关键字来定义。并且,
catch
关键字后面往往好像接受一个函数参数一样,可以是各种类型的异常数据对象;但是
__except
关键字则不同,它后面跟的却是一个表达式(可以是各种类型的表达式,后面会进一步分析)。
try-except
进阶
与
C++
异常处理模型很相似,在一个函数中,可以有多个
try-except
语句。它们可以是一个平面的线性结构,也可以是分层的嵌套结构。例程代码如下:
//
例程
1
//
平面的线性结构
#include
void main()
{
puts(“hello”);
__try
{
puts(“in try”);
}
__except(1)
{
puts(“in except”);
}
//
又一个
try-except
语句
__try
{
puts(“in try”);
}
__except(1)
{
puts(“in except”);
}
puts(“world”);
}
//
例程
2
//
分层的嵌套结构
#include
void main()
{
puts(“hello”);
__try
{
puts(“in try”);
//
又一个
try-except
语句
__try
{
puts(“in try”);
}
__except(1)
{
puts(“in except”);
}
}
__except(1)
{
puts(“in except”);
}
puts(“world”);
}
//
例程
3
//
分层的嵌套在
__except
模块中
#include
void main()
{
puts(“hello”);
__try
{
puts(“in try”);
}
__except(1)
{
//
又一个
try-except
语句
__try
{
puts(“in try”);
}
__except(1)
{
puts(“in except”);
}
puts(“in except”);
}
puts(“world”);
}
1.
受监控的代码模块被执行(也即
__try
定义的模块代码);
2.
如果上面的代码执行过程中,没有出现异常的话,那么控制流将转入到
__except
子句之后的代码模块中;
3.
否则,如果出现异常的话,那么控制流将进入到
__except
后面的表达式中,也即首先计算这个表达式的值,之后再根据这个值,来决定做出相应的处理。这个值有三种情况,如下:
EXCEPTION_CONTINUE_EXECUTION (–1)
异常被忽略,控制流将在异常出现的点之后,继续恢复运行。
EXCEPTION_CONTINUE_SEARCH (0)
异常不被识别,也即当前的这个
__except
模块不是这个异常错误所对应的正确的异常处理模块。系统将继续到上一层的
try-except
域中继续查找一个恰当的
__except
模块。
EXCEPTION_EXECUTE_HANDLER (1)
异常已经被识别,也即当前的这个异常错误,系统已经找到了并能够确认,这个
__except
模块就是正确的异常处理模块。控制流将进入到
__except
模块中。
try-except
深入
上面的内容中已经对
try-except
进行了全面的了解,但是有一点还没有阐述到。那就是如何在
__except
模块中获得异常错误的相关信息,这非常关键,它实际上是进行异常错误处理的前提,也是对异常进行分层分级别处理的前提。可想而知,如果没有这些起码的信息,异常处理如何进行?因此获取异常信息非常的关键。
Windows
提供了两个
API
函数,如下:
LPEXCEPTION_POINTERS GetExceptionInformation(VOID);
DWORD GetExceptionCode(VOID);
其中
GetExceptionCode()
返回错误代码,而
GetExceptionInformation()
返回更全面的信息,看它函数的声明,返回了一个
LPEXCEPTION_POINTERS
类型的指针变量。那么
EXCEPTION_POINTERS
结构如何呢?如下,
typedef struct _EXCEPTION_POINTERS { // exp
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS;
呵呵!仔细瞅瞅,这是不是和上一篇文章中,用户程序所注册的异常处理的回调函数的两个参数类型一样。是的,的确没错!其中
EXCEPTION_RECORD
类型,它记录了一些与异常相关的信息;而
CONTEXT
数据结构体中记录了异常发生时,线程当时的上下文环境,主要包括寄存器的值。因此有了这些信息,
__except
模块便可以对异常错误进行很好的分类和恢复处理。不过特别需要注意的是,这两个函数只能是在
__except
后面的括号中的表达式作用域内有效,否则结果可能没有保证(至于为什么,在后面深入分析异常模型的实现时候,再做详细阐述)。看一个例程吧!代码如下:
#include
#include
int exception_access_violation_filter(LPEXCEPTION_POINTERS p_exinfo)
{
if(p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
printf(”
存储保护异常
/n”);
return 1;
}
else return 0;
}
int exception_int_divide_by_zero_filter(LPEXCEPTION_POINTERS p_exinfo)
{
if(p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
{
printf(”
被
0
除异常
/n”);
return 1;
}
else return 0;
}
void main()
{
puts(“hello”);
__try
{
__try
{
int* p;
//
下面将导致一个异常
p = 0;
*p = 45;
}
//
注意,
__except
模块捕获一个存储保护异常
__except(exception_access_violation_filter(GetExceptionInformation()))
{
puts(”
内层的
except
块中
“);
}
}
//
注意,
__except
模块捕获一个被
0
除异常
__except(exception_int_divide_by_zero_filter(GetExceptionInformation()))
{
puts(”
外层的
except
块中
“);
}
puts(“world”);
}
上面的程序运行结果如下:
hello
存储保护异常
内层的
except
块中
world
Press any key to continue
呵呵!感觉不错,大家可以在上面的程序基础之上改动一下,让它抛出一个被
0
除异常,看程序的运行结果是不是如预期那样。
最后还有一点需要阐述,在
C++
的异常处理模型中,有一个
throw
关键字,也即在受监控的代码中抛出一个异常,那么在
SEH
异常处理模型中,是不是也应该有这样一个类似的关键字或函数呢?是的,没错!
SEH
异常处理模型中,对异常划分为两大类,第一种就是上面一些例程中所见到的,这类异常是系统异常,也被称为硬件异常;还有一类,就是程序中自己抛出异常,被称为软件异常。怎么抛出呢?还是
Windows
提供了的
API
函数,它的声明如下:
VOID RaiseException(
DWORD dwExceptionCode, // exception code
DWORD dwExceptionFlags, // continuable exception flag
DWORD nNumberOfArguments, // number of arguments in array
CONST DWORD *lpArguments // address of array of arguments
);
很简单吧!实际上,在
C++
的异常处理模型中的
throw
关键字,最终也是对
RaiseException()
函数的调用,也即是说,
throw
是
RaiseException
的上层封装的更高级一类的函数,这以后再详细分析它的代码实现。这里还是看一个简单例子吧!代码如下:
#include
#include
int seh_filer(int code)
{
switch(code)
{
case EXCEPTION_ACCESS_VIOLATION :
printf(”
存储保护异常,错误代码:
%x/n”, code);
break;
case EXCEPTION_DATATYPE_MISALIGNMENT :
printf(”
数据类型未对齐异常,错误代码:
%x/n”, code);
break;
case EXCEPTION_BREAKPOINT :
printf(”
中断异常,错误代码:
%x/n”, code);
break;
case EXCEPTION_SINGLE_STEP :
printf(”
单步中断异常,错误代码:
%x/n”, code);
break;
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED :
printf(”
数组越界异常,错误代码:
%x/n”, code);
break;
case EXCEPTION_FLT_DENORMAL_OPERAND :
case EXCEPTION_FLT_DIVIDE_BY_ZERO :
case EXCEPTION_FLT_INEXACT_RESULT :
case EXCEPTION_FLT_INVALID_OPERATION :
case EXCEPTION_FLT_OVERFLOW :
case EXCEPTION_FLT_STACK_CHECK :
case EXCEPTION_FLT_UNDERFLOW :
printf(”
浮点数计算异常,错误代码:
%x/n”, code);
break;
case EXCEPTION_INT_DIVIDE_BY_ZERO :
printf(”
被
0
除异常,错误代码:
%x/n”, code);
break;
case EXCEPTION_INT_OVERFLOW :
printf(”
数据溢出异常,错误代码:
%x/n”, code);
break;
case EXCEPTION_IN_PAGE_ERROR :
printf(”
页错误异常,错误代码:
%x/n”, code);
break;
case EXCEPTION_ILLEGAL_INSTRUCTION :
printf(”
非法指令异常,错误代码:
%x/n”, code);
break;
case EXCEPTION_STACK_OVERFLOW :
printf(”
堆栈溢出异常,错误代码:
%x/n”, code);
break;
case EXCEPTION_INVALID_HANDLE :
printf(”
无效句病异常,错误代码:
%x/n”, code);
break;
default :
if(code & (1<<29))
printf(”
用户自定义的软件异常,错误代码:
%x/n”, code);
else
printf(”
其它异常,错误代码:
%x/n”, code);
break;
}
return 1;
}
void main()
{
puts(“hello”);
__try
{
puts(“try
块中
“);
//
注意,主动抛出一个软异常
RaiseException(0xE0000001, 0, 0, 0);
}
__except(seh_filer(GetExceptionCode()))
{
puts(“except
块中
“);
}
puts(“world”);
}
上面的程序运行结果如下:
hello
try
块中
用户自定义的软件异常,错误代码:
e0000001
except
块中
world
Press any key to continue
上面的程序很简单,这里不做进一步的分析。我们需要重点讨论的是,在
__except
模块中如何识别不同的异常,以便对异常进行很好的分类处理。毫无疑问,它当然是通过
GetExceptionCode()
或
GetExceptionInformation ()
函数来获取当前的异常错误代码,实际也即是
DwExceptionCode
字段。异常错误代码在
winError.h
文件中定义,它遵循
Windows
系统下统一的错误代码的规则。每个
DWORD
被划分几个字段,如下表所示:
例如我们可以在
winbase.h
文件中找到
EXCEPTION_ACCESS_VIOLATION
的值为
0 xC0000005
,将这个异常代码值拆开,来分析看看它的各个
bit
位字段的涵义。
C 0 0 0 0 0 0 5
(十六进制)
1100 0000 0000 0000 0000 0000 0000 0101
(二进制)
第
3 0
位和第
3 1
位都是
1
,表示该异常是一个严重的错误,线程可能不能够继续往下运行,必须要及时处理恢复这个异常。第
2 9
位是
0
,表示系统中已经定义了异常代码。第
2 8
位是
0
,留待后用。第
1 6
位至
2 7
位是
0
,表示是
FACILITY_NULL
设备类型,它代表存取异常可发生在系统中任何地方,不是使用特定设备才发生的异常。第
0
位到第
1 5
位的值为
5
,表示异常错误的代码。
如果程序员在程序代码中,计划抛出一些自定义类型的异常,必须要规划设计好自己的异常类型的划分,按照上面的规则来填充异常代码的各个字段值,如上面示例程序中抛出一个异常代码为
0xE0000001
软件异常。
总结
(
1
)
C++
异常模型用
try-catch
语法定义,而
SEH
异常模型则用
try-except
语法;
(
2
)
与
C++
异常模型相似,
try-except
也支持多层的
try-except
嵌套。
(
3
)
与
C++
异常模型不同的是,
try-except
模型中,一个
try
块只能是有一个
except
块;而
C++
异常模型中,一个
try
块可以有多个
catch
块。
(
4
)
与
C++
异常模型相似,
try-except
模型中,查找搜索异常模块的规则也是逐级向上进行的。但是稍有区别的是,
C++
异常模型是按照异常对象的类型来进行匹配查找的;而
try-except
模型则不同,它通过一个表达式的值来进行判断。如果表达式的值为
1
(
EXCEPTION_EXECUTE_HANDLER
),表示找到了异常处理模块;如果值为
0
(
EXCEPTION_CONTINUE_SEARCH
),表示继续向上一层的
try-except
域中继续查找其它可能匹配的异常处理模块;如果值为
-1
(
EXCEPTION_CONTINUE_EXECUTION
),表示忽略这个异常,注意这个值一般很少用,因为它很容易导致程序难以预测的结果,例如,死循环,甚至导致程序的崩溃等。
(
5
)
__except
关键字后面跟的表达式,它可以是各种类型的表达式,例如,它可以是一个函数调用,或是一个条件表达式,或是一个逗号表达式,或干脆就是一个整型常量等等。最常用的是一个函数表达式,并且通过利用
GetExceptionCode()
或
GetExceptionInformation ()
函数来获取当前的异常错误信息,便于程序员有效控制异常错误的分类处理。
(
6
)
SEH
异常处理模型中,异常被划分为两大类:系统异常和软件异常。其中软件异常通过
RaiseException()
函数抛出。
RaiseException()
函数的作用类似于
C++
异常模型中的
throw
语句。