关闭

关闭

关闭

封号提示

内容

首页 C与C++中的异常处理.pdf

C与C++中的异常处理.pdf

C与C++中的异常处理.pdf

上传者: bjutzw 2011-10-10 评分 0 0 0 0 0 0 暂无简介 简介 举报

简介:本文档为《C与C++中的异常处理pdf》,可适用于IT/计算机领域,主题内容包含C与C中的异常处理RobertSchmidt著无情译目录异常和标准C对它的支持Microsoft对异常处理方法的扩展标准C异常处理的基本语法和语义实符等。

C与C中的异常处理RobertSchmidt著无情译目录异常和标准C对它的支持Microsoft对异常处理方法的扩展标准C异常处理的基本语法和语义实例剖析EHC的new和delete操作时的异常处理Microsoft对于<new>的实现版本中的异常处理部分构造及placementdelete自动删除类属new和delete、placementnew和placementdeleteplacementnew和placementdelete及处理构造函数抛出的异常从私有子对象中产生的异常异常规格申明unexpected()的实现上固有的限制异常安全模板安全模板安全(续)指导方针C异常和VisualCSEH的混合使用异常和标准C对它的支持(前言略)异常分类基于DrGUI的建议我把我的第一个专栏投入到“程序异常”的系列上。我认识到“exception”这个术语有些不明确并和上下文相关尤其是C标准异常(Cstandardexceptions)和Microsoft的结构化异常(structuredexceptionhandling)。不幸的的是“异常”一词太常见了随时出现在语言的标准和常见的编程文献中。因为不想创造一个新名词所以我将尽力在此系列的各部分中明确我对“异常”的用法。zPart概述通常意义上的异常的性质和标准C库提供的处理它们的方法。zPart纵览Microsoft对这些标准C库方法的扩展:专门的宏和结构化异常处理。zPart及其余将致力于标准C异常处理体系。(C语言使用者可能在Part后放弃但我鼓励你坚持到底我所提出的许多点子同样适用于C虽然不是很直接。)本质上看程序异常是指出现了一些很少发生的或出乎意料的状态通常显示了一个程序错误或要求一个必须提供的回应。不能满足这个回应经常造成程序功能削弱或死亡有时导致整个系统和它一起down掉。不幸的是试图使用传统的防护方法来编制健壮的代码经常只是将一个问题(意外崩溃)换成了另外一个问题(更混乱的设计和代码)。太多的程序员认为这个交换抵不上程序意外崩溃时造成的烦恼于是选择了生活在危险之中。认识到这一点后C标准增加了一个优雅并且基本上不可见的“异常体系”到语言中就这样这个方法产生了。如同我们在Part的开始部分将要看到的这个方法大部分情况下很成功但在很微妙的情况下可能失败。异常的生命阶段在这个系列里我将展示C和C处理异常体系运行于异常整个生命期的每一阶段时的不同之处:z阶段:一个软件错误发生。这个错误也许产生于一个被底层驱动或内核映射为软件错误的硬件响应事件(如被除)。z阶段:错误的原因和性质被一个异常对象携带。这个对象的类型可以简单的整数值到繁杂的C类对象。z阶段:你的程序必须检测这个异常对象:或者轮询它的存在或者由其主动上报。z阶段:检测代码必须决定如何处理异常。典型的方法分成三类。a忽略异常对象并期望别人处理它。b在这个对象上干些什么并还允许别人再继续处理它。c获得异常的全部所有权。z阶段:既然异常已经处理了程序通常恢复并继续执行。恢复分成两种:a恢复异常从异常发生处继续执行。b终止异常从异常被处理处继续执行。当在程序外面(由运行期库或操作系统)终止异常时恢复经常是不可能的程序将异常结束。我故意忽略了硬件错误事件因为它们完全是底层平台范围内的事。取而代之我假定一些软件上的可检测错误已经发生并产生了一个处于第一阶段的软件异常对象。C标准库异常处理体系C标准库提供了几个方法来处理异常。它们也全部在标准C中有效只是相关的头文件名字变了:老的C标准头文件<nameh>映射到了新的C标准头文件<cname>。(头文件名的前缀“C”是个助记符暗示着这些全是C库头文件。)虽然基于向后兼容性老的C头文件也被C保留但我建议你尽可能使用新的头文件。对于绝大部分实际使用而言最大的变化是在新的头文件中申明的函数被包含在命名空间std内。举个例子C语言使用#include<stdioh>FILE*f=fopen("blarneytxt","r")在C中被改成#include<cstdio>std::FILE*f=std::fopen("blarneytxt","r")或更C风格的#include<cstdio>usingnamespacestdFILE*f=fopen("blarneytxt","r")不幸的是Microsoft的VisualC没有将这些新的头文件包含在命名空间std中虽然这是C标准所要求的(subclauseD)。除非VisualC在这些头文件中已经正确地支持了std我将一直在我的专栏中使用老式的C风格命名。(象MIcrosoft这样的运行库卖主这么做是合理的正确地实现这些C程序库的头文件极可能要求维护和测试两份完全不同的底层代码这是不可能受欢迎的也不值得多花力气的工作。)无条件终止仅次于彻底忽略一个异常大概最容易的异常处理方法是程序自我毁灭。有时最懒的方法事实上是最正确的。在你开始嘲笑以前应该认识到一些异常表示的状况是如此严重以致于怎么也不可能合理恢复的。也许最好的例子就是malloc时返回。如果空闲堆管理程序不能提供可用的连续空间你程序的健壮性将严重受损并且恢复的可能性是渺茫的。C库头文件<stdlibh>提供了两个终止程序的函数:abort()和exit()。这两个函数运行于异常生命期的和。它们都不会返回到其调用者中并都导致程序结束。这样它们就是结束异常处理的最后一步。虽然两个函数在概念上是相联系的但它们的效果不同:zabort():程序异常结束。默认情况下调用abort()导致运行期诊断和程序自毁。它可能会也可能不会刷新缓冲区、关闭被打开的文件及删除临时文件这依赖于你的编译器的具体实现。zexit():文明地结束程序。除了关闭文件和给运行环境返回一个状态码外exit()还调用了你挂接的atexit()处理程序。一般调用abort()处理灾难性的程序故障。因为abort()的默认行为是立即终止程序你就必须负责在调用abort()前存储重要数据。(当我们谈论到<signalh>时你可以使得abort()自动调用cleanup代码。)相反exit()执行了挂接在atexit()上的自定义cleanup代码。这些代码被按照其挂接的反序执行你可以把它们当作虚拟析构器。通过必要的cleanup代码你可以安全地终止程序而没有留下尾巴。例如:#include<stdioh>#include<stdlibh>staticvoidatexithandler(void){printf("within'atexithandler'n")}staticvoidatexithandler(void){printf("within'atexithandler'n")}intmain(void){atexit(atexithandler)atexit(atexithandler)exit(EXITSUCCESS)printf("thislineshouldneverappearn")return}*Whenrunyieldswithin'atexithandler'within'atexithandler'andreturnsasuccesscodetocallingenvironment*(注意即使是程序从main()正常返回而没有明确调用exit()所挂接的atexit()代码仍然会被调用。)无论abort()还是exit()都不会返回到它的调用者中且都将导致程序结束。在这个意义上来说它们都表现为终止异常的最后一步。有条件地终止abort()和exit()让你无条件终止程序。你还可以有条件地终止程序。其实现体系是每个程序员所喜爱的诊断工具:断言定义于<asserth>。这个宏的典型实现如下所示:#ifdefinedNDEBUG#defineassert(condition)((void))#else#defineassert(condition)assert((condition),#condition,FILE,LINE)#endif如定义体所示当宏NDEBUG被定义时断言是无行为的这暗示了它只对调试版本有效。于是断言条件从不在非调试版本中被求值这会造成同样的代码在调试和非调试版本间有奇妙的差异。*debugversion*#undefNDEBUG#include<asserth>#include<stdioh>intmain(void){inti=assert(i!=)printf("iisdn",i)return}*Whenrunyieldsiis*现在通过定义NDEBUG从debug版变到release版:*releaseversion*#defingNDEBUG#include<asserth>#include<stdioh>intmain(void){inti=assert(i!=)printf("iisdn",i)return}*Whenrunyieldsiis*要避免这个差异必须确保断言表达式的求值不会包含有影响的副作用。在仅供调试版使用的定义体中断言变成呼叫assert()函数。我起了这个名字而你所用的运行库的实现可以调用任何它想调用的内部函数。无论它叫什么这个函数通常有以下形式:voidassert(inttest,charconst*testimage,charconst*file,intline){if(!test){printf("Assertionfailed:s,files,linedn",testimage,file,line)abort()}}所以失败的断言在调用abort()前显示出失败情况的诊断条件、出错的源文件名称和行号。我在这里演示的诊断机构“printf()”相当粗糙你所用的运行库的实现可能产生更多的反馈信息。断言处理了异常的阶段到。它们实际上是一个带说明信息的abort()并做了前提条件检查如果检查失败程序中止。一般使用断言调试逻辑错误和绝不可能出现在正确的程序中的情况。*'f'nevercalledbyotherprograms*staticvoidf(int*p){assert(p!=)**}对比一下逻辑错误和可以存在于正确程序中的运行期错误:*getfile'name'fromuser*FILE*file=fopen(name,mode)assert(file!=)*questionableuse*这样的错误表示异常情况但不是bug。对这些运行期异常断言大概不是个合适的处理方法你应该用我下面将介绍的另一个体系来代替。非局部的跳转与刺激的abort()和exit()相比,goto语句看起来是处理异常的更可行方案。不幸的是goto是本地的:它只能跳到所在函数内部的标号上而不能将控制权转移到所在程序的任意地点(当然除非你的所有代码都在main体中)。为了解决这个限制C函数库提供了setjmp()和longjmp()函数它们分别承担非局部标号和goto作用。头文件<setjmph>申明了这些函数及同时所需的jmpbuf数据类型。原理非常简单:zsetjmp(j)设置“jump”点用正确的程序上下文填充jmpbuf对象j。这个上下文包括程序存放位置、栈和框架指针其它重要的寄存器和内存数据。当初始化完jump的上下文setjmp()返回值。z以后调用longjmp(j,r)的效果就是一个非局部的goto或“长跳转”到由j描述的上下文处(也就是到那原来设置j的setjmp()处)。当作为长跳转的目标而被调用时setjmp()返回r或(如果r设为的话)。(记住setjmp()不能在这种情况时返回。)通过有两类返回值setjmp()让你知道它正在被怎么使用。当设置j时setjmp()如你期望地执行但当作为长跳转的目标时setjmp()就从外面“唤醒”它的上下文。你可以用longjmp()来终止异常用setjmp()标记相应的异常处理程序。#include<setjmph>#include<stdioh>jmpbufjvoidraiseexception(void){printf("exceptionraisedn")longjmp(j,)*jumptoexceptionhandler*printf("thislineshouldneverappearn")}intmain(void){if(setjmp(j)==){printf("'setjmp'isinitializing'j'n")raiseexception()printf("thislineshouldneverappearn")}else{printf("'setjmp'wasjustjumpedinton")*thiscodeistheexceptionhandler*}return}*Whenrunyields:'setjmp'isinitializing'j'exceptionraised'setjmp'wasjustjumpedinto*那个填充jmpbuf的函数不在调用longjmp()之前返回。否则存储在jmpbuf中的上下文就有问题了:jmpbufjvoidf(void){setjmp(j)}intmain(void){f()longjmp(j,)*logicerror*return}所以你必须把setjmp()处理成只是到其所在位置的一个非局部跳转。Longjmp()和setjmp()联合体运行于异常生命期的和阶段。longjmp(j,r)产生异常对象r(一个整数)并且作为返回值传送到setjmp(j)处。实际上setjmp()函数通报了异常r。信号C函数库也提供了标准的(虽然原始的)“事件”处理包。这个包定义了一组事件和信号以及标准的方法来触发和处理它们。这些信号或者表示了一个异常状态或者表示了一个不协调的外部事件基于所谈论的主题我将只集中讨论异常信号。为了使用这些包需要包含标准头文件<signalh>。这个头文件申明了函数raise()和signal()数据类型sigatomict和以SIG开头的信号事件宏。标准要求有六个信号宏也许你所用的运行库实的现会再附加一些。这些信号被固定死在<signalh>中你不能增加自定义的信号。信号通过调用raise()产生并被处理函数捕获。运行时体系提供默认处理函数但你能通过signal()函数安装自己的处理函数。处理函数可以通过sigatomict类型的对象和外部进行通讯如类型名所示对这样的对象的操作是原子操作或者说中断安全的。当你挂接信号处理函数时通常提供一个函数地址这个的函数必须接受一个整型值(所要处理的信号事件)并且无返回。这样信号处理函数有些象setjmp()它们所收到的仅有的异常信息是单个整数:voidhandler(intsignalvalue)voidf(void){signal(SIGFPE,handler)*registerhandler***raise(SIGFPE)*invokehandler,passingit'SIGFPE'*}只可其一地你可以安装两个特别的处理函数:zsignal(SIGxxx,SIGDFL)为指定的信号挂接系统的缺省处理函数。zsignal(SIGxxxSIGIGN)告诉系统忽略指定的信号。signal()函数返回前次挂接的处理函数的地址(表明挂接成功)或返回SIGERR(表明挂接失败)。处理函数被调用表明信号正在试图恢复异常。当然你可以在处理函数中随意地调用abort()、exit()或longjmp()有效地将信号解释为终止异常。有趣的是abort()自己事实上在内部调用了raise(SIGABRT)。SIGABRT的缺省处理函数发起了一个诊断并终止程序当然你可以安装自己的处理函数来改变这个行为。不能改变的是abort()的终止程序的行为。Abort()理论上的实现如下:voidabort(void){raise(SIGABRT)exit(EXITFAILURE)}也就是说即使你的SIGABRT处理函数返回了abort()仍然中止你的程序。C语言标准在信号处理函数的行为上增加了一些限制和解释。如果你有C语言标准我建议你查阅条款的细节。(很不幸C语言和C语言的标准在Internet都得不到。)<signalh>的申明覆盖了异常的整个生存期从产生到死亡。在标准的C语言运行期库中它们是最接近于异常完全解决方案的。全局变量<setjmph>和<signalh>一般使用异常通知体系:当试图通知一个异常事件时唤醒一个处理函数。如果你更愿意使用轮询体系C标准库在<errnoh>提供了例子。这个头文件定义了errno及其一些可能的取值。标准要求这样三个值:EDOM、ERANGE和EILSEQ分别适用于域、范围和多字节顺序错误你的编译器可能又加了些其它的它们全以字母“E”开头。errno通过由运行库的代码设置它而用户代码查询它的办法将二者联系起来运行于异常生命期的到:运行库产生异常对象(一个简单的整数)把值拷给errno然后依赖用户的代码去轮询和检测这个异常。运行库主要在<mathh>和<stdioh>的函数中使用errno。errno在程序开始时设为函数库程序不会再次把它设为。因此要检测错误你必须先将errno设为再调用运行库程序调用完后检查errno的值:#include<errnoh>#include<mathh>#include<stdioh>intmain(void){doublex,y,result*somehowset'x'and'y'*errno=result=pow(x,y)if(errno==EDOM)printf("domainerroronxypairn")elseif(errno==ERANGE)printf("rangeerroronresultn")elseprintf("xtothey=dn",(int)result)return}注意:errno不一定要绑在一个对象上:int*errnofunction(){staticintrealerrno=returnrealerrno}#defineerrno(*errnofunction())intmain(void){errno=**if(errno==EDOM)**}你可以在自己的程序中采用这样的技巧对errno及其值进行模拟。使用C的话你当然可以把这种策略扩展到类或命名空间的对象和函数上。(实际上在C中这个技巧是SingletonPattern的基础。)返回值和回传参数象errno这样的异常对象不是没有限制的:z所有相关联的部分必须一致确保设置和检查同一个对象。z无关的部分可能意外地修改了对象。z如果没有在调用程序前重设对象或在调用下一步前没有检查它们你就可能漏了异常。z宏和内部代码中的对象在重名时将掩盖异常对象。z静态对象天生就不是(多)线程安全的。总之这些对象很脆弱:你太容易用错它们编译器没有警告程序却有不可预测的行为。要排除这些不足你需要这样的对象:z被两个正确的部分访问--一个产生异常一个检测异常。z带有一个正确的值。z名字不能被掩盖线程安全。函数返回值满足这些要求因为它们是无名的临时变量由函数产生而只能被调用者访问。调用一完成调用者就可以检查或拷贝返回值然后原始的返回对象将消失而不能被重用。又因为是无名的它不能被掩盖。(对于C我假设只有右值函数调用表达也就是说不能返回引用。由于我限定现在只谈论C兼容的技巧而C不支持引用这样的假设是合理的。)返回值出现在异常生命期的阶段。在调用和被调用函数的联合体中这只是完整的异常处理的一部分:intf(){interror**if(error)*Stage:erroroccurred*return*Stage:generateexceptionobject***}intmain(void){if(f()!=)*Stage:detectexception*{*Stage:handleexception*}*Stage:recover*}返回值是C标准库所喜欢的异常传播方法。看下面的例子:if((p=malloc(n))==)**if((c=getchar())==EOF)**if((ticks=clock())<)**注意典型的C习惯用法:在同一条语句中接收返回值和检测异常。这种压缩表达式重载一个通道(返回值对象)来携带两个不同的含义:合法的数据值和异常值。代码必须按两条路来解释这个通道直到知道哪个是正确的。这种函数返回值的用法常见于很多语言中尤其是Microsoft开发的语言无关的ComponentObjectModel(COM)。COM方法通过返回一类型为HRESULT(特别安排的位无符号值)的对象提示异常。和刚讨论的例子不同COM的返回值只携带状态和异常信息回传信息通过参数列表中的指针进行。回传指针和C的引用型的参数是函数返回值的变形但有些明显的不同:z你能忽略和丢弃返回值。回传参数则绑定到了相应的实参上所以不可能完全忽略它们。和返回值相比参数在函数和它们的调用者间形成了紧耦合。z通过回传参数可以返回任意个数的值而通过返回值只能返回一个值。所以回传参数提供了多个返回值。z返回值是临时对象:它们在调用前不存在并且在调用结束是消失。实参的生命期远长于函数的调用过程。小结这次大概地介绍了异常和标准C对它的传统支持。第二部分我将研究Microsoft对标准C方法的扩展:特有的异常处理宏、结构化异常处理或说SEH。我将总结所有C兼容方法(包括SEH)的局限性并在第三部分拉开C异常的序幕。回到目录Microsoft对异常处理方法的扩展前次我概述了异常的分类和C标准库支持的处理方法。这次讨论Microsoft对这些方法的扩展:结构化异常处理(SEH)和MicrosoftFoundationClass(MFC)异常处理。SEH对C和C都有效MFC异常体系只对C有效。机构化异常处理机构化异常处理是Windows提供的服务功能并对所有语言写的程序有效。在VisualC中Microsoft封装和简化了这些服务(通过非标准的关键字和库程序)。Windows平台的其它编译器可能选择不同的方式来到达相似的结果。在这个专栏中名词“StructuredExceptionHandling”和“SEH”专指VisualC对Windows异常服务的封装。关键字为了支持SEHMicorsoft用四个新关键字扩展了C和C语言:zexceptzfinallyzleaveztry因为这是非标关键字必须打开扩展选项后再编译(关掉Fa)。为什么这些关键字带下划线?C标准(条款“Globalnames”)规定:下列名字和函数总是保留给编译器:z所有带双下划线()或以一个下划线加一个大写字母开始的名字保留给编译器随意使用。z所有以一个下划线开始的名字保留给编译器作全局名称用。C标准有类似的申明。既然SEH的关键字符合上面的规则Microsoft就有权这样使用它们。这也表明你不被允许在自己的程序中使用保留的名字。你必须避免定义名字类似MYHEADERH或FatalError的标识符。有趣而又不幸地VisualC的applicationwizards产生的源代码使用了保留的标识符。例如如果你用ATLCOMAppWizard生成一个新的service结果框架代码定义了如Handler和twinMain的名字--标准所说的你的程序不能使用的保留名称。要减少这个不合规定行为你当然可以手工更改这些名称。还好这些有疑问的名字都是类的私有变量在类的定义外面是不可见的在h和cpp中进行全局替换是可行的。不幸的是有一个函数(twinMain)和一个对象(Module)被申明了extern也就是说程序的其它部分会假定你使用了这些名字。(事实上VisualC库libclib在连接时需要名字twinMain可用。)我建议你保留Wizard生成的名字不要在你自己的代码中定义这样的名字就可以了。另外你应该将所有不合标准的定义写入文档并留给程序的维护人员记住VisualC以后的版本(和现有的其它C编译器)可能以另外的方式使用这些名字从而破坏了你的代码。标识符Microsoft也在非标头文件excpth中定义了几个SEH的标识符并且包含入windowsh中。在其内部定义了:z供except的过滤表达式使用的过滤结果宏。zWin对象和函数的别名宏用于查询异常信息和状态。z伪关键字宏和前面谈到的四个关键字有着相同名字和含义但没有下划线。(例如宏leave对应SEH关键字leave。)Microsoft用这些宏令我抓狂。他们对同一个函数了定义多个别名。例如excpth有如下申明和定义:unsignedlongcdeclexceptioncode(void)#defineGetExceptionCodeexceptioncode#defineexceptioncodeexceptioncode也就是说你可以用三种方法调用同一函数。你用哪个?并且这些别名会如你所期望地被维护吗?在Microsoft的文档中它看起来偏爱GetExceptionCode它的名字和其它全局WindowsAPI函数风格一致。我在MSDN中搜索到处GetExceptionCode两个exceptioncode而exceptioncode个数为。根据Microsoft的引导推荐使用GetExceptionCode及类似名称的其它函数。因为exceptioncode的两个别名是宏所以你不能再使用同样的名字了。我曾经犯过这个错当我在为这个专栏写例程的时候。我定义了一个局部对象叫exceptioncode(大概是吧)。实际上我就是定义了一个局部对象叫exceptioncode这是我无意中使用的宏exceptioncode展开的结果。当我一想到是这个问题解决方案就是简单地将我的对象名字从exceptioncode改为code。最后excpth定义了一个特别的宏--“try”--已经成为C真正的关键字的东西。这意味着你不能在包含了excpth的编译单元中简单地混合SEH和标准C的异常块除非你愿意#undef这个try宏。当这样undef而露出真正的try关键字时要冒搞乱SEH的维护人员大脑的危险。另一方面精通标准C的程序员会将try理解为一个关键字而不是宏。我认为包含一个头文件(即使是象excpth这样的非标头文件)不应该改变符合语言标准的代码的行为。我更坚持掩盖或重定义掉语言标准定义的关键字是个坏习惯。我建议:#undeftry同样不使用其它的伪关键字宏直接使用真正的关键字(如try)。语法最基本的SEH语法是try块。如下形式:trycompoundstatementhandler处理体:except(filterexpression)compoundstatement或:finallycompoundstatement完整一点看try块如下:try{}except(filterexpression){}或:try{}finally{}在try里面你必须使用一个leave语句:try{leave}在更大的程序块中一个try块被认为是个单条语句:if(x){try{}finally{}}等价于:if(x)try{}finally{}其它注意点:z在给定的try块中你必须有一个正确的异常处理函数。z所有的语句必须合并。即使只有一条语句跟在try、except或finally后面也必须将它放入{}中。z在异常处理函数中相应的过滤表达式必须有一个或能转换为一个int型的值。基本语意上次我列举了异常生命期的个阶段。在SEH体系下这些阶段实现如下:z操作系统上报了一个硬件错误或检测到了一个软件错误或用户代码检测到一个错误(阶段)。z(通常是由用户调用Win函数RasieException启动)操作系统产生并触发一个异常对象(阶段)。这个对象是一个结构其属性对异常处理函数可见。z异常处理函数“看到”异常并且有机会捕获它(阶段和)。取决于处理函数的意愿异常将或者恢复或者终止。(阶段)。一个简单的例子:intfilter(void){*Stage*}intmain(void){try{if(someerror)*Stage*RaiseException()*Stage**Stageofresumingexception*}except(filter())*Stage*{*Stageofterminatingexception*}return}Microsoft调用定义在except中的异常处理函数和定义在finally中的终止函数。一旦异常被触发由except开始的异常处理函数被异常发生点顺函数调用链向外面询问。每个被发现的异常处理函数其过滤表达式都被求值。每次求值后发生什么取决于其返回结果。excpth定义了个过滤结果的宏都是int型的:zEXCEPTIONCONTINUEEXECUTION=zEXCEPTIONCONTINUESEARCH=zEXCEPTIONEXECUTEHANDLER=前面我说过过滤表达式必须兼容int型所以它们和这个宏的值匹配。这个说法太保守了:我的经验显示VisualC接受的过滤表达式可以具有所有的整型、指针型、结构、数组甚至是void型!(但我在尝试浮点指针时遇到了编译错误。)更进一步所有求出的值看来都有效(至少对整型如此)。所有非零且符号位为的值效果相当于EXCEPTIONEXECUTEHANDLER而符号位为的相当于EXCEPTIONCONTINUEEXECUTION。这大概是按位取模的结果。如果一个异常处理函数的过滤求值结果是EXCEPTIONCONTINUESEARCH这个处理函数拒绝捕获异常将继续搜索下一个异常处理函数。通过由过滤表达式产生一个非EXCEPTIONCONTINUESEARCH来捕获异常一旦捕获程序就恢复。怎么恢复仍然由过滤表达式的值决定:zEXCEPTIONCONTINUEEXECUTION:表现为恢复异常。从发生异常处下面开始执行。异常处理函数本身的代码不执行。zEXCEPTIONEXECUTEHANDLER:表现为终止异常。从异常发生处开始退栈一路上所遇到终止函数都被执行。栈退到捕获异常的处理函数所在的一级为止。进入处理函数体并执行。如名所示终止处理函数(以finally开始的代码)在终止异常时被调用。里面是cleanup代码它们就象C标准库中的atexit()函数和C的析构函数。终止处理函数在正常执行流程也会进入就象不是捕获型代码。相反异常处理函数总表现为捕获型:它们只在其过滤表达式求值为EXCEPTIONEXECUTEHANDLER时才进入。终止处理函数并不明确知道自己是从正常流程进入的还是在一个try块异常终止时进入的。要判断这点可以调用AbnormalTermination函数。此函数返回一个int表明是从正常流程进入的其它值表明在异常终止时进入的。AbnormalTermination实际上是个指向abnormaltermination()的宏。VisualC将abnormaltermination()设计为环境敏感的函数就象一个关键字。你不能随便调用这个函数只能在终止处理函数中调用。这意味着你不能在终止处理函数中调用一个中间函数再在此中间函数中调用abnormaltermination()这样做会得到一个编译期错误。例程下面的C例子显示了不同的过滤表达式值和处理函数本身类型的相互作用。第一个版本是个小的完整程序以后的版本都在它前面一个上有小小的改动。所有的版本都自解释的你能看清流程和行为。程序通过RaiseException()触发一个异常对象。RaiseException()函数的第一个参数是异常的代码类型是位无符号整型(DWORD)Microsoft为用户自定义的错误保留了xE,xEFFFFFFF的范围。其它参数一般填。这里使用的异常过滤器很简单。实际使用中大概要调用GetExceptionCode()和GetExceptionInformation()来查询异常对象的属性。Version#:TerminatingException用VisualC生成一个空的Win控制台程序命名为SEHtest选项为默认。将下列C源码加入工程文件:#include<stdioh>#include"windowsh"#definefilter(level,status)(printf("s:*sfilter=>sn",#level,(int)(*(level)),"",#status),(status))#defineterminationtrace(level)printf("s:*shandlingsnormalterminationn",#level,(int)(*(level)),"",AbnormalTermination()"ab":"")staticvoidtrace(intlevel,charconst*message){printf("d:*ssn",level,*level,"",message)}externintmain(void){DWORDconstcode=xEtrace(,"beforefirsttry")try{trace(,"try")try{trace(,"try")try{trace(,"try")try

用户评论(0)

0/200

精彩专题

上传我的资料

每篇奖励 +2积分

资料评价:

/25
仅支持在线阅读

意见
反馈

立即扫码关注

爱问共享资料微信公众号

返回
顶部