try, except, finally, leave异常模型机制 ╰+攻爆jí腚メ 2022-03-16 03:46 146阅读 0赞 分享一下我老师大神的人工智能教程。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来![https://blog.csdn.net/jiangjunshow][https_blog.csdn.net_jiangjunshow] **导读:** 从本篇文章开始,将全面阐述\_\_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 void main() \{ // 定义受监控的代码模块 \_\_try \{ puts( " in try " ); \} // 定义异常处理模块 \_\_except( 1 ) \{ puts( " in except " ); \} \} 呵呵!是不是很简单,而且与C++异常处理模型很相似。当然,为了与C++异常处理模型相区别,VC编译器对关键字做了少许变动。首先是在每个关键字加[上两][Link 1]个下划线作为前缀,这样既保持了语义上的一致性,另外也尽最大可能来避免了关键字的有可能造成名字冲突而引起的麻烦等;其次,C++异常处理模型是使用catch关键字来定义异常处理模块,而SEH是采用\_\_except关键字来定义。并且,catch关键字后面往往好像接受一个函数参数一样,可以是各种类型的异常数据对象;但是\_\_except关键字则不同,它后面跟的却是一个表达式(可以是各种类型的表达式,后面会进一步分析)。 **try-except进阶** 与C++异常处理模型很相似,在一个函数中,可以有多个try-except语句。它们可以是一个平面的线性结构,也可以是分层的嵌套结构。例程代码如下: // 例程1 // 平面的线性结构 void main() \{ \_\_try \{ puts( " in try " ); \} \_\_except( 1 ) \{ puts( " in except " ); \} // 又一个try-except语句 \_\_try \{ puts( " in try1 " ); \} \_\_except( 1 ) \{ puts( " in except1 " ); \} \} // 例程2 // 分层的嵌套结构 void main() \{ \_\_try \{ puts( " in try " ); // 又一个try-except语句 \_\_try \{ puts( " in try1 " ); \} \_\_except( 1 ) \{ puts( " in except1 " ); \} \} \_\_except( 1 ) \{ puts( " in except " ); \} \} // 例程3 // 分层的嵌套在\_\_except模块中 void main() \{ \_\_try \{ puts( " in try " ); \} \_\_except( 1 ) \{ // 又一个try-except语句 \_\_try \{ puts( " in try1 " ); \} \_\_except( 1 ) \{ puts( " in except1 " ); \} puts( " in except " ); \} \} 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模块中获得异常错误的相关信息,这非常关键,它实际上是进行异常错误处理的前提,也是对异常进行分层分级别处理的前提。[可想而知][Link 2],如果没有这些起码的信息,异常处理如何进行?因此获取异常信息非常的关键。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后面的括号中的表达式作用域内有效,否则结果可能没有保证(至于为什么,在后面深入分析异常模型的实现时候,再做详细阐述)。看一个例程吧!代码如下: 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块中 " ); \} \} 上面的程序运行结果如下: 存储保护异常 内层的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的上层封装的更高级一类的函数,这以后再详细分析它的代码实现。这里还是看一个简单例子吧!代码如下: 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块中 " ); \} \} 上面的程序运行结果如下: 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语句。 分享一下我老师大神的人工智能教程。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来![https://blog.csdn.net/jiangjunshow][https_blog.csdn.net_jiangjunshow] [https_blog.csdn.net_jiangjunshow]: https://blog.csdn.net/jiangjunshow/article/details/77338485 [Windows]: https://www.baidu.com/s?wd=Windows&tn=24004469_oem_dg&rsv_dl=gh_pl_sl_csd [Link 1]: https://www.baidu.com/s?wd=%E4%B8%8A%E4%B8%A4&tn=24004469_oem_dg&rsv_dl=gh_pl_sl_csd [Link 2]: https://www.baidu.com/s?wd=%E5%8F%AF%E6%83%B3%E8%80%8C%E7%9F%A5&tn=24004469_oem_dg&rsv_dl=gh_pl_sl_csd
相关 Java异常处理机制:try, catch, finally Java的异常处理机制是通过`try`, `catch`,和`finally`语句来实现的。 1. `try`: 这是开始执行可能抛出异常代码的部分。如果在`try`块中的代 以你之姓@/ 2024年10月20日 23:09/ 0 赞/ 16 阅读
相关 Java异常处理机制:try-catch-finally Java的异常处理机制是通过`try-catch-finally`结构来实现的,它主要用于捕获和处理程序运行过程中可能出现的异常。 1. `try`块:这是代码可能抛出异常的 迷南。/ 2024年09月22日 19:27/ 0 赞/ 40 阅读
相关 Java异常处理机制:try-catch-finally Java的异常处理机制是通过try-catch-finally结构来实现的,主要目的是捕获和处理程序运行中可能发生的错误。 1. try块:这是异常处理的主要部分,用于执行可 妖狐艹你老母/ 2024年09月22日 08:00/ 0 赞/ 42 阅读
相关 Java异常处理机制——try-catch-finally Java的异常处理机制是通过`try-catch-finally`结构来实现的,它是Java编程中用来处理可能发生的错误的关键部分。 1. `try`块:这是执行可能会抛出异 港控/mmm°/ 2024年09月18日 19:21/ 0 赞/ 38 阅读
相关 Java异常处理机制:try, catch, finally Java的异常处理机制是通过try-catch-finally结构来实现的,具体步骤如下: 1. try块:这是捕获和处理异常的地方。如果在try块中的代码抛出了一个异常,那 待我称王封你为后i/ 2024年09月10日 05:18/ 0 赞/ 35 阅读
相关 python异常处理--try except else raise finally 写程序时遇到异常情况,程序可能无法正常运行。此时就需要引入异常处理 1.try ...except try 后面写正常运行的程序代码,except即为异常情况 ![复制代 超、凢脫俗/ 2023年10月08日 08:47/ 0 赞/ 35 阅读
相关 try_except_finally blog: [python中的try/except/else/finally][python_try_except_else_finally] python文档:[错误和异常 布满荆棘的人生/ 2023年08月17日 16:37/ 0 赞/ 84 阅读
相关 Python中异常捕捉try..except...finally的使用 Python开发中 ,常见错误类型有两种 :语法错误和异常。语法错误是程序没有运行时,由开发IDE环境检测到错误,比如缩进错误,变量没有写完整,缺少:等解析错误。而异常 骑猪看日落/ 2022年05月16日 07:40/ 0 赞/ 197 阅读
相关 try, except, finally, leave异常模型机制 分享一下我老师大神的人工智能教程。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来![https://blog.csdn.net/jiangjuns ╰+攻爆jí腚メ/ 2022年03月16日 03:46/ 0 赞/ 147 阅读
还没有评论,来说两句吧...