4.结构化异常处理
结构化异常处理(Structured Exception Handling,简称SEH)是微软针对Windows程序异常处理进行的扩展,在Visual C++中,它同时支持C和C++语言。SEH不宜与标准C++异常处理和MFC异常处理混用,对于C++程序,微软建议使用标准C++的异常处理。
为了支持SEH,Visual C++中定义了四个关键字(由于这些关键字是非标准关键字,其它编译器不一定支持),用以扩展C 和C++语言:
(1)__except
(2)__finally
(3)__leave
(4)__try
其基本语法为:
或:
其执行的步骤如下:
(1)__try块被执行;
(2)如果__try块没有出现异常,则执行到__except块之后;否则,执行到__except块,根据filter-expression的值决定异常处理方法:
a. filter-expression的值为EXCEPTION_CONTINUE_EXECUTION (-1)
恢复异常,从发生异常处下面开始执行,异常处理函数本身不被执行;
b. filter-expression的值为EXCEPTION_CONTINUE_SEARCH (0)
异常不被识别,拒绝捕获异常,继续搜索下一个异常处理函数;
c. filter-expression的值为EXCEPTION_EXECUTE_HANDLER (1)
异常被识别,终止异常,从异常发生处开始退栈,一路上遇到的终止函数都被执行。
看看这个例子:
程序的输出为:
如果我们把__except(puts("in filter"), 1)改为__except(puts("in filter"), 0),程序的输出将变为:
程序的执行也告崩溃,弹出如图3所示的对话框。
要想这个程序能正确地执行,我们可以在第一个__try块的外面再套一个__try块和一个接收filter-expression返回值为EXCEPTION_EXECUTE_HANDLER的__except块,程序改为:
程序输出:
由此可以看出,因为第一个__except的filter-expression返回EXCEPTION_CONTINUE_SEARCH 的原因,"in except1"没有被输出。程序之所以没有崩溃,是因为最终碰到了接收EXCEPTION_EXECUTE_HANDLER的第2个__except。
SEH使用复杂的地方在于较难控制异常处理的流动方向,弄不好程序就"挂"了。如果把例4-1中的__except(puts("in filter"), 1)改为__except(puts("in filter"), -1),程序会进入一个死循环,输出:
最后疯狂地输出"in filter",我们把断点设置在__except(puts("in filter"), -1)语句之前,按F5会不断进入此断点。
5.各种异常处理的对比
下表给出了从各个方面对这本文所给出的Visual C++所支持的四种异常处理进行的对比:
本文所讲解的仅仅是Visual C++异常处理的初步知识,对于更深入的内容,还需要我们在不断的编程过程中去领悟和学习。
在程序设计过程中,我们不能嫌异常处理"麻烦",对可能的错误视而不见、不加考虑。因为避免了异常处理的"麻烦",将会给我们的程序带来更大的"麻烦"。而程序中包含必要的异常处理,也是对一位优秀程序员的基本要求。
结构化异常处理(Structured Exception Handling,简称SEH)是微软针对Windows程序异常处理进行的扩展,在Visual C++中,它同时支持C和C++语言。SEH不宜与标准C++异常处理和MFC异常处理混用,对于C++程序,微软建议使用标准C++的异常处理。
为了支持SEH,Visual C++中定义了四个关键字(由于这些关键字是非标准关键字,其它编译器不一定支持),用以扩展C 和C++语言:
(1)__except
(2)__finally
(3)__leave
(4)__try
其基本语法为:
__try { ...//可能导致异常的被监控代码块 } __except(filter-expression) { ...//异常处理函数 } |
或:
__try { ... } __finally { ...//终止 } |
其执行的步骤如下:
(1)__try块被执行;
(2)如果__try块没有出现异常,则执行到__except块之后;否则,执行到__except块,根据filter-expression的值决定异常处理方法:
a. filter-expression的值为EXCEPTION_CONTINUE_EXECUTION (-1)
恢复异常,从发生异常处下面开始执行,异常处理函数本身不被执行;
b. filter-expression的值为EXCEPTION_CONTINUE_SEARCH (0)
异常不被识别,拒绝捕获异常,继续搜索下一个异常处理函数;
c. filter-expression的值为EXCEPTION_EXECUTE_HANDLER (1)
异常被识别,终止异常,从异常发生处开始退栈,一路上遇到的终止函数都被执行。
看看这个例子:
//例4-1 #include "stdio.h" void main() { int* p = NULL; // 定义一个空指针 puts("SEH begin"); __try { puts("in try"); __try { puts("in try"); *p = 0; // 引发一个内存访问异常 } __finally { puts("in finally"); } } __except(puts("in filter"), 1) { puts("in except"); } puts("SEH end"); } |
程序的输出为:
SEH begin in try //执行__try块 in try //执行嵌入的__try块 in filter //执行filter-expression,返回EXCEPTION_EXECUTE_HANDLER in finally //展开嵌入的__finally in except //执行对应的__except块 SEH end //处理完毕 |
如果我们把__except(puts("in filter"), 1)改为__except(puts("in filter"), 0),程序的输出将变为:
SEH begin in try //执行__try块 in try //执行嵌入的__try块 in filter //执行filter-expression,返回EXCEPTION_CONTINUE_SEARCH in finally //展开嵌入的__finally |
程序的执行也告崩溃,弹出如图3所示的对话框。
图3 不能被正确执行的SEH |
要想这个程序能正确地执行,我们可以在第一个__try块的外面再套一个__try块和一个接收filter-expression返回值为EXCEPTION_EXECUTE_HANDLER的__except块,程序改为:
//例4-2 #include "stdio.h" void main() { int* p = NULL; // 定义一个空指针 puts("SEH begin"); __try { __try { puts("in try"); __try { puts("in try"); *p = 0; // 引发一个内存访问异常 } __finally { puts("in finally"); } } __except(puts("in filter"), 0) { puts("in except"); } } __except(puts("in filter"), 1) { puts("in except"); } puts("SEH end"); } |
程序输出:
SEH begin in try //执行__try块 in try //执行嵌入的__try块 in filter1 //执行filter-expression,返回EXCEPTION_CONTINUE_SEARCH in filter2 //执行filter-expression,返回EXCEPTION_EXECUTE_HANDLER in finally //展开嵌入的__finally in except2 //执行对应的__except块 SEH end //处理完毕 |
由此可以看出,因为第一个__except的filter-expression返回EXCEPTION_CONTINUE_SEARCH 的原因,"in except1"没有被输出。程序之所以没有崩溃,是因为最终碰到了接收EXCEPTION_EXECUTE_HANDLER的第2个__except。
SEH使用复杂的地方在于较难控制异常处理的流动方向,弄不好程序就"挂"了。如果把例4-1中的__except(puts("in filter"), 1)改为__except(puts("in filter"), -1),程序会进入一个死循环,输出:
SEH begin in try //执行__try块 in try //执行嵌入的__try块 in filter //执行filter-expression,返回EXCEPTION_CONTINUE_EXECUTION in filter in filter in filter in filter …//疯狂输出"in filter" |
最后疯狂地输出"in filter",我们把断点设置在__except(puts("in filter"), -1)语句之前,按F5会不断进入此断点。
5.各种异常处理的对比
下表给出了从各个方面对这本文所给出的Visual C++所支持的四种异常处理进行的对比:
异常处理 | 支持语言 | 是否标准 | 复杂度 | 推荐使用 |
C异常处理 | C语言 | 标准C | 简单 | 推荐 |
C++异常处理 | C++语言 | 标准C++ | 较简单 | 推荐 |
MFC异常处理 | C++语言 | 仅针对MFC程序 | 较简单 | 不推荐 |
SEH异常处理 | C和C++语言 | 仅针对Microsoft编译环境 | 较复杂 | 不推荐 |
本文所讲解的仅仅是Visual C++异常处理的初步知识,对于更深入的内容,还需要我们在不断的编程过程中去领悟和学习。
在程序设计过程中,我们不能嫌异常处理"麻烦",对可能的错误视而不见、不加考虑。因为避免了异常处理的"麻烦",将会给我们的程序带来更大的"麻烦"。而程序中包含必要的异常处理,也是对一位优秀程序员的基本要求。