第24章 异常处理程序与软件异常
本章讲解SHE结构的另一个方面的功能。分三个部分:一、异常分类。二、异常处理程序结构。三、与终止处理程序的区别。
一、异常分类:硬件异常和软件异常。
二、当一个硬件或软件异常被抛出时,OS会给我们的应用程序一个查看异常类型的机会,并允许应用程序自己处理这个异常。异常处理程序的语法结构:
__try
{//guarded body}
__except(exception filter(异常过滤程序))
{//exception handler(异常处理程序)}
try块后必须且仅能跟一个except块或finally块,不能同时跟俩,多个finally块或except块也不行。但却可以将try-finally块嵌套与try-except块中,反过来也可以。
当抛出异常时,系统定位到except块的开始处,并对异常过滤程序的表达式求值,这个表达式的值必定为以下三个标识符之一:EXCEPTION_EXECUTE_HANDLER、EXCEPTION_EXECUTE_SEARCH、EXCEPTION_EXECUTE_EXECUTION。
OK,这两个图是从书上截取的。我想说的是,这两个图是错误的。有兴趣的试试这段代码:
void Func1() { __try { int j = 0; int i = 5 / j; Func2(); } __except(EXCEPTION_EXECUTE_HANDLER) { AfxMessageBox("Func1!\n"); } } void Func2() { int dwTemp = 0; __try { int i = 5; } __finally { AfxMessageBox("Func2!\n"); } AfxMessageBox("Func3!\n"); }
现在我自己画正确的图:
发生EXCEPTION_CONTINUE_EXECUTION异常时,代码继续执行,所以最好避免使用此类型的异常。
全局展开,我口述下吧:
情形1:try-except块外面包含着try-except块:若异常发生在外层try-except块,则内层try-except块不执行;若异常发生在内层的try-except块,则执行完内层后接着下一行执行代码。
情形2:try-finally块外面包含着try-except块,且异常发生在try-except块:当异常发生时,若try-finally块还未执行,则仅执行except块。其实类似情况1。
情形3(也是最复杂的情况):try-finally块外面包含着try-except块,且异常发生在try-finally块。执行顺序:先执行try-finally块的finally,然后再执行try-except的except块。若异常发生在try-except块,则finally块就不执行。
注意:自Windows Vista开始,若一个异常发生在try/finally块,而其外层有没有try/except块(同时过滤程序返回EXCEPTION_CONTINUE_HANDLER),进程就立刻终止,不再全局展开也不执行finally块。
试试下面的,看能算出正确的执行顺序吗?答案见代码后。
void Func1() { //1.do any processing here. __try { //2.call another function. Func2(); } __except(/*6.Evaluate filter*/EXCEPTION_EXECUTE_HANDLE) { //8.After the unwind, the exception handler executes. } //9.exception handled---continue execution. } void Func2() { DWORD dwTemp = 0; //3.do any processing here. __try { //4.request permission to access protected data. WaitForSingleObject(g_hSem, INFINITE); //5.modify the data //an exception is generated here. g_dwProtectedData = 5 / dwTemp; } __finally { //7.global unwind occurs because filter evaluated to EXCETPTION_EXECUTE_HANDLER. //allow others to use protected data. ReleaseSemaphore(g_hSem, 1, NULL); } //continue processing--never executes. }
答案:上面的数字表明了执行顺序。
GetExceptionCode是内在函数,它的返回值表明刚刚发生的异常的类型。此函数只能在异常过滤程序里(即__except之后的括号里)或异常处理程序的代码里调用。
逗号操作符:从左到右执行逗号分隔的表达式,当所有表达式都求值完毕时,返回最后一个表达式的值。
软件异常:我们可在程序里强制抛出一个异常,而非由CPU捕获某个事件抛出的异常。传统的做法是:函数通过返回一些错误代码来指明运行失败。函数的调用者应检查这些错误代码并采取相应的措施。这会导致调用者需要频繁的做清理工作并返回给它自己的调用者一个失败代码。错误代码的逐层传播导致代码很难编写和维护。
另一种方法是我们可让函数在失败时抛出异常,而不是返回错误代码。这种方式,代码可易编写和维护。且因省略了很多的错误检查代码,程序执行效率更高。RaiseException可抛出一个异常。
软件异常的捕获方式与硬件异常完全一样。参考P660。
三、与终止处理程序区别:①异常过滤程序(exception filter)和异常处理程序(exception handler)主要由OS来负责执行-----在异常过滤程序表达式计算和异常处理程序执行方面,编译器所做的工作十分有限。②还记得在终止处理程序try块中建议不用return,continue,break等语句,但在异常处理程序的try块中,这些语句不会导致程序性能损失或增加代码量。