[转]C++异常处理 9

*lpAddress = ''''A''''; // <- Here, Will Happen a Access Exception!

VirtualFree((LPVOID)lpAddress, 1024, MEM_RELEASE);
return dwReturnValue;
}

DWORD Func_SEHTerminate()
{
DWORD dwReturnData = 0;
HANDLE hSem = NULL;
const char* lpSemName = "TermSem";
hSem = CreateSemaphore(NULL, 1, 1, lpSemName);

__try
{
WaitForSingleObject(hSem,INFINITE);
dwReturnData = TermHappenSomeError();
}
__finally
{
ReleaseSemaphore(hSem,1,NULL);
CloseHandle(hSem);
}
return dwReturnData;
}
在上面的代码中,我们看到TermHappenSomeError()函数包含一个错误(对没有Commit的页进行提交),会引起一个无效内存访问。如果没有S E H,在这种情况下,将会给用户显示一个很常见的Application Error对话框。当用户忽略这个错误对话框,该进程就结束了。当这个进程结束(由于一个无效内存访问),信标仍将被占用并且永远不会被释放,这时候,任何等待信标的其他进程中的线程将不会被分配C P U时间。但若将对R e l e a s e S e m a p h o r e的调用放在f i n a l l y块中,就可以保证信标获得释放,即使某些其他函数会引起内存访问错误。

如果结束处理程序足够强,能够捕捉由于无效内存访问而结束的进程,我们就可以相信它也能够捕捉s e t j u m p和l o n g j u m p的结合,还有那些简单语句如b r e a k和c o n t i n u e。
=======================
下面是一个非常有挑战性的代码,但是这段代码在现实编程中,我相信没有程序员会写这么无聊的代码段。
代码:(返回值:14)
DWORD Func_SEHTerminateHard()
{
DWORD dwTemp = 0;
while(dwTemp < 10)
{
__try
{
if(dwTemp == 2)
continue;
if(dwTemp == 3)
break;
}
__finally
{
dwTemp++;
}
dwTemp++;
}
dwTemp += 10;

return dwTemp;
}
我们来一步一步地分析函数做了什么。首先d w Te m p被设置成0。t r y块中的代码执行,但两个i f语句的值都不为T R U E。执行自然移到f i n a l l y块中的代码,在这里d w Te m p增加到1。然后f i n a l l y块之后的指令又增加d w Te m p,使它的值成为2,当循环继续,d w Te m p为2,t r y块中的c o n t i n u e语句将被执行。如果没有结束处理程序在从t r y块中退出之前强制执行f i n a l l y块,执行就立即跳回w h i l e测试,d w Te m p不会被改变,将出现无限(死)循环。利用一个结束处理程序,系统知道c o n t i n u e语句要引起控制流过早退出t r y块,而将执行移到f i n a l l y块。在f i n a l l y块中,d w Te m p被增加到3。但f i n a l l y块之后的代码不执行,因为控制流又返回到c o n t i n u e,再到循环的开头。现在我们处理循环的第三次重复。这一次,第一个i f语句的值是FA L S E,但第二个语句的值是T R U E。系统又能够捕捉要跳出t r y块的企图,并先执行f i n a l l y块中的代码。现在d w Te m p增加到4。由于b r e a k语句被执行,循环之后程序部分的控制恢复。这样, f i n a l l y块之后的循环中的代码没有执行。循环下面的代码对d w Te m p增加1 0,这时d w Te m p的值是1 4,这就是调用这个函数的结果。

尽管结束处理程序可以捕捉t r y块过早退出的大多数情况,但当线程或进程被结束时,它不能引起f i n a l l y块中的代码执行。当调用E x i t T h r e a d或E x i t P r o c e s s时,将立即结束线程或进程,而不会执行f i n a l l y 块中的任何代码。另外,如果由于某个程序调用Te r m i n a t e T h r e a d或Te r m i n a t e P r o c e s s,线程或进程将死掉, f i n a l l y块中的代码也不执行。某些C运行期函数(例如a b o r t)要调用E x i t P r o c e s s,也使f i n a l l y块中的代码不能执行。虽然没有办法阻止其他程序结束你的一个线程或进程,但你可以阻止你自己过早调用E x i t T h r e a d和E x i t P r o c e s s。

========================
回到我们的基本函数,我们对它进行修改,接下来的代码是:(返回值:100)
DWORD Func_SEHTerminateMReturn()
{
DWORD dwReturnData = 0;
HANDLE hSem = NULL;
const char* lpSemName = "TermSem";
hSem = CreateSemaphore(NULL, 1, 1, lpSemName);

__try
{
WaitForSingleObject(hSem,INFINITE);
dwReturnData = 5;
return dwReturnData;
}
__finally
{
ReleaseSemaphore(hSem,1,NULL);
CloseHandle(hSem);
return (DWORD)100;
}

dwReturnData = 10;
return dwReturnData;
}
t r y块将要执行,并试图将dwReturnData的值(5)返回给这个函数的调用者。前面已经说过,试图从t r y块中过早的返回将导致产生代码,把返回值置于由编译程序建立的临时变量中。然后, f i n a l l y块中的代码被执行。在这里, f i n a l l y块中增加了一个r e t u r n语句。那么返回5还是1 0 0?这里的答案是1 0 0,因f i n a l l y块中的r e t u r n语句引起值1 0 0存储在值5所存储的临时变量中,覆盖了值5。当f i n a l l y块完成执行,现在临时变量中的值( 1 0 0)从函数返回给调用程序。

我们已经看到结束处理程序在补救t r y块中的过早退出的执行方面很有效,但也看到结束处理程序由于要阻止t r y块的过早退出而产生了我们不希望有的结果。更好的办法是在结束处理程序的t r y块中避免任何会引起过早退出的语句。实际上,最好将r e t u r n、c o n t i n u e、b r e a k、g o t o等语句从结束处理程序的t r y块和f i n a l l y块中移出去,放在结束处理程序的外面。这样做会使编译程序产生较小的代码,因为不需要再捕捉t r y块中的过早退出,也使编译程序产生更快的代码(因为执行局部展开的指令也少)。另外,代码也更容易阅读和维护

应用结束处理程序编程
我们已经谈过了结束处理程序的基本语法和语意。现在看一看如何用结束处理程序来简化
一个更复杂的编程问题。先看一个完全没有利用结束处理程序的函数:
(唉~~~~~~~~~~~~各位大哥,我实在吃不消打了,所以代码我就不paste上来了,在这里我只说说注意点和编程规范。)

在编程中,有很多人往往喜欢用错误判断,这样造成的后果是代码的可读性很差,代码难以维护,所以我们使用结束处理程序的真正好处是函数的所有清理( c l e a n u p)代码都局部化在一个地方且只在一个地方: f i n a l l y块。如果需要在这个函数中再增加条件代码,只需在f i n a l l y块中简单地增加一个清理行,不需要回到每个可能失败的地方添加清理代码

但是我们已经发现和证实了,如果我们在__try块中使用到了return等跳转语句的时候,系统开销这个问题就产生了。为了帮助避免在t r y块中使用r e t u r n语句,微软在其C / C + +编译程序中增加了另一个关键字__l e a v e。(自己看MSDN把)

在t r y块中使用 l e a v e关键字会引起跳转到t r y块的结尾。可以认为是跳转到t r y块的右大括号。由于控制流自然地从t r y块中退出并进入f i n a l l y块,所以不产生系统开销。当按照这种方式利用结束处理程序来设计函数时,要记住在进入t r y块之前,要将所有资源句柄初始化为无效的值。然后,在f i n a l l y块中,查看哪些资源被成功的分配,就可以知道哪些要释放。另外一种确定需要释放资源的办法是对成功分配的资源设置一个标志。然后, f i n a l l y块中的代码可以检查标志的状态,来确定资源是否要释放。

你可能感兴趣的:(异常处理)