Visual C++异常处理机制原理与应用(四)——C/C++结构化异常处理之try-except异常处理的使用(中)
2017-12-04 13:41
381 查看
在上一篇博文《Visual C++异常处理机制原理与应用(三)——C/C++结构化异常处理之try-except异常处理的使用(上)》中,我们已经对VC中C/C++结构化异常处理的异常处理机制的特点进行了分析。这篇文章主要通过一个实例讲解C/C++结构化异常处理中可能最令人困惑之处——即多级异常处理与终止处理嵌套时程序的执行流程。在开始分析之前,先温习一下两者的特性:
被终止处理程序保护的代码块,将会在执行流程试图越出代码块之前执行局部展开,转入终止处理程序进行执行。如果终止处理程序中没有越出保护块的代码,则执行完终止处理程序后,执行流程返回保护块中,执行引发局部展开的那条代码。
被异常处理程序保护的代码块,如果其中某条指令执行时引发异常,则执行流为从内而外地调用各级except块中的过滤函数或过滤表达式,直到找到一个能处理本异常的try块,然后执行其异常处理代码。
但仔细想想就会发现问题:如果try-except块中嵌套了一个try-finally块,而被后者保护的代码出现了刚好能被前者处理的异常,那么在执行前者的异常处理代码时,其实执行流程已经越出了try-finally块,那这种情况下是不是也应该先执行finally部分的终止处理代码呢?答案是肯定的。C/C++的结构化异常处理机制将这种行为称为全局展开。
当然,全局展开是可以被中途打断的,在任意一个finally中如果包含return语句,则该语句执行后返回上级函数中,而原本将在finally后执行的异常处理程序将不会被执行。其原理是VC中异常处理程序中过滤函数的调用者(该函数名为except_handler4或早起的except_handler3)在执行finally中代码的时候,提前将栈帧切换到了对应的函数中(主要是保证ebp的值是正确的),因此在遇到ret指令后,就能顺利地拿到返回地址返回上级调用者的后一条语句继续执行,且栈完全是正确的(其原理将在后续文章中进行详细分析)
我们用一个例子来加以说明。在本例中,主函数WinMain将调用包含了引发异常的RaiseExcept函数。
在主函数中有两级异常处理程序,外层的try-except能够处理RaiseExcept函数中引发的异常,内层为try-finally终止处理程序。
在RaiseExcept函数中,引发异常的代码同样被两级try块保护。内层是一个try-finally终止处理程序,外层是一个try-except异常处理块,但该块无法对引发的引发的异常进行处理。
根据之前的分析,当异常发生后,首先执行的是RaiseExcept函数中的外层try的过滤函数,由于该函数不能处理EXCEPTION_ACCESS_VIOLATION类型(即访问违规类)异常,因此系统SEH机制继续向外找到主函数中的外层try的过滤函数,正好该函数能处理异常。于是,在该try的异常处理代码执行前,由于执行流程越出了内外两级终止处理程序,因此内外层的finally块代码先后被执行,然后才是最外层的try的异常处理代码执行。
而最终的执行结果也应证了我们的分析。程序执行流程:
RaiseExcept函数外层try的过滤函数——>主函数外层try的过滤函数——>RaiseExcept函数内层try的finally块——>主函数内层try的finally块——>主函数外层try的except块。
被终止处理程序保护的代码块,将会在执行流程试图越出代码块之前执行局部展开,转入终止处理程序进行执行。如果终止处理程序中没有越出保护块的代码,则执行完终止处理程序后,执行流程返回保护块中,执行引发局部展开的那条代码。
被异常处理程序保护的代码块,如果其中某条指令执行时引发异常,则执行流为从内而外地调用各级except块中的过滤函数或过滤表达式,直到找到一个能处理本异常的try块,然后执行其异常处理代码。
但仔细想想就会发现问题:如果try-except块中嵌套了一个try-finally块,而被后者保护的代码出现了刚好能被前者处理的异常,那么在执行前者的异常处理代码时,其实执行流程已经越出了try-finally块,那这种情况下是不是也应该先执行finally部分的终止处理代码呢?答案是肯定的。C/C++的结构化异常处理机制将这种行为称为全局展开。
当然,全局展开是可以被中途打断的,在任意一个finally中如果包含return语句,则该语句执行后返回上级函数中,而原本将在finally后执行的异常处理程序将不会被执行。其原理是VC中异常处理程序中过滤函数的调用者(该函数名为except_handler4或早起的except_handler3)在执行finally中代码的时候,提前将栈帧切换到了对应的函数中(主要是保证ebp的值是正确的),因此在遇到ret指令后,就能顺利地拿到返回地址返回上级调用者的后一条语句继续执行,且栈完全是正确的(其原理将在后续文章中进行详细分析)
我们用一个例子来加以说明。在本例中,主函数WinMain将调用包含了引发异常的RaiseExcept函数。
在主函数中有两级异常处理程序,外层的try-except能够处理RaiseExcept函数中引发的异常,内层为try-finally终止处理程序。
在RaiseExcept函数中,引发异常的代码同样被两级try块保护。内层是一个try-finally终止处理程序,外层是一个try-except异常处理块,但该块无法对引发的引发的异常进行处理。
#include <windows.h> #include <tchar.h> int ExceptFilterInner(DWORD dwExceptionCode) { MessageBox(NULL, TEXT("Inner try`s filter running"), TEXT("Inner Try"), MB_OK); if (dwExceptionCode == EXCEPTION_ACCESS_VIOLATION) { return EXCEPTION_CONTINUE_SEARCH; } else { return EXCEPTION_CONTINUE_EXECUTION; } } int ExceptFilterOuter() { MessageBox(NULL, TEXT("Outer try`s filter running"), TEXT("Outer Try"), MB_OK); //return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_EXECUTE_HANDLER; } void RaiseExcept() { __try { _try { *(PDWORD)NULL = 0; } __finally { MessageBox(NULL, TEXT("Inner::Finally block execute"), TEXT("In Inner::finally"), MB_OK); } } __except (ExceptFilterInner(GetExceptionCode())) { MessageBox(NULL, TEXT("Never execute"), TEXT("haha"), MB_OK); } } int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, INT) { __try { __try { RaiseExcept(); } __finally { MessageBox(NULL, TEXT("Outer::Finally block execute"), TEXT("In Outer::finally"), MB_OK); } } __except (ExceptFilterOuter()) { MessageBox(NULL, TEXT("Outer try catched"), TEXT("haha"), MB_OK); } return 0; }
根据之前的分析,当异常发生后,首先执行的是RaiseExcept函数中的外层try的过滤函数,由于该函数不能处理EXCEPTION_ACCESS_VIOLATION类型(即访问违规类)异常,因此系统SEH机制继续向外找到主函数中的外层try的过滤函数,正好该函数能处理异常。于是,在该try的异常处理代码执行前,由于执行流程越出了内外两级终止处理程序,因此内外层的finally块代码先后被执行,然后才是最外层的try的异常处理代码执行。
而最终的执行结果也应证了我们的分析。程序执行流程:
RaiseExcept函数外层try的过滤函数——>主函数外层try的过滤函数——>RaiseExcept函数内层try的finally块——>主函数内层try的finally块——>主函数外层try的except块。
相关文章推荐
- Visual C++异常处理机制原理与应用(三)——C/C++结构化异常处理之try-except异常处理的使用(上)
- 《Visual C++异常处理机制原理与应用(二)—— C/C++结构化异常处理之try-finally终止处理的使用与原理(下)》
- Visual C++异常处理机制原理与应用(一)—— C/C++结构化异常处理之try-finally终止处理的使用与原理(上)
- SSH原理及应用---Struts2的工作机制及使用
- SSH原理及应用---Spring的工作机制及使用
- 使用MQ的触发器机制的大致原理及过程如下
- Java中的异常处理机制的简单原理和应用
- C#中使用try{}catch{}finally{}对系统性能的影响和处理机制(摘录)
- Qt的signal/slot机制的原理和使用
- Java中的异常处理机制的简单原理和应用
- java基础问题----Java中的异常处理机制的简单原理和应用
- SSH原理及应用---hibernate的原理及使用
- 使用nginx后如何在web应用中获取用户ip及原理解释
- C#中使用反射机制得到类型实例应用Demo
- sniffer技术原理及应用,包括编程方法和工具使用
- JMS(Jboss Messaging)的一点使用心得(十三)拔网线后的重连----JMS Connection原理浅析及应用
- Java中的异常处理机制的简单原理和应用。