大龙的博客

常用链接

统计

最新评论

SEH的强大功能之二(转)

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

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

try-finally的作用


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

The try-finally statement is a Microsoft extension to the C and C++ 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 <windows.h>
#include <stdio.h>

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 <stdio.h>

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语句是不是有可能破坏上面这条规则呢?因为在C语言中,goto语句一般直接对应一条jmp跳转指令,所以如果真的如此的话,那么goto语句很容易破坏上面这条规则。还是看一个具体的例子吧!

#include <stdio.h>

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 <stdio.h>

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 <stdio.h>

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

#include <stdio.h>
#include <stdlib.h>

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机制结合起来,进行一个全面而综合的评述,继续吧!

posted on 2008-01-25 19:29 大龙 阅读(464) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理