try catch异常处理与SEH

来源:互联网 发布:网上礼佛软件 编辑:程序博客网 时间:2024/05/21 08:56

转自:http://www.soft-bin.com/html/2010/08/06/try_catch_and_seh.html


  • 作者: luckzj
  • 发表时间: 2010年8月6日
  • 本文链接: http://www.soft-bin.com/html/2010/08/06/try_catch_and_seh.html
  • copy right (c) http://soft-bin.com all right reserved.
  • 转载请注明出处

异常处理是软件设计中非常重要的一个部分,不同的语言实现了不同的异常处理机制。C++语言也实现了异常处理机制,即我们常见的try-catch语句,而除此之外,我们在Windows平台下编程时,经常会遇到 __try, __except, __finally这几个与异常相关的关键字,这些是Windows实现的结构化异常处理机制,即SEH,在这篇文章里,我将分别介绍这两种异常处理机制。

try-catch异常处理

try-catch是C++语言规定的异常处理关键字。try块包含的语句块中如果抛出了异常,则程序会运行catch语句块中的代码,我们称这种行为叫做异常的捕获。那么对于抛出异常,我们使用的是thow关键字,这个关键字将一个对象当作异常抛出,而在catch语句块中,我们可以接收到这个被“抛出”的异常对象,如下例:

 try{    int a = 0;    throw a;}catch(int b){    cout << "Exception captured:" << b << endl;}

这里我的示例中抛出的是一个整型的数字,我们也可以抛出一个类的对象,但需要注意的是,catch的声明就像是函数一样,其行为也像是函数,我们在catch中捕捉到的是异常对象的一份拷贝,而并非异常对象本身。

在有些情况下,我们捕获异常并不是为了对这个异常进行处理,而是修改异常的信息,然后将之抛出。C++专门提供了这样的操作方式,即我们可以直接使用throw关键字,后面无需跟随具体错误对象,这样可以将捕捉到的异常抛出。

class CExcept{pubic:    int val;};void Func(){    try    {        throw CExcept();  // 表示使用构造函数构造一个类的对象    }    catch(CExcept exp)    {        exp.val = -1;        throw;    }}

上面这段代码的本意是将抛出的异常稍作修改后,继续向上抛出,但这是不能达到目的的,因为exp只是被抛出异常的一份拷贝。实际上在catch的参数中,我们也可以使用&来获取异常对象的引用,这样就可以完成对异常对象的修改:

void Func(){    try    {        throw CExcept();    }    catch(CExcept& exp)    {        exp.val = -1;        throw;    }}

catch语句块可以捕获固定类型的异常,且可以在一个try语句块中连接多个catch,如下:

try{    // operation}catch(int a){}catch(CExcept b){}

系统将会根据捕获到的异常对象的具体类型,调用不同的catch语句块。

异常捕获的语句的catch除了捕获固定类型的异常外,还可以不指定异常类型,从而捕获所有类型的异常,我们只需要在catch的参数中,使用 … 即可。这种用法通常用于确保资源使用完毕后被释放,例如:

try{    mutex.Lock();    // operation}catch(...){    mutex.Unlock();}mutex.Unlock();

在上述代码中,我们取得的mutex无论如何都会被释放掉,这就避免了因为在获得mutex以后的操作出现异常,而使得mutex无法解锁。

需要注意的是,catch(…) 必须出现在所有的捕捉固定类型的catch语句块的最后,否则编译器会报错。

try语句块在抛出异常以后,会从本函数内开始查找相匹配的catch块,如果在本语句块后没有找到对应的catch,则会退出当前函数,向上一级函数继续查找,这个过程被称作栈展开。这个查找过程将会一直到main函数,如果最终没有找到catch语句,则系统会调用C++标准库中定义的terminate()函数,终止进程的执行。

在catch语句块执行完毕以后,程序会返回到catch语句块后的一行代码,而不可以返回到异常抛出的地点。

结构化异常处理SEH

SEH是微软在Windows系统中引入的异常处理机制。SEH与C++异常处理机制类似,但却不等同于C++异常处理机制,实际上在visual C++中, C++异常处理机制内部也使用到Windows的SEH。

SEH使用到的关键字与C++异常处理机制类似,但又不大相同。其关键字分别为: __try, __except, __finally, __leave。

典型的SEH应用形式为:

__try{    // operations}__finally{    // operations}

上述用法中,无论 __try 语句块中作何操作,在__try语句块退出后,必然会执行__finally语句块中的内容。如果在__try语句块中存在 return 语句,那么这个返回值将被编译器暂存起来,直到finally执行完毕后,才以这个返回值返回,例如:

__try{    return 1;}__finally{    return 2;}

这段代码在__try语句最后,将1缓存起来,而在执行__finally后,缓存起来的返回值又被2所覆盖,因此这段代码最终返回结果为2。

__finally的这个特性使得我们可以在__finally中完成对资源的释放操作,确保不会因为程序的异常导致资源无法正常释放。

然而,虽然 __finally 可以在绝大多数情况下保证被执行到,但当我们调用 ExitThread或ExitProcess时,线程或者进程将立即结束,因此__finally块中的内容是得不到执行的。同样的,在调用TerminateThread或者TerminateProcess后,__finally也是无法被执行到的。这就是为什么在我们编写程序时,要尽量避免调用这些函数原因之一。

如果在 __try块中使用 return 等语句,则编译器会自动在返回之前,执行 __finally 块中的语句,这个过程被成为局部展开,在__try块中存在返回语句时,编译器会在返回之前进行局部展开,这样会使得我们的程序变得复杂,最好的情况是我们不要在__try中使用这种语句,我们可以使用 __leave。

__leave的作用是直接跳转到__try语句块的结尾,因此我们可以自然地结束__try,进入__finally,这个过程不需要进行局部展开,没有任何系统开销。我们可以编写这样的程序,所有的资源都在 __finally 块中进行判断,如果资源为有效,则进行释放,而在__try块中,一旦发现无效资源,则使用 __leave 退出__try语句块,这样的代码会显得结构清晰,可读性强。

在SEH中,__try语句块除了可以和 __finally 搭配使用外,还可以和 __except 搭配使用,但 __finally和__except不可以同时存在。

__except 类似于 catch,但又决然不同,catch用于捕获异常对象,而__except却不是,它也有输入参数,但却仅限于以下三个数值:

标识符数值含义EXCEPTION_EXECUTE_HANDLER1__except语句需要被执行,执行以后,跳转到__except块的后一条指令开始继续执行EXCEPTION_CONTINUE_SEARCH0不执行__except语句的内容,向上寻找可以执行的__except语句块EXCEPTION_CONTINUE_EXECUTION-1在__except语句执行完毕以后,跳转到异常发生的地方继续执行

第一种用与我们前面讲到的 try-catch 类似,第二种用法相当于没有找到except语句块,第三种用法用在如下情形,即我们可以对出现的错误进行修补时,修补完错误后,让程序继续正常运行:

char* pBuff = NULL;__try{    strcpy(pBuf, "hello");}__except(EXCEPTION_CONTINUE_EXECUTION){    pBuf = new char[1024];}

我们在这里新开辟了一块空间,在实际应用中,我们可能是事先专门开辟好了一块专门的缓存空间,用于解决此类问题。

在上面的例子中,我直接给 __except EXCEPTION_CONTINUE_EXECUTION 参数,实际应用则不是,我们可以使用一个函数调用,来对当前出现的异常进行判断,从而返回__except的参数,使得 __except 语句块可以根据不同的异常类型执行不同的操作。

获取异常信息的函数为:

// 返回错误代码,常见的错误码可以再 WinBase.h中找到
DWORD GetExceptionCode();
 
typedef struct _EXCEPTION_POINTERS{
    PEXCEPTION_RECORD ExceptionRecord;
    PCONTEXT ContextRecord;
}EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
 
PEXCEPTION_POINTERS GetExceptionInformation();

关于这两个函数的具体使用方法,可以查阅相关的文档,我们可以在 __except 语句块的传入参数中加入一个异常过滤器,通过过滤器函数判断该__except应该如何执行。

C++的try-catch中,抛出异常使用 throw 关键字,而在SEH中,我们使用 RaiseException函数来引发异常:

VOID RaiseException(
    DWORDdwExceptionCode,
    DWORDdwExceptionFlags,
    DWORDnNumberOfArguments,
    CONST ULONG_PTR* pArguments);

RaiseException 的第一个参数为错误码,如果我们希望自己定义一个错误代码,则需要遵循标准的Windows错误代码格式,这个格式请查阅相关文档。

第二个参数 dwExceptionFlags,必须是0或者 EXCEPTION_NONCONTINUABLE,表示使用EXCEPTION_CONTINUE_EXCEPTION来影响这个异常是否合法。

后两个参数表示传入给异常的参数数量及具体参数列表。我们可以通过 GetExceptionInformation来获取这些异常信息。

总结,try-catch和SEH的区别

1. try-catch是C++标准的异常处理机制,在windows下,仍然是通过SEH机制实现。
2. SEH是微软定义的结构化异常处理机制,并非C++语言特性。
3. try-catch异常机制可以捕获异常对象,抛出异常,其关键字包括 try, catch, throw
4. SEH异常机制可以在捕获到异常后,获取异常代码及异常相关信息,从而判断应当如何处理这个异常。其中并不包括异常对象的概念。 SEH的关键字包括 __try, __except, __finally, __leave



原创粉丝点击