1、Windows对于C++异常处理的扩展:
为了更便捷的在Windows系统中进行异常处理,微软在C++异常处理的机制上,又增加了扩展,称为SEH(Structured Exception Handling),即结构化异常处理;
语法如下:
__try
{
……
}
__except (<exception>)
{
……
}
Windows exception 最常见的问题 :
runtime_error 运行时错误:仅在运行时才能检测到的问题
range_error 运行时错误:生产的问题超出了有意义的值域范围
overflow_error 运行时错误:计算上溢
underflow_error 运行时错误:计算下溢
logic_error 逻辑错误: 可在运行前检测到的问题
domain_error 逻辑错误: 参数的结果值不存在
invalid_argument 逻辑错误: 不合适的参数
length_error 逻辑错误: 试图生成一个超出该类型最大长度的对象
out_of_range 逻辑错误:使用一个超出有效范围的值
2、__try -__except:
__try-__except是Microsoft扩展出的C++关键字,__try块中出现错误或异常,一般不再用throw抛出,而是直接产生一个EXCEPTION_POINTERS类型的异常数据,然后开始查找SEH例程入口(调试的情况除外)。首先就会找到与__try块对应的__except块。__except的参数<exception>与catch的参数作用完全不同,也不类似于函数的参数,它主要是用于控制后面的程序执行,为这几个值之一:
EXCEPTION_EXECUTE_HANDLER(1),表示下面执行__except块内及其后面的代码
EXCEPTION_CONTINUE_EXECUTION(-1),表示回到抛出异常处继续向下执行
EXCEPTION_CONTINUE_SEARCH(0), 表示查找下一个异常处理例程入口
Microsoft提供两个函数GetExceptionCode(), GetExceptionInformation(),分别可以获取异常号和EXCEPTION_POINTERS类型的异常数据指针。而且这两个函数只能在__except参数<exception>的表达式中使用。为了保证这一点,在VC中,编译器做了特殊处理,如果这两个函数没有在正确的位置,将产生编译错误。
3、例:处理除零异常:
#include <windows.h>
Intmain()
{
inti= 1;
intj = 0;
__try
{
i/= j;
}
__except(GetExceptionCode() ==
EXCEPTION_INT_DIVIDE_BY_ZERO ?
EXCEPTION_EXECUTE_HANDLER :
EXCEPTION_CONTINUE_SEARCH)
{
cout<< “除零异常" << endl;
}
}
4、例:处理写内存异常:
Int main()
{
__try
{
int *p=NULL;
*p = 13;
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
cout << “内存异常" << endl;
}
}
5、Windows异常处理流程:
(1)中止当前程序的执行。
(2)如果程序处于被调试状态,向调试器发送EXCEPTION_DEBUG_EVENT消息。
(3)如果程序没有被调试或者调试器未能处理异常,查找线程相关的异常处理例程(如对应__except块)并处理。如果前面查找到的例程返回 EXCEPTION_CONTINUE_SEARCH,且线程有多个异常处理例程,则沿这些例程入口地址组成的链式结构逐一向后查找,请求下一个例程处理。
(4)如果线程没有对应的异常处理例程,或线程所有例程都返回EXCEPTION_CONTINUE_SEARCH,而且程序处于被调试状态,再次通知调试器。
(5)如果程序没有被调试或者调试器仍未处理异常,则进入主线程的“最终异常处理例程”链继续查找。
(6)“最终异常处理例程”链的最后是Windows默认的系统异常处理程序__CxxUnhandledExceptionFilter(),其处理通常是弹出一个异常对话框,上面显示一些异常信息,提 供“关闭”、“调试”等按钮。
6、 SEH 到 C++异常的转换
在同一个程序中,如果使用WIN32API它会抛出SHE,使用C++库函数,它们又会抛出C++异常,Win32API和C++函数混和使用时如果使用两种异常捕获机制时,使用起来会影响程序的可读性,因此C++运行库提供了_set_se_translator 函数,在SHE异常发生时通过回调方式来转换SEH异常为C++异常。在此提供一个转换的宏来实现转换。
转换宏的代码:
#define INSTALL_SEHCONVERT() ExceptionConvert ecExceptionConvert
class SEHException
{
private :
unsigned int nSE;
public :
SEHException() {}
SEHException( unsigned int n ) : nSE( n ) {}
~SEHException() {}
unsigned int getSeNumber() { return nSE; }
};
class ExceptionConvert
{
public :
ExceptionConvert(){OldFanc = _set_se_translator(trans_func); }
~ExceptionConvert(){_set_se_translator(OldFanc); }
private :
static void trans_func( unsigned int u, EXCEPTION_POINTERS* pExp )
{
throw SEHException(u);
}
_se_translator_function OldFanc;
};
使用上面 INSTALL_SEHCONVERT 宏后就可以使用如下代码来捕获SHE异常了
INSTALL_SEHCONVERT();
Try
{
…
}
catch(SEHException &seh){
…
}
7、 同步异常与异步异常
1) VC的C++ Exception 采用两种模式捕获异常:同步模式和异步模式。VC的工程的调试版本缺省使用异步模式,工程的发布版本缺省使用同步模式。在同步模式下,VC的编译器假定代 码中只有在显示使用throw和调用函数的时候才会引发异常,因此,在同步模式下,VC编译出的代码比较小,但在这种模式下,try-catch对不能捕 获内存访问异常与算术除零异常等。在异步模式下,VC的编译器为try块内的每一条语句生成异常捕获代码,在这种情况下,他能够捕获全部的异常,还能保证 栈上对象在解栈中正确释放。为了要在发行版本中也能够捕获全部异常就需要打开异步模式,但代价是程序编译出代码变大,运行速度变慢。
2)编译选项:
同步模式的编译选项为/EHs或者/GX(等同于/EHsc)
异步模式的编译选项为/EHa
8、 多线程下的异常捕获
在创建线程并运行线程的函数中把创建线程的代码放在try块中并不会捕获到线程函数中发生的异常,线程函数中发生的异常只能在线程函数中捕获。并且每一个线程都需要自己的SHE转换宏。转换宏可以放在线程函数的开始部分