导读: 
从本篇文章开始,将全面阐述__try,__except,__finally,__leave异常模型机制,它也即是Windows系列操作系统平台上提供的SEH模型。主人公阿愚将在这里与大家分享SEH的学习过程和经验总结。

  SEH有两项非常强大的功能。当然,首先是异常处理模型了,因此,这篇文章首先深入阐述SEH提供的异常处理模型。另外,SEH还有一个特别强大的功能,这将在下一篇文章中进行详细介绍。

try-except入门

  SEH的异常处理模型主要由try-except语句来完成,它与标准C++所定义的异常处理模型非常类似,也都是可以定义出受监控的代码模块,以及定义异常处理模块等。还是老办法,看一个例子先,代码如下:

//seh-test.c
#include

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

  呵呵!是不是很简单,而且与C++异常处理模型很相似。当然,为了与C++异常处理模型相区别,VC编译器对关键字做了少许变动。首先是在每个关键字加上两个下划线作为前缀,这样既保持了语义上的一致性,另外也尽最大可能来避免了关键字的有可能造成名字冲突而引起的麻烦等;其次,C++异常处理模型是使用catch关键字来定义异常处理模块,而SEH是采用__except关键字来定义。并且,catch关键字后面往往好像接受一个函数参数一样,可以是各种类型的异常数据对象;但是__except关键字则不同,它后面跟的却是一个表达式(可以是各种类型的表达式,后面会进一步分析)。

try-except进阶

  与C++异常处理模型很相似,在一个函数中,可以有多个try-except语句。它们可以是一个平面的线性结构,也可以是分层的嵌套结构。例程代码如下:

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

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

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

puts("world");
}

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

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

puts("world");
}

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

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

puts("in except");
}

puts("world");
}
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后面的括号中的表达式作用域内有效,否则结果可能没有保证(至于为什么,在后面深入分析异常模型的实现时候,再做详细阐述)。看一个例程吧!代码如下:

#include 
#include

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()
{
puts("hello");
__try
{
__try
{
int* p;

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

puts("world");
}

上面的程序运行结果如下:
hello
存储保护异常
内层的except块中
world
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的上层封装的更高级一类的函数,这以后再详细分析它的代码实现。这里还是看一个简单例子吧!代码如下:

#include 
#include

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()
{
puts("hello");
__try
{
puts("try块中");

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

puts("world");
}

上面的程序运行结果如下:
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.51cto.com/xzv587/1363172

__try,__except,__finally,__leave相关推荐

  1. __try,__except,__finally,__leave异常模型机

    导读:  从本篇文章开始,将全面阐述__try,__except,__finally,__leave异常模型机制,它也即是Windows系列操作系统平台上提供的SEH模型.主人公阿愚将在这里与大家分享 ...

  2. C++ 动态内存管理:c/c++的动态内存管理,new/delete,operator new/delete,placement-new, 内存泄漏

    c/c++的动态内存管理 new/delete opeartor new/delete placement-new 内存泄漏 c/c++的动态内存管理 在开始之前首先要了解c和c++的内存分布,我简单 ...

  3. 手把手教你JavaEE的分页查询、分页展示,有了这个,你的项目又多了一个谈资

    前言: 我们在写项目的时候,往往有一些项目的信息展示.而展示的数据量往往是很大的,这时候,加入一个分页的功能往往是最理想的选择. 先简单描述一下功能: 根据你的数据量和指定的页面展示数据条数,进行查询 ...

  4. NumPy — 创建全零、全1、空、arange 数组,array 对象类型,astype 转换数据类型,数组和标量以及数组之间的运算,NumPy 数组共享内存

    NumPy 简介 一个用 python 实现的科学计算包.包括: 1.一个强大的 N 维数组对象 Array : 2.比较成熟的(广播)函数库: 3.用于整合 C/C++ 和 Fortran 代码的工 ...

  5. 不要抱怨,勇敢向前走,你就能拥有更好的自己

    最近半年我无意间看了三个视频:"其中有两个大学生,还有一个博士:毕业几年了都没有工作在家里肯老.可是,他们家境并不好,他们的父母着急得没办法叫了记者和警察". 他们抱怨自己的出身不 ...

  6. 毕业,新的开始,撸起袖子加油干!

    毕业假期: 飞奔回家 6月底,终于忙完了学校了的各种事,迎来了自己的毕业旅行,我选择回家,在家休息了一周. 回家的感觉,整个人慢了下来,和自己静静的相处了几天,调整了身体和心态. 回家的几天,父母也放 ...

  7. CVPR 2020几篇论文内容点评:目标检测跟踪,人脸表情识别,姿态估计,实例分割等

    CVPR 2020几篇论文内容点评:目标检测跟踪,人脸表情识别,姿态估计,实例分割等 CVPR 2020中选论文放榜后,最新开源项目合集也来了. 本届CPVR共接收6656篇论文,中选1470篇,&q ...

  8. Python数据挖掘1:创建一位数组和二维数组,取最大最小值,切片

    ''' 来源:天善智能韦玮老师课堂笔记 1.numpy 可以高效处理数据.提供数组支持.很多模块都依赖他,比如pandas.scipy.matplotlib都依赖他,所以这个模块是基础. 2.pand ...

  9. AndroidStudio git 提交代码,创建分支,合并分支,回滚版本,拉取代码

    主要有: 提交代码,创建分支,合并分支,回滚版本,拉去代码 1 首先电脑中下载git 2 新建的项目把.git 仓库放到项目总中as 工具的右下角 会显示 Git:master 点击有一个弹框如下 然 ...

最新文章

  1. 中国互联网+固体饮料行业商业模式创新与投资机会深度研究报告
  2. java开心农场安卓_开心农场之田园日记
  3. bzoj 1597 土地购买
  4. 编程语言的语法与语义
  5. 太原市智能家居行业协会成立
  6. 幅度和幅值有区别吗_你知道避雷器与浪涌保护器二者的区别吗?
  7. C#listview控件
  8. 不安装iTunes实现USB数据线与电脑传文件
  9. Oracle PO ER Model
  10. Java:15位或18位居民身份证号码简单校验(正则表达式)
  11. BZOJ 1778 [Usaco2010 Hol] Dotp 驱逐猪猡
  12. Java中的数组利用键盘输入求平均数
  13. 【论文阅读记录】孪生网络(Siamese network)
  14. sql中不用group by分组进行计总
  15. @Component和@ComponentScan
  16. Java基础Day01-Java基础语法
  17. 计算机考研学校好又好考的是,考研最容易的985大学 考研院校难度排名
  18. SystemUI之任务管理器
  19. Docker学习-Network网络
  20. 2006-〉2007/01/03 周三 晴天 醉生梦死的假期

热门文章

  1. 活动报名小程序源码/thinkphp后台管理报名小程序源码
  2. DPlayer播放器本地化P2P解析加速版源码
  3. Java23种设计模式之概念篇
  4. 通过IHttpHandlerFactory,过滤TextBox、Input和Textarea中的特殊字符
  5. PHP 多维数组搜索 PHP multi dimensional array search
  6. Swift中文教程(十三) 继承
  7. 15款提高表格操作的jQuery插件
  8. EtherCAT是什么?
  9. LeetCode 524. Longest Word in Dictionary through Deleting
  10. 【AI视野·今日CV 计算机视觉论文速览 第237期】Thu, 30 Sep 2021