ImageVerifierCode 换一换
格式:DOCX , 页数:30 ,大小:158.22KB ,
资源ID:3497475      下载积分:12 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/3497475.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(24异常处理程序和软件异常.docx)为本站会员(b****3)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

24异常处理程序和软件异常.docx

1、24异常处理程序和软件异常第24章 异常处理程序和软件异常异常是我们不希望有的事件。在编写程序的时候,程序员不会想去存取一个无效的内存地址或用0来除一个数值。不过,这样的错误还是常常会发生的。C P U负责捕捉无效内存访问和用0除一个数值这种错误,并相应引发一个异常作为对这些错误的反应。C P U引发的异常,就是所谓的硬件异常( hardware exception)。在本章的后面,我们还会看到操作系统和应用程序也可以引发相应的异常,称为软件异常( software exception)当出现一个硬件或软件异常时,操作系统向应用程序提供机会来考察是什么类型的异常被引发,并能够让应用程序自己来处

2、理异常。下面就是异常处理程序的文法:_try /Guarded body ._except(exception filter) / Exception handler .注意_e x c e p t关键字。每当你建立一个t r y块,它必须跟随一个f i n a l l y块或一个e x c e p t块。一个try 块之后不能既有f i n a l l y块又有e x c e p t块。但可以在t r y - e x c e p t块中嵌套t r y - f i n a l l y块,反过来也可以。24.1 通过例子理解异常过滤器和异常处理程序与结束处理程序(前一章讨论过)不同,异常过滤器(

3、 exception filter)和异常处理程序是通过操作系统直接执行的,编译程序在计算异常过滤器表达式和执行异常处理程序方面不做什么事。下面几节的内容举例说明t r y - e x c e p t块的正常执行,解释操作系统如何以及为什么计算异常过滤器,并给出操作系统执行异常处理程序中代码的环境。24.1.1 Funcmeister1这里是一个t r y - e x c e p t i o n块的更具体的例子。DWORD Funcmeister1() DWORD dwTemp; /1. Do any processing here. . _try /2. Perform some opera

4、tion. dwTemp = 0; _except(EXCEPTION_EXECUTE_HANDLER) / Handle an exception; this never executes. . /3. Continue processing. return(dwTemp);在F u n c m e i s t e r 1的t r y块中,只是把一个0赋给d w Te m p变量。这个操作决不会造成异常的引发,所以e x c e p t块中的代码永远不会执行。注意这与t r y - f i n a l l y行为的不同。在d w Te m p被设置成0之后,下一个要执行的指令是r e t u

5、 r n语句。尽管在结束处理程序的t r y块中使用r e t u r n、g o t o、c o n t i n u e和b r e a k语句遭到强烈地反对,但在异常处理程序的t r y块中使用这些语句不会产生速度和代码规模方面的不良影响。这样的语句出现在与e x c e p t块相结合的t r y块中不会引起局部展开的系统开销。24.1.2 Funcmeister2让我们修改这个函数,看会发生什么事情:DWORD Funcmeister2() DWORD dwTemp = 0; /1. Do any processing here. . _try /2. Perform some ope

6、ration(s). dwTemp = 5 / dwTemp; / Generates an exception dwTemp += 10; / Never executes _except( /* 3. Evaluate filter. */ EXCEPTION_EXECUTE_HANDLER) /4. Handle an exception. MessageBeep(0); . /5. Continue processing. return(dwTemp);F u n c m e i s t e r 2中,t r y块中有一个指令试图以0来除5。C P U将捕捉这个事件,并引发一个硬件异常

7、。当引发了这个异常时,系统将定位到e x c e p t块的开头,并计算异常过滤器表达式的值,过滤器表达式的结果值只能是下面三个标识符之一,这些标识符定义在Wi n d o w s的E x c p t . h文件中(见表2 4 - 1)。表24-1 标识符及其定义 标识符定义为E X C E P T I O N _ E X E C U T E _ H A N D L E R1E X C E P T I O N _ C O N T I N U E _ S E A R C H0E X C E P T I O N _ C O N T I N U E _ E X E C U T I O N-1下面几节

8、将讨论这些标识符如何改变线程的执行。在阅读这些内容时可参阅图2 4 - 1,该图概括了系统如何处理一个异常的情况。图24-1 系统如何处理一个异常24.2 EXCEPTION_EXECUTE_HANDLER在F u n c m e i s t e r 2中,异常过滤器表达式的值是E X C E P T I O N _ E X E C U T E _ H A N D L E R。这个值的意思是要告诉系统:“我认出了这个异常。即,我感觉这个异常可能在某个时候发生,我已编写了代码来处理这个问题,现在我想执行这个代码。”在这个时候,系统执行一个全局展开(本章后面将讨论),然后执行向e x c e p

9、t块中代码(异常处理程序代码)的跳转。在e x c e p t块中代码执行完之后,系统考虑这个要被处理的异常并允许应用程序继续执行。这种机制使Wi n d o w s应用程序可以抓住错误并处理错误,再使程序继续运行,不需要用户知道错误的发生。但是,当e x c e p t块执行后,代码将从何处恢复执行?稍加思索,我们就可以想到几种可能性。第一种可能性是从产生异常的C P U指令之后恢复执行。在F u n c m e i s t e r 2中执行将从对d w Te m p加1 0的指令开始恢复。这看起来像是合理的做法,但实际上,很多程序的编写方式使得当前面的指令出错时,后续的指令不能够继续成功地

10、执行。在F u n c m e i s t e r 2中,代码可以继续正常执行,但是, F u n c m e i s t e r 2已不是正常的情况。代码应该尽可能地结构化,这样,在产生异常的指令之后的C P U指令有望获得有效的返回值。例如,可能有一个指令分配内存,后面一系列指令要执行对该内存的操作。如果内存不能够被分配,则所有后续的指令都将失败,上面这个程序重复地产生异常。这里是另外一个例子,说明为什么在一个失败的C P U指令之后,执行不能够继续。我们用下面的程序行来替代F u n c m e i s t e r 2中产生异常的C语句:malloc(5 / dwTemp);对上面的程序

11、行,编译程序产生C P U指令来执行除法,将结果压入栈中,并调用m a l l o c函数。如果除法失败,代码就不能继续执行。系统必须向栈中压东西,否则,栈就被破坏了。所幸的是,微软没有让系统从产生异常的指令之后恢复指令的执行。这种决策使我们免于面对上面的问题。第二种可能性是从产生异常的指令恢复执行。这是很有意思的可能性。如果在e x c e p t块中有这样的语句会怎么样呢:dwTemp = 2;在e x c e p t块中有了这个赋值语句,可以从产生异常的指令恢复执行。这一次,将用2来除5,执行将继续,不会产生其他的异常。可以做些修改,让系统重新执行产生异常的指令。你会发现这种方法将导致某

12、些微妙的行为。我们将在“ EXCEPTION_ CONTINUE_EXECUTION”一节中讨论这种技术。第三种可能性是从e x c e p t块之后的第一条指令开始恢复执行。这实际是当异常过滤器表达式的值为E X C E P T I O N _ E X E C U T E _ H A N D L E R时所发生的事。在e x c e p t块中的代码结束执行后,控制从e x c e p t块之后的第一条指令恢复。24.2.1 一些有用的例子假如要实现一个完全强壮的应用程序,该程序需要每周7天,每天2 4小时运行。在今天的世界里,软件变得这么复杂,有那么多的变量和因子来影响程序的性能,笔者认为

13、如果不用S E H,要实现完全强壮的应用程序简直是不可能的。我们先来看一个样板程序,即C的运行时函数s t r c p y:char* strcpy( char* strDestination, const char* strSource);这是一个相当简单的函数,它怎么会引起一个进程结束呢?如果调用者对这些参数中的某一个传递N U L L(或任何无效的地址),s t r c p y就引起一个存取异常,并且导致整个进程结束。使用S E H,就可以建立一个完全强壮的s t r c p y函数:char* RobustStrCpy(char* strDestination, const char*

14、 strSource) _try strcpy(strDestination, strSource); _except(EXCEPTION_EXECUTE_HANDLER) / Nothing to do here return(strDestination);这个函数所做的一切就是将对s t r c p y的调用置于一个结构化的异常处理框架中。如果s t r c p y执行成功,函数就返回。如果s t r c p y引起一个存取异常,异常过滤器返回E X C E P T I O N _E X E C U T E _ H A N D L E R,导致该线程执行异常处理程序代码。在这个函数中,处

15、理程序代码什么也不做,R o b u s t S t r C p y只是返回到它的调用者,根本不会造成进程结束。我们再看另外一个例子。这个函数返回一个字符串里的以空格分界的符号个数:int RobustHowManyToken(const char* str) int nHowManyTokens = -1; / -1 indicates failure char* strTemp = NULL; / Assume failure _try / Allocate a temporary buffer strTemp = (char*) malloc(strlen(str) + 1); / Co

16、py the original string to the temporary buffer strcpy(strTemp, str); / Get the first token char* pszToken = strtok(strTemp, ); / Iterate through all the tokens for(; pszToken != NULL; pszToken = strtok(NULL, ) nHowManyTokens+; nHowManyTokens+; / Add 1 since we started at -1 _except(EXCEPTION_EXECUTE

17、_HANDLER) / Nothing to do here /Free the temporary buffer (guaranteed) free(strTemp); return(nHowManyTokens);这个函数分配一个临时缓冲区并将一个字符串复制到里面。然后该函数用C运行时函数strtok来获取字符串的符号。临时缓冲区是必要的,因strtok要修改它所操作的串。感谢有了SEH,这个非常简单的函数就处理了所有的可能性。我们来看在几个不同的情况下函数是如何执行的。首先,如果调用者向函数传递了N U L L(或任何无效的内存地址),n H o w M a n y To k e n s

18、被初始化成-1。在t r y块中对s t r l e n的调用会引起存取异常。异常过滤器获得控制并将控制转移给e x c e p t块,e x c e p t块什么也不做。在e x c e p t块之后,调用f r e e来释放临时内存块。但是,这个内存从未分配,所以结束调用f r e e,向它传递N U L L作为参数。ANSI C明确说明用N U L L作为参数调用f r e e是合法的。这时f r e e什么也不做,这并不是错误。最后,函数返回-1,指出失败。注意进程并没有结束。其次,调用者可能向函数传递了一个有效的地址,但对m a l l o c的调用(在t r y块中)可能失败并返回

19、N U L L。这将导致对s t r c p y的调用引起一个存取异常。同样,异常过滤器被调用,e x c e p t块执行(什么也不做),f r e e被调用,传递给它N U L L(什么也不做),返回-1,告诉调用程序该函数失败。注意进程也没有结束。最后,假定调用者向函数传递了一个有效的地址,并且对m a l l o c的调用也成功了。这种情况下,其余的代码也会成功地在n H o w M a n y To k e n s变量中计算符号的数量。在t r y块的结尾,异常过滤器不会被求值, e x c e p t块中代码不会被执行,临时内存缓冲区将被释放,并向调用者返回n H o w M a

20、n y To k e n s。使用S E H会感觉很好。R o b u s t H o w M a n y To k e n函数说明了如何在不使用t r y - f i n a l l y的情况下保证释放资源。在异常处理程序之后的代码也都能保证被执行(假定函数没有从t r y块中返回应避免的事情)。我们再看一个特别有用的S E H例子。这里的函数重复一个内存块:PBYTE RobustMemDup(PBYTE pbSrc, size_t cb) PBYTE pbDup = NULL; / Assume failure _try / Allocate a buffer for the dupli

21、cate memory block pbDup = (PBYTE) malloc(cb); memcpy(pbDup, pbSrc, cb); _except(EXCEPTION_EXECUTE_HANDLER) free(pbDup); pbDup = NULL; return(pbDup);这个函数分配一个内存缓冲区,并从源块向目的块复制字节。然后函数将复制的内存缓冲区的地址返回给调用程序(如果函数失败则返回N U L L)。希望调用程序在不需要缓冲区时释放它。这是在e x c e p t块中实际有代码的第一个例子。我们看一看这个函数在不同条件下是如何执行的。 如果调用程序对p b S r

22、 c参数传递了一个无效地址,或者如果对m a l l o c的调用失败(返回N U L L),m e m c p y将引起一个存取异常。该存取异常执行过滤器,将控制转移到e x c e p t块。在e x c e p t块内,内存缓冲区被释放, p b D u p被设置成N U L L以便调用程序能够知道函数失败。这里,注意ANSI C允许对f r e e传递N U L L。 如果调用程序给函数传递一个有效地址,并且如果对m a l l o c的调用成功,则新分配内存块的地址返回给调用程序。24.2.2 全局展开当一个异常过滤器的值为E X C E P T I O N _ E X E C U

23、T E _ H A N D L E R时,系统必须执行一个全局展开(global unwind)。这个全局展开使所有那些在处理异常的t r y _ e x c e p t块之后开始执行但未完成的t r y - f i n a l l y块恢复执行。图2 4 - 2是描述系统如何执行全局展开的流程图,在解释后面的例子时,请参阅这个图。void FuncOStimpy1() /1. Do any processing here. . _try /2. Call another function. FuncORen1(); / Code here never executes. _except( /

24、* 6. Evaluate filter. */ EXCEPTION_EXECUTE_HANDLER) /8. After the unwind, the exception handler executes. MessageBox(); /9. Exception handled-continue execution. void FuncORen1() DWORD dwTemp = 0; /3. Do any processing here. _try /4. Request permission to access protected data. WaitForSingleObject(g

25、_hSem, INFINITE); /5. Modify the data. / An exception is generated here. g_dwProtectedData = 5 / dwTemp; _finally /7. Global unwind occurs because filter evaluated / to EXCEPTION_EXECUTE_HANDLER. / Allow others to use protected data. ReleaseSemaphore(g_hSem, 1, NULL); / Continue processing-never exe

26、cutes. . 函数F u n c O S t i m p y 1和F u n c O r e n 1结合起来可以解释S E H最令人疑惑的方面。程序中注释的标号给出了执行的次序,我们现在开始做一些分析。F u n c OSt i m p y 1开始执行,进入它的t r y块并调用F u n c O R e n 1。F u n c O R e n 1开始执行,进入它的t r y块并等待获得信标。当它得到信标,F u n c O R e n 1试图改变全局数据变量g _ d w P r o t e c t e d D a t a。但由于除以0而产生一个异常。系统因此取得控制,开始搜索一个与e

27、x c e p t块相配的t r y块。因为F u n c O R e n 1中的t r y与同一个f i n a l l y块相配,所以系统再上溯寻找另外的t r y块。这里,系统在F u n c O S t i m p y 1中找到一个t r y块,并且发现这个t r y块与一个e x c e p t块相配。系统现在计算与F u n c O S t i m p y 1中e x c e p t块相联的异常过滤器的值,并等待返回值。当系统看到返回值是E X C E P T I O N _ E X E C U T E _ H A N D L E R的,系统就在F u n c O R e n 1的

28、f i n a l l y块中开始一个全局展开。注意这个展开是在系统执行F u n c O S t i m p y 1的e x c e p t块中的代码之前发生的。对于一个全局展开,系统回到所有未完成的t r y块的结尾,查找与f i n a l l y块相配的t r y块。在这里,系统发现的f i n a l l y块是F u n c O R e n 1中所包含的f i n a l l y块。当系统执行F u n c O R e n 1 的f i n a l l y块中的代码时,就可以清楚地看到S E H的作用了。F u n c O R e n 1释放信标,使另一个线程恢复执行。如果这个f

29、i n a l l y块中不包含R e l e a s e S e m a p h o r e的调用,则信标不会被释放。在f i n a l l y块中包含的代码执行完之后,系统继续上溯,查找需要执行的未完成f i n a l l y块。在这个例子中已经没有这样的f i n a l l y块了。系统到达要处理异常的t r y - e x c e p t块就停止上溯。这时,全局展开结束,系统可以执行e x c e p t块中所包含的代码。结构化异常处理就是这样工作的。S E H比较难于理解,是因为在代码的执行当中与系统牵扯太多。程序代码不再是从头到尾执行,系统使代码段按照它的规定次序执行。这种执

30、行次序虽然复杂,但可以预料。按图2 4 - 1和图2 4 - 2的流程图去做,就可以有把握地使用S E H。图24-2 系统如何执行一个全局展开为了更好地理解这个执行次序,我们再从不同的角度来看发生的事情。当一个过滤器返回E X C E P T I O N _ E X E C U T E _ H A N D L E R时,过滤器是在告诉系统,线程的指令指针应该指向e x c e p t块中的代码。但这个指令指针在F u n c O R e n 1的t r y块里。回忆一下第2 3章,每当一个线程要从一个t r y - f i n a l l y块离开时,必须保证执行f i n a l l y块中的代码。在发生异常时,全局展开就是保证这条规则的机制。24.2.3 暂停全局展开通过在f i n a l l y块里放入一个r e t u r n语句,可以阻止系统去完成一个全局展开。请看下面的代码:void FuncMonkey() _try FuncFish(); _except(EXCEPTION_EXECUTE_HANDLER) MessageBeep(0); MessageBox();void FuncFish() FuncPheasant(); MessageBox();void FuncPheasant() _try strcpy(NULL, NULL); _finally

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1