Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2287289
  • 博文数量: 668
  • 博客积分: 10016
  • 博客等级: 上将
  • 技术积分: 8588
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-29 19:22
文章分类

全部博文(668)

文章存档

2011年(1)

2010年(2)

2009年(273)

2008年(392)

分类:

2008-08-07 09:04:59

上一篇文章讲述了SEH的异常处理机制,也即try-except模型的使用规则。本篇文章继续探讨SEH另外一项很重要的机制,那就是“有效保证的清除”,其实这才是SEH设计上最为精华的一个东东,对于C程序而言,它贡献简直是太大了。

  SEH的这项机制被称为结束处理(Termination Handling),它是通过try-finally语句来实现的,下面开始讨论吧!

  try-finally的作用

  对于try-finally的作用,还是先看看MSDN中怎么说的吧!摘略如下:

  The try-finally statement is a extension to the C and languages that enables 32-bit target applications to guarantee execution of cleanup code when execution of a block of code is interrupted. Cleanup consists of such tasks as deallocating memory, closing files, and releasing file handles. The try-finally statement is especially useful for routines that have several places where a check is made for an error that could cause premature return from the routine.

  上面的这段话的内容翻译如下:

  try-finally语句是Microsoft对C和C++语言的扩展,它能使32位的目标程序在异常出现时,有效保证一些资源能够被及时清除,这些资源的清除任务可以包括例如内存的释放,文件的关闭,文件句柄的释放等等。try-finally语句特别适合这样的情况下使用,例如一个例程(函数)中,有几个地方需要检测一个错误,并且在错误出现时,函数可能提前返回。

  try-finally的语法规则

  上面描述try-finally机制的有关作用时,也许一时我们还难以全面理解,不过没关系,这里还是先看一下try-finally的语法规则吧!其实它很简单,示例代码如下:

  //seh-test.c

  #include

  #include

  void main()

  {

  puts("hello");

  __try

  {

  puts("__try块中");

  }

  // 注意,这里不是__except块,而是__finally取代

  __finally

  {

  puts("__finally块中");

  }

  puts("world");

  }

  上面的程序运行结果如下:

  hello

  __try块中

  __finally块中

  world

  Press any key to continue

  try-finally语句的语法与try-except很类似,稍有不同的是,__finally后面没有一个表达式,这是因为try-finally语句的作用不是用于异常处理,所以它不需要一个表达式来判断当前异常错误的种类。另外,与try-except语句类似,try-finally也可以是多层嵌套的,并且一个函数内可以有多个try-finally语句,不管它是嵌套的,或是平行的。当然,try-finally多层嵌套也可以是跨函数的。这里不一一列出示例,大家可以自己测试一番。

  另外,对于上面示例程序的运行结果,是不是觉得有点意料之外呢?因为__finally块中的put(“__finally块中”)语句也被执行了。是的,没错!这就是try-finally语句最具有魔幻能力的地方,即“不管在何种情况下,在离开当前的作用域时,finally块区域内的代码都将会被执行到”。呵呵!这的确是很厉害吧!为了验证这条规则,下面来看一个更典型示例,代码如下:

  #include

  void main()

  {

  puts("hello");

  __try

  {

  puts("__try块中");

  // 注意,下面return语句直接让函数返回了

  return;

  }

  __finally

  {

  puts("__finally块中");

  }

  puts("world");

  }

  上面的程序运行结果如下:

  hello

  __try块中

  __finally块中

  Press any key to continue

  上面的程序运行结果是不是有点意思。在__try块区域中,有一条return语句让函数直接返回了,所以后面的put(“world”)语句没有被执行到,这是很容易被理解的。但是请注意,__finally块区域中的代码也将会被予以执行过了,这是不是进一步验证了上面了那条规则,呵呵!阿愚深有感触的想:“__finally的特性真的很像对象的析构函数”,朋友们觉得如何呢?

  另外,大家也许还特别关心的是,goto语句是不是有可能破坏上面这条规则呢?因为在中,goto语句一般直接对应一条jmp跳转指令,所以如果真的如此的话,那么goto语句很容易破坏上面这条规则。还是看一个具体的例子吧!

  #include

  void main()

  {

  puts("hello");

  __try

  {

  puts("__try块中");

  // 跳转指令

  goto RETURN;

  }

  __finally

  {

  puts("__finally块中");

  }

  RETURN:

  puts("world");

  }

  上面的程序运行结果如下:

  hello

  __try块中

  __finally块中

  world

  Press any key to continue

  呵呵!即便上面的示例程序中,goto语句跳过了__finally块,但是__finally块区域中的代码还是被予以执行了。当然,大家也许很关心这到底是为什么?为什么try-finally语句具有如此神奇的功能?这里不打算深入阐述,在后面阐述SEH实现的时候会详细分析到。这里朋友们只牢记一点,“不管是顺序的线性执行,还是return语句或goto语句无条件跳转等情

  况下,一旦执行流在离开当前的作用域时,finally块区域内的代码必将会被执行”

  try-finally块中的异常

  上面只列举了return语句和goto语句的情况下,但是如果程序中出现异常的话,那么finally块区域内的代码还会被执行吗?上面所讲到的那条规则仍然正确吗?还是看看示例,代码如下:

  #include

  void test()

  {

  puts("hello");

  __try

  {

  int* p;

  puts("__try块中");

  // 下面抛出一个异常

  p = 0;

  *p = 25;

  }

  __finally

  {

  // 这里会被执行吗

  puts("__finally块中");

  }

  puts("world");

  }

  void main()

  {

  __try

  {

  test();

  }

  __except(1)

  {

  puts("__except块中");

  }

  }

  上面的程序运行结果如下:

  hello

  __try块中

  __finally块中

  __except块中

  Press any key to continue

  从上面示例程序的运行结果来看,它是和“不管在何种情况下,在离开当前的作用域时,finally块区域内的代码都将会被执行到”这条规则相一致的。

  __leave关键字的作用

  其实,总结上面的__finally块被执行的流程时,无外乎三种情况。第一种就是顺序执行到__finally块区域内的代码,这种情况很简单,容易理解;第二种就是goto语句或return语句引发的程序控制流离开当前__try块作用域时,系统自动完成对__finally块代码的调用;第三种就是由于在__try块中出现异常时,导致程序控制流离开当前__try块作用域,这种情况下也是由系统自动完成对__finally块的调用。无论是第2种,还是第3种情况,毫无疑问,它们都会引起很大的系统开销,编译器在编译此类程序代码时,它会为这两种情况准备很多的额外代码。一般第2种情况,被称为“局部展开(LocalUnwinding)”;第3种情况,被称为“全局展开(GlobalUnwinding)”。在后面阐述SEH实现的时候会详细分析到这一点。

  第3种情况,也即由于出现异常而导致的“全局展开”,对于而言,这也许是无法避免的,因为你在利用异常处理机制提高程序可靠健壮性的同时,不可避免的会引起性能上其它的一些开销。呵呵!这世界其实也算瞒公平的,有得必有失。

  但是,对于第2种情况,程序员完全可以有效地避免它,避免“局部展开”引起的不必要的额外开销。实际这也是与结构化程序设计思想相一致的,也即一个程序模块应该只有一个入口和一个出口,程序模块内尽量避免使用goto语句等。但是,话虽如此,有时为了提高程序的可读性,程序员在编写代码时,有时可能不得不采用一些与结构化程序设计思想相悖的做法,例如,在一个函数中,可能有多处的return语句。针对这种情况,SEH提供了一种非常有效的折衷方案,那就是__leave关键字所起的作用,它既具有像goto语句和return语句那样类似的作用(由于检测到某个程序运行中的错误,需要马上离开当前的__try块作用域),但是又避免了“局部展开” 的额外开销。还是看个例子吧!代码如下:

  #include

  void test()

  {

  puts("hello");

  __try

  {

  int* p;

  puts("__try块中");

  // 直接跳出当前的__try作用域

  __leave;

  p = 0;

  *p = 25;

  }

  __finally

  {

  // 这里会被执行吗?当然

  puts("__finally块中");

  }

  puts("world");

  }

  void main()

  {

  __try

  {

  test();

  }

  __except(1)

  {

  puts("__except块中");

  }

  }

  上面的程序运行结果如下:

  hello

  __try块中

  __finally块中

  world

  Press any key to continue

  这就是__leave关键字的作用,也许大家在编程时很少使用它。但是请注意,如果你的程序中,尤其在那些业务特别复杂的函数模块中,既采用了SEH机制来保证程序的可靠性,同时代码中又拥有大量的goto语句和return语句的话,那么你的源代码编译出来的二进制程序将是十分糟糕的,不仅十分庞大,而且效率也受很大影响。此时,建议不妨多用__leave关键字来提高程序的性能。

  try-finally深入

  现在,相信我们已经对try-finally机制有了非常全面的了解,为了更进一步认识try-finally机制的好处(当然,主人公阿愚认为,那些写过下设备驱动程序的朋友一定深刻认识到try-finally机制的重要性),这里给出一个具体的例子。还记得,在《第21集 Windows系列平台中所提供的异常处理机制》中,所讲述到的采用setjmp和longjmp异常处理机制实现的那个简单例程吗?现在如果有了try-finally机制,将能够很容易地来避免内存资源的泄漏,而且还极大地提高了程序模块的可读性,减少程序员由于不小心造成的程序bug等隐患。采用SEH重新实现的代码如下:

  #include

  #include

  void test1()

  {

  char* p1, *p2, *p3, *p4;

  __try

  {

  p1 = malloc(10);

  p2 = malloc(10);

  p3 = malloc(10);

  p4 = malloc(10);

  // do other job

  // 期间可能抛出异常

  }

  __finally

  {

  // 这里保证所有资源被及时释放

  if(p1) free(p1);

  if(p2) free(p2);

  if(p3) free(p3);

  if(p4) free(p4);

  }

  }

  void test()

  {

  char* p;

  __try

  {

  p = malloc(10);

  // do other job

  // 期间可能抛出异常

  test1();

  // do other job

  }

  __finally

  {

  // 这里保证资源被释放

  if(p) free(p);

  }

  }

  void main( void )

  {

  __try

  {

  char* p;

  __try

  {

  p = malloc(10);

  // do other job

  // 期间可能抛出异常

  test();

  // do other job

  }

  __finally

  {

  // 这里保证资源被释放

  if(p) free(p);

  }

  }

  __except(1)

  {

  printf("捕获到一个异常\n");

  }

  }

  呵呵!上面的代码与采用setjmp和longjmp机制实现的代码相比,是不是更简洁,更美观。这就是try-finally语句的贡献所在。

  总结

  (1) “不管在何种情况下,在离开当前的作用域时,finally块区域内的代码都将会被执行到”,这是核心法则。

  (2) try-finally语句的作用相当于面向对象中的析构函数。

  (3) goto语句和return语句,在其它少数情况下,break语句以及continue语句等,它们都可能会导致程序的控制流非正常顺序地离开__try作用域,此时会发生SEH的“局部展开”。记住,“局部展开”会带来较大的开销,因此,程序员应该尽可能采用__leave关键字来减少一些不必要的额外开销。

  通过这几篇文章中对SEH异常处理机制的深入阐述,相信大家已经能够非常熟悉使用SEH来进行编程了。下一篇文章把try-except和try-finally机制结合起来,进行一个全面而综合的评述,继续吧!

阅读(900) | 评论(0) | 转发(0) |
0

上一篇:更进一步认识SEH

下一篇:SEH的综合

给主人留下些什么吧!~~