01
介绍
作为一名C++开发者,当提到异常处理时,我们往往会联想到try,catch、throw等关键字。这些关键字我们并不陌生。虽然实际应用中很少使用,但却不可否认C++这一机制的强大作用。
(对于不了解异常处理是什么的小伙伴,推荐翻看《C++ Primer》一书的相关章节)。
对于C++异常处理的强大及使用,这里我不再赘述。而作为一名Windows平台开发者,今天我准备给大家介绍另一种异常处理机制:Windows平台下的Windows结构化异常处理(Structured exception handling, Windows SEH)。
SEH可以捕获到操作系统级别的异常事件。某些场景下可以帮助开发者更准确、更快速的定位问题。但SEH只能再Windows平台下工作。无法满足跨平台产品的需求。
(对于Windows平台下的开发者来说,学会使用SEH来分析缺陷仍有很大的必要性。)
02
应用
Windows SEH主要包含两个主要功能:终止处理、异常处理(重点)。
提供了以下关键字:__try、__finally、__except、__leave。
终止处理:使用try、finally、leave关键字。
当try中的代码逻辑无论以何种方式退出时,finally中的代码都被执行。
异常处理:使用try、except关键字。
当try中的代码逻辑产生异常事件时,except中可以接收到对应的异常事件。
下面介绍一下这两种功能的使用方式。
终止处理
我们先看以下代码片段:
代码1:
__try {
num = 5; // step1
}
__finally {
num = 6; // step2
}
return num; // step3
代码2:
__try {
num = 5; // step1
return num; // step3
}
__finally {
num = 8; // step2
}
num = 7;
return num;
代码3:
__try {
lock.Lock(); // step1
return ; // step3
}
__finally {
lock.UnLock(); // step2
}
return;
代码1返回值是6,代码2返回值是8。从注释中我们不难看出执行的时序,无论try执行是完成,还是中途return,finally中的代码都将执行。这种机制确保函数退出前可以让开发者做一些事情。
例如:代码3中我们用这样的方式保证lock锁在return时,一定执行UnLock过程。
当然,我们也可以定义一个class类型的局部类对象,利用作用域来控制解锁的时机。我们不对比哪种方法更优秀,开发者应根据具体情况来判断使用哪一种方式。
至于try-finally的实现,则是由编译器来完成。当进入和退出一个try时,编译器将产生特殊的代码,同时也将产生一些表,以支持处理SEH。需要一提的是代码2中step3。编译器再监测到try语句中包含return、goto、longjump时将生成额外的代码,这将产生不必要的开销。我们可以使用leave关键字来降低开销。
(如果是性能敏感的代码应该避免使用try-finally。)
使用leave关键字可以告诉编译器直接跳转到finally中,无需再进行监测及生成额外代码。leave的使用也比较简单。在代码3中step3处,直接用__leave替换return关键字即可。
异常处理
异常处理用于捕获程序中产生的异常事件。这对于开发者来说实用性更高。异常事件又分为硬件异常、软件异常。
硬件异常:是指CPU产生的异常事件。
软件异常:是指代码中生成一个异常事件。
通过try-except捕获并处理这些异常事件。对调查某些内存异常问题帮助很大。
我们先看下代码片段:
代码4:
__try {
num = num / 0; // step1
}
__except (EXCEPTION_EXECUTE_HANDLER) {
num = 0; // step2
}
return num; // step3
代码5:
__try {
num = num / 0; // step1
}
__except (
GetExceptionCode() ==EXCEPTION_IN_DIVIDE_BY_ZERO||
GetExceptionCode() ==EXCEPTION_FLT_DIVIDE_BY_ZERO) {
num = 0; // step2
}
return num; // step3
try-except的使用与C++的try、catch比较相似。代码5中,except过滤器通过简单的判断语句,便可以筛选出一个或多个异常事件进行处理。
代码5中的GetExceptionCode是一个宏定义,并且只能在异常过滤器里(即except之后的括号里)或异常处理代码里面调用。否则编译器会报错。
对于除0问题,CPU会抛出异常事件,我们在except中可以直接捕获到EXCEPTION_IN_DIVIDE_BY_ZERO或EXCEPTION_FLT_DIVIDE_BY_ZERO异常事件。并且进行了简单的纠正处理。这样程序仍可以继续运行。对于那种即使已经出错了,也要继续正确运行的软件来说,这样的机制尤为重要。
而C++往往只能判断除数是否为零,然后throw异常。这对于复杂的程序来说实际作用并不大。
下面,我们列举部分SEH支持的异常事件:
内存相关:
EXCFEPTION_ACCESS_VIOLATION:线程对一个虚拟地址读写而没有访问权。(常见)
EXCFEPTION_DATATYPE_MISALIGNMENT:线程读写未对齐数据。
EXCFEPTION_GUARD_PAGE:试图访问带有保护属性的内存页
EXCFEPTION_STACK_OVERFLOW:线程已经使用了所有栈。
调试相关:
EXCFEPTION_BREAKPOINT:遇到一个断点
EXCFEPTION_INVALID_HANDLE:向函数传递了无效的句柄
整数相关:
EXCFEPTION_INT_DIVIDE_BY_ZERO:线程试图用整数0去除另一个整数
浮点数相关:
EXCFEPTION_FLT_DIVIDE_BY_ZERO:线程试图用浮点数0去除另一个浮点数
EXCFEPTION_FLT_STACK_CHECK:浮点操作的结果导致栈上溢或下溢
上述所提供的异常事件可以帮助开发者分析锁定问题。
03
感谢
对于SEH更为复杂的应用,这里就不在介绍了。感兴趣的小伙伴可以翻看《windows核心编程》一书。关于SEH书中有更为全面的讲解与案例。
![a715951ed8ad2cb50a94065108cda776.png](https://img-blog.csdnimg.cn/img_convert/a715951ed8ad2cb50a94065108cda776.png)