因为工作需要,又要研究一下C++的Exception机制,用MSVC的话,自然又要了解一下Windows的Standard Exception Handling(简称SEH,不是SHE),实际上MSVC的Exception机制就是基于SEH的。
在这个链接(
http://51cmm.csai.cn/ExpertEyes/No159.htm)上,介绍了一下SEH在VC中的体现,就是__try/__except关键字,在__excep后面的()中是一个表达式,值可以是
EXCEPTION_CONTINUE_EXECUTION (–1) 异常被忽略,控制流将在异常出现的点之后,继续恢复运行。
EXCEPTION_CONTINUE_SEARCH (0) 异常不被识别,也即当前的这个__except模块不是这个异常错误所对应的正确的异常处理模块。系统将继续到上一层的try-except域中继续查找一个恰当的__except模块。
EXCEPTION_EXECUTE_HANDLER (1) 异常已经被识别,也即当前的这个异常错误,系统已经找到了并能够确认,这个__except模块就是正确的异常处理模块。控制流将进入到__except模块中。
EXCEPTION_CONTINUE_SEARCH (0) 异常不被识别,也即当前的这个__except模块不是这个异常错误所对应的正确的异常处理模块。系统将继续到上一层的try-except域中继续查找一个恰当的__except模块。
EXCEPTION_EXECUTE_HANDLER (1) 异常已经被识别,也即当前的这个异常错误,系统已经找到了并能够确认,这个__except模块就是正确的异常处理模块。控制流将进入到__except模块中。
文中还举了一个使用EXCEPTION_CONTINUE_EXECUTION的例子,如下
- #include <stdio.h>
- void main()
- {
- int j, zero;
- puts("hello");
- __try
- {
- puts("main()函数的try块中");
- zero = 0;
- j = 10;
- // 下面将导致一个异常
- j = 45 / zero;
- // 注意,异常出现后,程序控制流又恢复到了这里
- printf("这里会执行到吗?值有如何呢?j=%d /n", j);
- }
- // 注意,这里把zero变量赋值为1,试图恢复错误,
- // 当控制流恢复到原来异常点时,避免了异常的再次发生
- __except(puts("in filter"), zero = 1, -1)
- {
- puts("main()函数的except块中");
- }
- puts("world");
- }
hello
main()函数的try块中
in filter
这里会执行到吗?值有如何呢?j=45
world
Press any key to continue
也许在原作者的开发环境下,的确是这个结果,但是......
这段程序想演示一个recover exception的技巧,在C/C++中,用逗号串起来的表达式还是一个表达式,这个整体表达式的值是最后一个表达式的值,所以
puts("in filter"), zero = 1, -1的返回值是最后一个值(-1),也就是EXCEPTION_CONTINUE_EXECUTION,而之前的表达式也会被evaluate,也就是"in filter"会被打印,而且zero也会被赋为1,这样,在这个thread回去再次执行
j = 45 / zero;的时候,zero已经是1了,于是是不会再次有异常发生。真的是这样吗?不见得,在我的机器上,这段程序陷入一个死循环。
我用VS2005的Debug模式看了一下生成的汇编代码,发现问题所在了,zero的值一开始就被MOV到ECX中了,然后把45这个常数MOV到EAX中,然后IDIV EAX, ECX,再看__except部分的汇编代码,zero=1的汇编代码只是修改了表示zero的内存,并没有动ECX,这样__except在ignore这个exception之后,并不是直观上的回去执行“j=45/zero”这个C++语句,而是去执行“IDIV EAX,ECX”这条机器指令,而这时候ECX还是0,所以又是一个exception,陷入死循环!
从中需要加深几点意识:
1) __except(EXCEPTION_CONTINUE_EXECUTION)是让thread回到发生exception的机器指令,不是回到发生exception的C/C++语句;
2) 没有多少适合用__except(EXCEPTION_CONTINUE_EXECUTION)的场合,除非__try部分是用汇编写的。