结构化异常处理SEH之__finally终止处理

结构化异常处理(Structuredexception handling)简称SEH。是windows系统提供的异常处理机制。促使windows将SEH加入到windows系统的一个关键原因就是:它可以简化操作系统本身的开发工作,同时还让系统更加健壮。

我们当然也可以在我们的程序中添加SEH机制,这样我们的应用程序也可以变得更加健壮。使用SEH,我们在编写代码时可以先集中精力完成软件的正常工作流程。也就是说将软件主要功能编写和软件异常处理这两个任务分离开,最后再去处理软件可能遇到的各种错误情况。

为了实现SEH,编译器完成了很多的工作。在进入和离开异常处理代码时编译器会插入一些额外的代码,有时这会导致很大的开销。后面的文章我会介绍编译器都是做了哪些工作。

虽然不同的厂商按照不同的方式来实现SEH,但是大部分的编译厂商都遵循了microsoft的语法规则。因此本文我们将介绍Microsoft visualC++编译器规定的语法。

SEH包括两个方面的内容:终止处理和异常处理。本文我们介绍终止处理,下一篇文章将介绍异常处理。


终止处理
终止处理程序确保无论一个代码块是如何退出的,都能保证该终止处理程序能够被执行。其语法如下:

__try
{  
   //被保护的代码。  
}  
__finally//终止处理。  
{  
   //终止处理代码。  
} 




注意:try和finally前都有两个下划线。


终止程序也分为两个部分:__try块为中止处理程序要保护的代码。无论程序以何种方式从该块中退出(如return、goto等语句),__finally块都会被执行。

__finally块为终止处理程序块,该块中的代码会在控制流从__try块退出后、程序结束之前被调用。一般用来执行一些清理操作,如释放资源、释放占有的互斥量等。

但是上面所说的”无论何种方式“有些绝对。因为这个世界本来就没有绝对的东西。当在try块中使用了ExitProcess、ExitThread、TerminateProcess、TerminateThread来终止线程或进程时,finally块就不会被调用。要特别注意这些例外,禁止在try块中使用这些函数。

下面通过代码给大家讲解终止处理程序的使用:

__try  
{  
    WaitForSingleObject(hMutex,INFINITE);  
    if(x==false)  
       return-1;  
}  
__finally  
{  
	ReleaseMutex(hMutex);  
	return -2;  
}  
return 0;

可以看到上面的代码在try中首先等待互斥量内核对象被触发,等待成功后互斥量就属于该线程所有。如果此时执行return-1,则该线程结束,就会导致互斥量对象一直被占有的情况的发生,其他线程会一直处于等待状态。这就是所谓的资源泄漏。有了finally块的保护后,当执行return-1程序试图退出try块时,编译器会让finally块在return之前执行,同时在return(其他语句,如goto等语句也一样)语句之前插入一些代码。return的返回值会被保存在一个临时变量中。
但上面的代码程序的返回值是多少呢?是-1,还是-2呢?答案是-2。当编译器在try块中检测到return语句时,虽然会生成一些代码将返回值保存在一个临时变量中。但是由于finally块代码中也有一个return,finally块中return的值会将原来的返回值覆盖。所以函数最终返回-2。
这个过程被称为局部展开。局部展开会在系统因为try块中的代码提前退出时发生。局部展开会导致非常大的额外开销,因为编译器必须插入代码来保证finally块在程序退出之前执行。最理想的情况就是代码控制流正常的离开try块而进入到finally块中,这时的额外开销最小。在x86体系下离开try块正常进入到finally块只需要执行一条指令。

因此为了将性能开销降到最低,我们改进了上面的代码:
__try  
{  
	WaitForSingleObject(hMutex,INFINITE);  
	if(x==false)  
		goto EndOfTryBlock;  
	//一些代码。  
	EndOfTryBlock:  
}  
__finally  
{  
	ReleaseMutex(hMutex);  
	return -2;  
}  
return 0;

在上面改进后的代码中,当在try块中检查到错误发生时不再直接调用return强制退出。而是执行了一条goto语句到try块的末尾。执行此跳转后,控制流从try块中正常退出,直接进入到finally块中。由于没有发生局部展开,编译器不需要插入额外指令,也就没有导致额外的开销。
由于goto语句会破坏程序的执行流程,很多书上都再三强调禁止使用goto。其实我们也没有必要使用goto,因为microsoft提供给我们一个关键字_leave,也可以执行类似的操作。关键字_leave会导致代码执行控制流跳转到try块的末尾,从而代码将正常的从try块进入到finally块中。

下面为使用__leave关键字的改进代码:
__try  
{  
	WaitForSingleObject(hMutex,INFINITE);  
	if(x==false)  
		__leave  
	//一些代码。  
}  
__finally  
{  
	ReleaseMutex(hMutex);  
	return -2;  
}  
return 0;

本章开头曾介绍过使用SEH可以让程序员将程序正常执行流程和错误处理分开。下面我们分别展示两个例子,一个没有使用SEH,而另一个使用SEH,看下它们到底有何差别,通过比较我们也可以更好的知道SEH是如何完成上述工作的。
下面是没有使用SEH的程序:
bool fun(char* fileName)  
{  
      HANDLEhFile=CreateFile(....);  
      if(hFile==INVALID_HANDLE_VALUE)  
      {  
           returnfalse;  
      }  
      HANDLEhFileMapping=CreateFileMapping(...);  
      if(hFile==NULL)  
      {  
           CloseHandle(hFile);  
           returnfalse;  
   
      }  
      char*p=MapViewOfFile(..);  
      if(p==NULL)  
      {  
           CloseHandle(hFile);  
           CloseHandle(hFileMapping);  
            return false;  
      }  
   
      //其他工作.......  
      return true;  
}

相信我们很多人都写过类似上面的代码。我们可以看到,上面的代码中包含了很多错误检查和资源清理的代码。过多的错误代码检查和资源清理工作会使得代码难以阅读,同时也难以编写、修改和维护。
现在让我们来通过使用SEH机制改进上面的代码:
bool fun(char* fileName)  
{  
      HANDLEhFile=INVALID_HANDLE_VALUE;  
      HANDLEhFileMapping=NULL;  
      char*p=NULL;  
      __try  
      {  
           hFile=CreateFile(....);  
           if(hFile==INVALID_HANDLE_VALUE)  
                 __leave;  
            hFileMapping=CreateFileMapping(...);  
           if(hFile==NULL)  
                 __leave;          
           p=MapViewOfFile(..);  
           if(p==NULL)  
                 __leave;  
      //其他代码。  
      }  
      __finally  
      {  
           if(hFile!=INVALID_HANDLE_VALUE)  
                 CloseHandle(hFile);  
           if(hFileMapping)  
                 CloseHandle(hFileMapping);  
   
      }  
      return true;  
}


从上面的代码我们可以看到代码简洁多了。清理工作放在最后执行,且能够保证能得到执行,代码看起来简洁而有序,提高了可读性也有利于以后的维护。
注意事项:前面我们介绍了两种会引起finally块执行的情形:

一:从try块正常退出,进入到finally块。
二:局部展开:从try块中提前退出,将程序控制流强制转到finally块。

除了上面的情况外,还有一种情况:全局展开,也会导致finally块被执行。关于全局展开由于涉及到异常处理的内容,我们将在下一篇文章中详细介绍!
finally的执行都是由上面三种情况之一造成的。

如果要确定到底正常进入finally还是异常退出try块退出,可以调用AbnormalTermination()函数来判断:
注意只能在finally块中调用该函数。当返回true时,表示控制流从try块中正常进入到finally块中。返回false时,则表示控制流从try块中异常退出,通常是由于执行goto、break、return或continue语句而导致局部展开,或是try块中的代码抛出异常而引起全局展开所致。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值