您的位置:首页 > 编程语言 > C语言/C++

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异常处理块,但该块无法对引发的引发的异常进行处理。

#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块。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: