第五部分结构化异常处理

上传人:ldj****22 文档编号:35985725 上传时间:2018-03-23 格式:PDF 页数:14 大小:631.03KB
返回 下载 相关 举报
第五部分结构化异常处理_第1页
第1页 / 共14页
第五部分结构化异常处理_第2页
第2页 / 共14页
第五部分结构化异常处理_第3页
第3页 / 共14页
第五部分结构化异常处理_第4页
第4页 / 共14页
第五部分结构化异常处理_第5页
第5页 / 共14页
点击查看更多>>
资源描述

《第五部分结构化异常处理》由会员分享,可在线阅读,更多相关《第五部分结构化异常处理(14页珍藏版)》请在金锄头文库上搜索。

1、下载第2 3章结束处理程序你可以闭上眼睛,想象有这样一个编程环境,在这个环境里,你编写的代码永远不会出错。 总有足够的内存,没有人会传递给你一个无效的指针,你需要的文件总是存在。如果按照这种 假想,编写程序不是很愉快的事情吗?那样的话,程序会非常容易编写、阅读和理解。我们不 会再让每个程序中通篇的i f语句和g o t o语句搞得昏头昏脑,只需从头到尾书写代码就是了。 如果说这种直接的编程环境只是一个美妙的梦想的话,那么结构化异常处理( S E H)就会 给你一个现实的惊喜。使用 S E H的好处就是当你编写程序时,只需要关注程序要完成的任务。 如果在运行时发生什么错误,系统会发现并将发生的问

2、题通知你。 利用S E H,你可以完全不用考虑代码里是不是有错误,这样就把主要的工作同错误处理分 离开来。这样的分离,可以使你集中精力处理眼前的工作,而将可能发生的错误放在后面处 理。 微软在Wi n d o w s中引入S E H的主要动机是为了便于操作系统本身的开发。操作系统的开发 人员使用S E H,使得系统更加强壮。我们也可以使用S E H,使我们的自己的程序更加强壮。 使用 S E H所造成的负担主要由编译程序来承担,而不是由操作系统承担。当异常块 (exception block)出现时,编译程序要生成特殊的代码。编译程序必须产生一些表( t a b l e)来 支持处理S E H

3、的数据结构。编译程序还必须提供回调( c a l l b a c k)函数,操作系统可以调用这 些函数,保证异常块被处理。编译程序还要负责准备栈结构和其他内部信息,供操作系统使用 和参考。在编译程序中增加S E H支持不是一件容易的事。不同的编译程序厂商会以不同的方式 实现S E H,这一点并不让人感到奇怪。幸亏我们可以不必考虑编译程序的实现细节,而只使用 编译程序的S E H功能。 由于各编译程序的实现上存在着差别,这样以特定的方式用特定的代码例子讨论 S E H的优 点就很困难。但大多数编译程序厂商都采用微软建议的文法。本书中的例子使用的文法和关键 字可能与其他一些公司编译程序所使用的不同

4、,但主要的S E H概念是一样的。本章使用 Microsoft Visual C+编译程序的文法。注意不要将结构化异常处理同C + +的异常处理相混淆。C + +异常处理是一种不同形 式的异常处理,其形式是使用C + +关键字c a t c h和t h r o w。微软的Visual C+也支持C + + 的异常处理,并且在内部实现上利用了已经引入到编译程序和 Wi n d o w s操作系统的结 构化异常处理的功能。S E H实际包含两个主要功能:结束处理( termination handling)和异常处理( e x c e p t i o n h a n d l i n g) 。本章讨

5、论结束处理,下一章讨论异常处理。 一个结束处理程序能够确保去调用和执行一个代码块 (结束处理程序, termination handler) , 而不管另外一段代码(保护体,guarded body)是如何退出的。结束处理程序的文法结构(使 用微软的Visual C+编译程序)如下:第五部分结构化异常处理- - t r y和- - f i n a l l y关键字用来标出结束处理程序两段代码的轮廓。在上面的代码段中,操作系 统和编译程序共同来确保结束处理程序中的 - - f i n a l l y代码块能够被执行,不管保护体( t r y块) 是如何退出的。不论你在保护体中使用 r e t u

6、 r n,还是g o t o,或者是l o n g j u m p,结束处理程序 (f i n a l l y块)都将被调用。下面将通过几个例子来说明这一点。23.1 通过例子理解结束处理程序由于在使用S E H时,编译程序和操作系统直接参与了程序代码的执行,为了解释 S E H如何 工作,最好的办法就是考察源代码例子,讨论例子中语句执行的次序。 因此,在下面几节给出不同的源代码片段,对每一个片段解释编译程序和操作系统如何改 变代码的执行次序。23.2 Funcenstein1为了甄别使用结束处理程序的各种情况,我们来考察更具体的代码例子。上面程序中加了标号的注释指出了代码执行的次序。在 F

7、u n c e n s t e i n 1中,使用t r y - f i n a l l y块 并没有带来很多好处。代码要等待信标( s e m a p h o r e) ,改变保护数据的内容,保存局部变量 d w Te m p的新值,释放信标,将新值返回给调用程序。23.3 Funcenstein2现在我们把这个程序稍微改动一下,看会发生什么事情。566计计第五部分 结构化异常处理下载在F u n c e n s t e i n 2中,t r y块的末尾增加了一个r e t u r n语句。这个r e t u r n语句告诉编译程序在这 里要退出这个函数并返回d w Te m p变量的内容,

8、现在这个变量的值是 5。但是,如果这个r e t u r n 语句被执行,该线程将不会释放信标,其他线程也就不能再获得对信标的控制。可以想象,这 样的执行次序会产生很大的问题,那些等待信标的线程可能永远不会恢复执行。 通过使用结束处理程序,可以避免 r e t u r n语句的过早执行。当r e t u r n语句试图退出t r y块时, 编译程序要确保f i n a l l y块中的代码首先被执行。要保证f i n a l l y块中的代码在t r y块中的r e t u r n语句 退出之前执行。F u n c e n s t e i n 2中,将对R e l e a s e S e m

9、a p h o r e的调用放在结束处理程序块中,保证 信标总会被释放。这样就不会造成一个线程一直占有信标,否则将意味着所有其他等待信标的 线程永远不会被分配C P U时间。 在f i n a l l y块中的代码执行之后,函数实际上就返回。任何出现在 f i n a l l y块之下的代码将不 再执行,因为函数已在t r y块中返回。所以这个函数的返回值是5,而不是9。 读者可能要问编译程序是如何保证在t r y块可以退出之前执行f i n a l l y块的。当编译程序检查源代 码时,它看到在t r y块中有r e t u r n语句。这样,编译程序就生成代码将返回值(本例中是 5)保存在

10、一 个编译程序建立的临时变量中。编译程序然后再生成代码来执行 f i n a l l y块中包含的指令,这称为局 部展开。更特殊的情况是,由于t r y块中存在过早退出的代码,从而产生局部展开,导致系统执行 f i n a l l y块中的内容。在f i n a l l y块中的指令执行之后,编译程序临时变量的值被取出并从函数中返回。 可以看到,要完成这些事情,编译程序必须生成附加的代码,系统要执行额外的工作。在 不同的C P U上,结束处理所需要的步骤也不同。例如,在 A l p h a处理器上,必须执行几百个甚 至几千个C P U指令来捕捉t r y块中的过早返回并调用f i n a l

11、l y块。在编写代码时,就应该避免引起 结束处理程序的t r y块中的过早退出,因为程序的性能会受到影响。本章后面,将讨论 _ _ l e a v e 关键字,它有助于避免编写引起局部展开的代码。 设计异常处理的目的是用来捕捉异常的不常发生的语法规则的异常情况(在我们的例 子中,就是过早返回) 。如果情况是正常的,明确地检查这些情况,比起依赖操作系统和编译第 23章 结束处理程序计计567下载程序的 S E H功能来捕捉常见的事情要更有效。 注意当控制流自然地离开t r y块并进入f i n a l l y块(就像在F u n c e n s t e i n 1中)时,进入f i n a l

12、l y块 的系统开销是最小的。在x86 CPU上使用微软的编译程序,当执行离开 try 块进入f i n a l l y块时, 只有一个机器指令被执行,读者可以在自己的程序中注意到这种系统开销。当编译程序要生成 额外的代码,系统要执行额外的工作时(如同在F u n c e n s t e i n 2中) ,系统开销就很值得注意了。23.4 Funcenstein3现在我们对函数再做修改,看会出现什么情况:在F u n c e n s t e i n 3中,当编译程序看到t r y块中的g o t o语句,它首先生成一个局部展开来执行 f i n a l l y块中的内容。这一次,在f i n

13、a l l y块中的代码执行之后,在R e t u r n Va l u e标号之后的代码将 执行,因为在t r y块和f i n a l l y块中都没有返回发生。这里的代码使函数返回 5。而且,由于中断 了从t r y块到f i n a l l y块的自然流程,可能要蒙受很大的性能损失(取决于运行程序的 C P U) 。23.5 Funcfurter1现在我们来考察另外的情况,这里可以真正显示结束处理的价值。看下面的函数:568计计第五部分 结构化异常处理下载现在假想一下,t r y块中的F u n c i n a t o r函数调用包含一个错误,会引起一个无效内存访问。 如果没有S E

14、H,在这种情况下,将会给用户显示一个很常见的Application Error对话框。当用户 忽略这个错误对话框,该进程就结束了。当这个进程结束(由于一个无效内存访问) ,信标仍 将被占用并且永远不会被释放,这时候,任何等待信标的其他进程中的线程将不会被分配 C P U 时间。但若将对R e l e a s e S e m a p h o r e的调用放在f i n a l l y块中,就可以保证信标获得释放,即使某 些其他函数会引起内存访问错误。 如果结束处理程序足够强,能够捕捉由于无效内存访问而结束的进程,我们就可以相信它 也能够捕捉s e t j u m p和l o n g j u m

15、p的结合,还有那些简单语句如b r e a k和c o n t i n u e。23.6 突击测验:F u n c a D o o d l e D o o现在做一个测试,读者判断一下下面的函数返回什么值?我们一步一步地分析函数做了什么。首先d w Te m p被设置成0。t r y块中的代码执行,但两个 i f语句的值都不为 T R U E。执行自然移到 f i n a l l y块中的代码,在这里 d w Te m p增加到1。然后 f i n a l l y块之后的指令又增加d w Te m p,使它的值成为2。第 23章 结束处理程序计计569下载当循环继续,d w Te m p为2,t

16、 r y块中的c o n t i n u e语句将被执行。如果没有结束处理程序在从 t r y块中退出之前强制执行f i n a l l y块,执行就立即跳回w h i l e测试,d w Te m p不会被改变,将出现 无限(死)循环。利用一个结束处理程序,系统知道c o n t i n u e语句要引起控制流过早退出t r y块, 而将执行移到f i n a l l y块。在f i n a l l y块中,d w Te m p被增加到3。但f i n a l l y块之后的代码不执行,因 为控制流又返回到c o n t i n u e,再到循环的开头。 现在我们处理循环的第三次重复。这一次,第一个 i f语句的值是FA L S E,但第二个语句的 值是T R U E。系统又能够捕捉要跳出t r y块的企图,并先执行f i n a l l y块中的代码。现在d w Te m p增 加到4。由于b r e a k语句被执行,循环之后程序部分的控制恢复。这样, f i n a l l y块之后的循环中 的代码没有执行。循环下面的代码对d w Te m p增

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 行业资料 > 其它行业文档

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号