全部博文(668)
分类:
2008-08-07 09:04:59
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
况下,一旦执行流在离开当前的作用域时,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种情况,也即由于出现异常而导致的“全局展开”,对于
#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
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机制结合起来,进行一个全面而综合的评述,继续吧!