一、简介
一个 exception ( 或者是critical error, 或者是crash) 通常意味着你的程序停止正常工作,需要停止执行。例如,由于序访问了无效的内存地址(例如NULL
指针),无法分配内存缓冲区(内存不足),C 运行时库 (CRT) 检测到错误并请求程序,可能会发生异常终止等情况。
C++ 程序可以处理多种类型的异常:
- 通过操作系统的结构化异常处理机制产生的 SEH 异常;
- 由 C 运行时库产生的 CRT 错误;
- 信号
每种错误类型都需要配置一个异常处理函数来拦截异常并执行一些错误恢复操作。
如果应用程序有多个执行线程,事情可能会更加复杂。一些异常处理程序适用于整个进程,但有些仅适用于当前线程。所以必须在每个线程中安装异常处理程序。
应用程序中的每个模块(EXE 或 DLL)都链接到 CRT 库(静态或动态)。异常处理技术强烈依赖于 CRT 链接类型。
错误类型的多样性、多线程程序中处理异常的差异以及异常处理对 CRT 链接的依赖性需要大量的工作来准确处理您的应用程序允许处理的所有异常。
二、关于异常
异常或严重错误通常意味着程序停止正常工作并需要停止执行。主要有以下原因造成:
- 程序访问了无效的内存地址(例如 NULL 指针)
- 由于无限递归导致堆栈溢出
- 大块数据写入小缓冲区
- 调用 C++ 类的纯虚方法
- 无法分配内存缓冲区(内存不足)
- 将无效参数传递给 C++ 系统函数
- C 运行时库检测错误并请求程序终止
主要有两种性质不同的异常:SEH 异常(结构化异常处理,SEH)和类型化 C++ 异常
1、SEH异常
操作系统提供了结构化异常处理机制,这意味着所有 Windows 应用程序都可以引发和处理 SEH 异常。SEH 异常最初是为 C 语言设计的,但它们也可以在 C++ 中使用。
SEH 异常使用__try{}__except(){}
构造处理。
代码示例:
int* p = NULL; // pointer to NULL
__try
{
// Guarded code
*p = 13; // causes an access violation exception
}
__except(EXCEPTION_EXECUTE_HANDLER) // Here is exception filter expression
{
// Here is exception handler
// Terminate program
ExitProcess(1);
}
2、C++ 类型异常
C++ 类型异常机制由 C 运行时库提供,这意味着只有 C++ 应用程序可以引发和处理此类异常。C++ 类型异常使用try{} catch{}
构造处理。
代码示例:
// exceptions
#include <iostream>
using namespace std;
int main () {
try
{
throw 20;
}
catch (int e)
{
cout << "An exception occurred. Exception Nr. " << e << endl;
}
return 0;
}
三、结构化异常处理(SEH)
当发生 SEH 异常时,您通常会看到如下图所示的的一个窗口,该窗口提供向 Microsoft 发送错误报告。可以使用RaiseException()
函数模拟 SEH 异常。
每个 SEH 异常都有一个关联的异常代码。
为了拉取异常信息,可以在__except()的参数中,使用GetExceptionCode()
函数提取语句内部的异常代码,使用GetExceptionInformation()
函数提取语句内部的异常信息。但是要使用这些内部函数,通常需要创建自定义异常过滤器。
处理流程图:
示例代码:
//SEH
int seh_filter(unsigned int code, struct _EXCEPTION_POINTERS* ep)
{
// Generate error report
// Execute exception handler
return EXCEPTION_EXECUTE_HANDLER;
}
void main()
{
__try
{
// .. some buggy code here
}
__except(seh_filter(GetExceptionCode(), GetExceptionInformation()))
{
// Terminate program
ExitProcess(1);
}
}
__try{}__except(){}
构造主要是面向 C 的。但是可以将 SEH 异常重定向到 C++ 类型异常并像处理 C++ 类型异常一样处理它。可以使用
_set_se_translator()
C++ 运行时库 (CRT) 提供的函数来完成。
// crt_settrans.cpp
// compile with: /EHa
#include <stdio.h>
#include <windows.h>
#include <eh.h>
void SEFunc();
void trans_func( unsigned int, EXCEPTION_POINTERS* );
class SE_Exception
{
private:
unsigned int nSE;
public:
SE_Exception() {}
SE_Exception( unsigned int n ) : nSE( n ) {}
~SE_Exception() {}
unsigned int getSeNumber() { return nSE; }
};
int main( void )
{
try
{
_set_se_translator( trans_func );
SEFunc();
}
catch( SE_Exception e )
{
printf( "Caught a __try exception with SE_Exception.\n" );
}
}
void SEFunc()
{
__try
{
int x, y=0;
x = 5 / y;
}
__finally
{
printf( "In finally\n" );
}
}
void trans_func( unsigned int u, EXCEPTION_POINTERS* pExp )
{
printf( "In trans_func.\n" );
throw SE_Exception();
}
【缺点】
__try{}__catch(Expression){}
构造的缺点是可能忘记保护可能导致程序无法处理的异常的潜在不正确代码。【改进】
可以使用与
SetUnhandledExceptionFilter()
函数一起设置的顶级未处理异常过滤器来捕获此类未处理的 SEH 异常。异常信息(异常发生前的CPU状态)通过
EXCEPTION_POINTERS
结构体传递给异常处理程序。
代码示例:
LONG WINAPI MyUnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionPtrs)
{
// Do something, for example generate error report
//..
// Execute default exception handler next
return EXCEPTION_EXECUTE_HANDLER;
}
void main()
{
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
// .. some unsafe code here
}
【缺点】
- 在发生异常的线程上下文中调用顶级 SEH 异常处理程序,会影响异常处理程序从某些异常(例如无效堆栈)中恢复的能力;
- 如果异常处理函数位于 DLL 内,则在使用该
SetUnhandledExceptionFilter()
函数时应小心。如果 DLL 在崩溃时被卸载,则行为可能无法预测。
四、向量异常处理
向量异常处理 (VEH) 是结构化异常处理(SEH)的扩展。它是在 Windows XP 中引入的。
要添加向量异常处理程序,可以使用AddVectoredExceptionHandler()
函数。
缺点是 VEH 仅在 Windows XP 及更高版本中可用,因此AddVectoredExceptionHandler()
应在运行时检查函数是否存在。
- VEH 允许监视或处理应用程序的所有 SEH 异常。为了保持向后兼容性,当程序的某些部分发生 SEH 异常时,系统会依次调用已安装的 VEH 处理程序,然后搜索通常的 SEH 处理程序。
- VEH 的一个优点是能够链接异常处理程序,因此如果在上层安装了向量异常处理程序,仍然可以拦截异常。
当需要像调试器一样监视_all_SEH 异常时,向量异常处理是合适的。但问题是必须决定要处理哪些异常以及跳过哪些异常。
在程序的代码中,某些异常可能会被__try{}__except(){}
构造有意地保护,并且通过在 VEH 中处理此类异常而不将其传递给基于帧的 SEH 处理程序,因此可能会在应用程序逻辑中引入错误。
【注】关于回调函数的选取
SetUnhandledExceptionFilter()
函数比 VEH 更适合异常处理,因为它是顶级 SEH 处理程序。如果未处理异常,则调用顶级 SEH 处理程序,无需决定是否应跳过异常。