ACCESS【 转】__try,__except,__finally,__leave异常模型机制

转自:http://blog.csdn.net/wwl33695/article/details/8686458

导读: 
从本篇文章开始,将完美阐述__try,__except,__finally,__leave异常模型机制,它吧就算是Windows系列操作系统平台达成提供的SEH模型。主人公阿愚用于这边跟大家享受SEH(
结构化异常处理)的就学过程以及经验总结。 深入了解要参见<<windows
核心编程>>第23, 24章.

SEH实际包含两独根本职能:结束处理(termination
handling)和雅处理(exception handling) 

当你建一个try块,它必须随一个finally块或一个except片。

一个try 块之后不能够既来finally块又有except块。但足以于try –
except块被嵌套try – finally块,反过来
也可以。

__try  __finally关键字用来号出得了处理程序两截代码的概貌

不管保护体(try块)
凡什么退出的。不论你以保护体中使return,还是goto,或者是longjump,结束处理程序
(finally块)都拿为调用。

在try使用__leave关键字会引起过反至try块的最后

 SEH有个别起十分有力的意义。当然,首先是非常处理模型了,因此,这篇稿子首先深入阐发SEH提供的老大处理模型。另外,SEH还有一个专程强的功用,这将在生一致首稿子中进行详尽介绍。

try-except入门
  SEH的很处理模型主要由try-except语句来完成,它与标准C++所定义的老处理模型非常相近,也都是好定义有被监督之代码模块,以及定义格外处理模块等。还是一直方法,看一个例证先,代码如下: 
//seh-test.c

ACCESS 1

void main()
{
    // 定义受监控的代码模块
    __try
    {
        puts("in try");
    }
    //定义异常处理模块
    __except(1)
    {
        puts("in except");
    }
}

ACCESS 2

 呵呵!是匪是怪粗略,而且与C++异常处理模型很相似。当然,为了和C++异常处理模型相区别,VC编译器对主要字做了片反。首先是当每个重点字加上两个下划线作为前缀,这样既保持了语义上之一致性,另外呢尽最可怜或来避免了严重性字之发出或导致名字冲突要滋生的累等;其次,C++异常处理模型是使catch关键字来定义格外处理模块,而SEH是下__except关键字来定义。并且,catch关键字后往往好像接受一个函数参数一样,可以是各种类型的好数据对象;但是__except关键字则不同,它后和的却是一个表达式(可以是各种类型的表达式,后面会尤其分析)。

try-except进阶
  以及C++异常处理模型很一般,在一个函数中,可以生多独try-except语句。它们得以是一个面的线性结构,也足以是子的嵌套结构。例程代码如下:

// 例程1
// 平面的线性结构

ACCESS 3

void main()
{
    __try
    {
        puts("in try");
    }
    __except(1)
    {
        puts("in except");
    }


    // 又一个try-except语句
    __try
    {
        puts("in try1");
    }
    __except(1)
    {
        puts("in except1");
    }
}

ACCESS 4

// 例程2
// 分层的嵌套结构

ACCESS 5

void main()
{
    __try
    {
        puts("in try");
        // 又一个try-except语句
        __try
        {
            puts("in try1");
        }
        __except(1)
        {
            puts("in except1");
        }
    }
    __except(1)
    {
        puts("in except");
    }
}

ACCESS 6

// 例程3
// 分层的嵌套在__except模块中

ACCESS 7

void main()
{
    __try
    {
        puts("in try");
    }
    __except(1)
    {
        // 又一个try-except语句
        __try
        {
            puts("in try1");
        }
        __except(1)
        {
            puts("in except1");
        }

        puts("in except");
    }
}

ACCESS 8

 1. 叫监控之代码模块于实践(也不怕__try定义之模块代码);
  2.
一旦点的代码执行过程遭到,没有出现异常的语句,那么控制流将转入到__except子句后的代码模块中;
  3.
再不,如果出现异常的言语,那么控制流将进入到__except后面的表达式中,也便首先计算是表达式的价,之后再次因此价值,来控制做出相应的处理。这个价有三种状态,如下:
  EXCEPTION_CONTINUE_EXECUTION (–1)
异常被忽视,控制流将在充分出现的触发之后,继续回升运转。
  EXCEPTION_CONTINUE_SEARCH (0)
异常不给辨认,也就是眼前之是__except模块不是者老错误所对应之正确性的杀处理模块。系统以继承到直达同一重合的try-except域中持续查找一个恰当的__except模块。
  EXCEPTION_EXECUTE_HANDLER (1)
异常都深受识别,也不怕眼前的这那个错误,系统曾找到了并能够确认,这个__except模块就是无可非议的万分处理模块。控制流将进入到__except模块中。
 
try-except深入
  上面的情中早已针对性try-except进行了到的垂询,但是有好几还未曾阐述到。那就是什么在__except模块中得好错误的连锁消息,这生关键,它事实上是拓展大错误处理的前提,也是针对好进行分层分级别处理的前提。可想而知,如果无这些起码的信息,异常处理如何进行?因此获很信息挺的机要。Windows提供了片只API函数,如下:
 

LPEXCEPTION_POINTERS GetExceptionInformation(VOID);
DWORD GetExceptionCode(VOID);

  其中GetExceptionCode()返回错误代码,而GetExceptionInformation()返回重新周全的信息,看她函数的宣示,返回了一个LPEXCEPTION_POINTERS类型的指针变量。那么EXCEPTION_POINTERS结构如何呢?如下,
 

typedef struct _EXCEPTION_POINTERS { // exp 
PEXCEPTION_RECORD ExceptionRecord; 
PCONTEXT ContextRecord; 
} EXCEPTION_POINTERS;

 

  呵呵!仔细瞅瞅,这是匪是和达到亦然首文章被,用户程序所注册之十分处理的回调函数的片独参数类型一样。是的,的确没有错!其中EXCEPTION_RECORD类型,它记录了有些同坏系的音;而CONTEXT数据结构体中记录了要命来常,线程当时之上下文环境,主要不外乎寄存器的值。因此有矣这些消息,__except模块便可本着老错误进行非常好的归类以及死灰复燃处理。不过特别需要注意的凡,这简单只函数只能是以__except后面的括号中的表达式作用域内立竿见影,否则结果也许没管(至于缘何,在背后深入剖析深模型的兑现上,再举行详细阐述)。看一个例程吧!代码如下:

ACCESS 9

int exception_access_violation_filter(LPEXCEPTION_POINTERS p_exinfo)
{
    if(p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
    {
        printf("存储保护异常\n");
        return 1;
    }
    else 
        return 0;
}

int exception_int_divide_by_zero_filter(LPEXCEPTION_POINTERS p_exinfo)
{
    if(p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
    {
        printf("被0除异常\n");
        return 1;
    }
    else 
        return 0;
}

void main()
{

    __try
    {
        __try
        {
            int* p;

            // 下面将导致一个异常
            p = 0;
            *p = 45;
        }
        // 注意,__except模块捕获一个存储保护异常
        __except(exception_access_violation_filter(GetExceptionInformation()))
        {
            puts("内层的except块中");
        }
  //可以在此写除0异常的语句
     int b = 0;
      int a = 1 / b;
    }
    // 注意,__except模块捕获一个被0除异常
    __except(exception_int_divide_by_zero_filter(GetExceptionInformation())) 
    {
        puts("外层的except块中");
    }
}

ACCESS 10

点的程序运行结果如下:

存储保护异常
内层的except块中
Press any key to continue

 

  呵呵!感觉是,大家可以在上面的次序基础之上改动一下,让其抛来一个被0除非常,看程序的运行结果是勿是若预期那样。
  最后还有一些亟需阐述,在C++的老处理模型中,有一个throw关键字,也就算以被监督的代码中丢掉来一个不胜,那么以SEH异常处理模型中,是未是吧理应来如此一个类似之根本字还是函数呢?是的,没错!SEH异常处理模型中,对特别划分为有限要命类,第一栽不畏是地方有例程中所盼的,这好像非常是系格外,也于号称硬件大;还有一样类似,就是程序中协调遗弃来老,被称之为软件大。怎么丢来吧?还是Windows提供了的API函数,它的扬言如下:
 

VOID RaiseException(
DWORD dwExceptionCode, // exception code
DWORD dwExceptionFlags, // continuable exception flag
DWORD nNumberOfArguments, // number of arguments in array
CONST DWORD *lpArguments // address of array of arguments
);

 

  很简短吧!实际上,在C++的充分处理模型中的throw关键字,最终也是对准RaiseException()函数的调用,也尽管是说,throw是RaiseException的上层封装的重复高级一近似的函数,这之后重新详细分析它的代码实现。这里还是看一个大概例子吧!代码如下:

ACCESS 11

int seh_filer(int code)
{
    switch(code)
    {
    case EXCEPTION_ACCESS_VIOLATION :
        printf("存储保护异常,错误代码:%x\n", code);
        break;
    case EXCEPTION_DATATYPE_MISALIGNMENT :
        printf("数据类型未对齐异常,错误代码:%x\n", code);
        break;
    case EXCEPTION_BREAKPOINT :
        printf("中断异常,错误代码:%x\n", code);
        break;
    case EXCEPTION_SINGLE_STEP :
        printf("单步中断异常,错误代码:%x\n", code);
        break;
    case EXCEPTION_ARRAY_BOUNDS_EXCEEDED :
        printf("数组越界异常,错误代码:%x\n", code);
        break;
    case EXCEPTION_FLT_DENORMAL_OPERAND :
    case EXCEPTION_FLT_DIVIDE_BY_ZERO :
    case EXCEPTION_FLT_INEXACT_RESULT :
    case EXCEPTION_FLT_INVALID_OPERATION :
    case EXCEPTION_FLT_OVERFLOW :
    case EXCEPTION_FLT_STACK_CHECK :
    case EXCEPTION_FLT_UNDERFLOW :
        printf("浮点数计算异常,错误代码:%x\n", code);
        break;
    case EXCEPTION_INT_DIVIDE_BY_ZERO :
        printf("被0除异常,错误代码:%x\n", code);
        break;
    case EXCEPTION_INT_OVERFLOW :
        printf("数据溢出异常,错误代码:%x\n", code);
        break;
    case EXCEPTION_IN_PAGE_ERROR :
        printf("页错误异常,错误代码:%x\n", code);
        break;
    case EXCEPTION_ILLEGAL_INSTRUCTION :
        printf("非法指令异常,错误代码:%x\n", code);
        break;
    case EXCEPTION_STACK_OVERFLOW :
        printf("堆栈溢出异常,错误代码:%x\n", code);
        break;
    case EXCEPTION_INVALID_HANDLE :
        printf("无效句病异常,错误代码:%x\n", code);
        break;
    default :
        if(code & (1<<29))
            printf("用户自定义的软件异常,错误代码:%x\n", code);
        else
            printf("其它异常,错误代码:%x\n", code);
        break;
    }

    return 1;
}


void main()
{
    __try
    {
        puts("try块中");

        // 注意,主动抛出一个软异常
        RaiseException(0xE0000001, 0, 0, 0);
    }
    __except(seh_filer(GetExceptionCode()))
    {
        puts("except块中");
    }

}

ACCESS 12

方的程序运行结果如下:
hello
try块中
用户从定义的软件大,错误代码:e0000001
except块中
world
Press any key to continue

 

地方的先后非常简单,这里不开进一步的剖析。我们用重点讨论的是,在__except模块中哪些鉴别不同之慌,以便对怪进行充分好之归类处理。毫无疑问,它自然是经过GetExceptionCode()或GetExceptionInformation
()函数来取当前底非常错误代码,实际也不怕凡是DwExceptionCode字段。异常错误代码在winError.h文件中定义,它本Windows系统下统一的错误代码的条条框框。每个DWORD被分几个字段,如下表所示:
例如我们好于winbase.h文件中找到EXCEPTION_ACCESS_VIOLATION的价为0
xC0000005,将这可怜代码值拆起来,来分析看她的一一bit位字段的涵义。
C 0 0 0 0 0 0 5 (十六进制)
1100 0000 0000 0000 0000 0000 0000 0101 (二进制)
第3 0位以及第3
1位都是1,表示该大是一个严重的不当,线程可能不能够继续朝着生运作,必须使及时处理恢复这个那个。第2
9各是0,表示系统受已定义了酷代码。第2 8员是0,留待后用。第1 6 位至2
7位是0,表示是FACILITY_NULL设备项目,它表示存取异常而产生在系被任何地方,不是运用一定设备才生的坏。第0位到第1
5位的值也5,表示充分错误的代码。
  如果程序员在程序代码中,计划抛来片起定义类型的万分,必须使规划设计好温馨之不胜类型的细分,按照上面的规则来填充异常代码的次第字段值,如上面示例程序中丢掉来一个特别代码为0xE0000001软件大。

总结
  (1)
C++异常模型用try-catch语法定义,而SEH异常模型则就此try-except语法;
  (2) 与C++异常模型相似,try-except也支撑多交汇的try-except嵌套。
  (3)
与C++异常模型不同之是,try-except模型中,一个try块只能是发一个except片;而C++异常模型中,一个try块可以产生多单catch块。
  (4)
与C++异常模型相似,try-except模型中,查找搜索怪模块的平整为是逐级向上拓展的。但是小有分之凡,C++异常模型是依照好对象的类别来拓展匹配查找的;而try-except模型则不同,它经过一个表达式的价值来进展判断。如果表达式的价为1(EXCEPTION_EXECUTE_HANDLER),表示找到了老大处理模块;如果值为0(EXCEPTION_CONTINUE_SEARCH),表示继续为达同样重合的try-except域中继承找其它可能相当的要命处理模块;如果值吗-1(EXCEPTION_CONTINUE_EXECUTION),表示忽略这个可怜,注意这个价一般很少用,因为它很易造成程序难以预测的结果,例如,死循环,甚至导致程序的倒台等。
   (5)
__except关键字后与的表达式,它可是各种类型的表达式,例如,它可以是一个函数调用,或是一个原则表达式,或是一个逗号表达式,或简直就是是一个整型常量等等。最常用之凡一个函数表达式,并且通过以GetExceptionCode()或GetExceptionInformation
()函数来取当前的不可开交错误信息,便于程序员有效控制甚错误的归类处理。
   (6)
SEH异常处理模型中,异常被剪切也简单非常接近:系统特别和软件大。其中软件大通过RaiseException()函数抛来。RaiseException()函数的企图类似于C++异常模型中之throw语句。

C++不常用要字(__leave)

**总结__finally块被实践的流水线时,无外乎三种情形。第一种就是逐一执行到__finally块区域外的代码,这种气象万分简短,容易掌握;第二栽不畏是goto语句或return语句引发的程序控制流离开当前__try块作用域时,系统自动就对__finally块代码的调用;第三种就是出于当__try块中出现特别时,导致程序控制流离开当前__try块作用域,这种情况下也是出于网活动就对__finally块的调用。无论是第
2栽,还是第3栽情况,毫无疑问,它们都见面引起大怪的系统开发,编译器在编译此类程序代码时,它会也当下点儿种植情景准备多之附加代码。一般第2栽情形,被称“局部进展(LocalUnwinding)”;第3种植状况,被称作“全局展开(GlobalUnwinding)”。在背后阐述SEH实现之时段会详细分析到立刻一点。
第3种情景,也便由出现异常而导致的“全局展开”,对于程序员而言,这或许是无能为力避免的,因为您在应用大处理体制提高程序可靠健壮性的还要,不可避免的会晤挑起性能上另的一部分付出。呵呵!这世界实质上呢算瞒公平的,有得自然有去。

  但是,对于第2种情景,程序员完全可中地避免她,避免“局部进展”引起的无必要的额外开销。实际及时也是跟结构化程序设计思想相平等的,也便一个顺序模块应该就生一个入口以及一个道,程序模块内尽量避免使用goto语句等。但是,话虽如此,有时为了增强程序的可读性,程序员在编写代码时,有时可能只能以局部及结构化程序设计思想相悖的做法,例如,在一个函数中,可能来差不多地处之return语句。针对这种状况,SEH提供了千篇一律栽死有效之折衷方案,那就算是__leave关键字所于底打算,它既具像goto语句子和return语句子那样类似之用意(由于检测到某某程序运行中的失实,需要就去时之
__try块作用域),但是又避免了“局部进展”
的额外开销。还是看个例证吧!代码如下:** 

ACCESS 13

#include <stdio.h>

void test()
{
puts("hello");
__try
{
int* p;
puts("__try块中");

// 直接跳出当前的__try作用域
__leave;
p = 0;
*p = 25;
}
__finally
{
// 这里会被执行吗?当然
puts("__finally块中");
}

puts("world");
}

void main()
{
__try
{
test();
}
__except(1)
{
puts("__except块中");
}
}

ACCESS 14

面的程序运行结果如下:
hello
__try块中
__finally块中
world
Press any key to continue

相关文章