第23章终止处理程序
一、SHE(结构化异常处理)好处
二、SHE是什么
三、SHE怎么用
四、SHE工作原理
一、先了解SHE的好处:可让我们在写代码时,先集中精力完成软件的正常工作流程。若在运行时出现什么问题,系统会捕获这个问题,并通知我们。使用SHE并不意味着可完全忽略代码中可能出现的错误,但可将软件注意功能编写和软件异常情况处理这两个任务分开。
二、SHE包括两方面的功能:终止处理和异常处理。本章讨论终止处理,下章讨论异常处理。区分SHE和C++异常处理:C++异常处理在形式上表现为使用关键字catch和throw,这个结构化异常处理的形式不同。Microsoft Visual C++支持异常处理,它再内部实现上其实就是利用了编译器和Windows操作系统的结构化异常处理功能。
三、终止处理程序确保不管一个代码块(被保护代码(gaurded body))是如何退出的,另一个代码块(终止处理程序(termination handler))总能被调用和执行。终止处理的语法如下(当使用Microsoft Visual C++编译器时):
_try
{//gaurded body}
_finally
{//termination handler}//除非调用ExitProcess、ExitThread、TerminateProcess、TerminatedThread来终止进程或线程,否则_finally代码块都能执行。
实例讲解:
1、Funcenstein2函数:
DWORD Funcensterin2()
{
DWORD dwTemp;
//1.do any processing here
_try
{
//2.request permission to access protected data, and then use it.
WaitForSingleObject(g_hSem, INFINITE);
g_dwProcetedData = 5;
dwTemp = g_dwProcetedData;
//return the new value;
return(dwTemp);
}
_finally
{
//3.allow others to use protected data.
ReleaseSemphore(g_hSem, 1, NULL);
}
//continue processing--this code will never execute in this version.
dwTemp = 9;
return(dwTemp);
}
通过使用终止处理程序可防止过早的执行return语句。当try块中有return语句时,试图退出try块时,编译器会让finally代码块在它之前执行。此函数可保证在函数退出前释放信号量。
原理:编译器检查程序代码时,当发现try块中有return语句,就会生成一些代码先将返回值保存在它创建的临时变量里,然后再执行finally块,此过程称之为局部展开。即当系统因try代码块中代码提取退出而执行finally代码块时,就会发生局部展开。
缺点:为使此机制运行,编译器必须生成一些额外代码,而系统也必须执行一些额外工作,所有应避免在try块中使用让其提前退出的语句,如return、continue、break、goto等。
2、Funcfurter1函数:
DWORD Funcfurter1()
{
DWORD dwTemp;
//1.do any processing here.
_try
{
//2.request permission to access protected data, and then use it.
WaitForSingleObject(g_hSem, INFINITE);
dwTemp = Funcinator(g_dwProtectedData);
}
_finally
{
//3.allow others to use protected data.
ReleaseSemaphore(g_hSem, 1, NULL);
}
//4.continue processing.
return(dwTemp);
}
若代码块中Funcinator函数存在一个缺陷会导致程序访问非法内存。若无SHE,这种情况最终导致Windows错误报告(Windows Error Reporting,简称WER)。这样进程就会终止(因非法内存访问),但信号量将依然被占用并再也得不到释放。其他进程中线程就会因无休止等待这个信号量而得不到CPU时间片。若将释放信号量的语句置于finally块中,即使try块中调用的函数发生了内存访问违规这样的异常,这个信号量仍可被释放。
但自Windows Vista开始,须显示包含try/finally框架,以确保在异常跑出时,finally代码块会执行。早起的windows系统里,异常发生时,finally块也不能保证绝对得到执行。如在XP里,若一个”栈耗尽异常”发生在try代码块里,finally块很可能得不到机会执行。
调用ExitThread或ExitProcess可立即终止线程或进程,而不会引发finally代码块执行。同样,若当前线程或进程因另一个程序调用TerminateThread或TerminateProcess而不得不结束,finally代码块也不会被执行。有些C运行期函数(如abort)因其内部调用ExitProcess,也会导致finally块不能执行。
3、Funcarama3函数:
DWORD Funcarama3()
{
//IMPORTANT:initialize all variables to assume failure.
HANDLE hFile = INVALID_HANDLE_VALUE;
PVOID pvBuf = NULL;
_try
{
DWORD dwNumBytesRead;
BOOL bOk;
hFile = CreateFile(_T("SOMEDATA.DATA"), GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (INVALID_HANDLE_VALUE == hFile)
{
return false;
}
pvBuf = VirtualAlloc(NULL, 1024, MEM_COMMIT, PAGE_EADWRITE);
if(NULL == pvBuf)
{
return false;
}
bOk = ReadFile(hFile, pvBuf, 1024, &dwNumBytesRead, NULL);
if (!bOk || (dwNumBytesRead != 1024))
{
return false;
}
//do some calculation on the data.
}
_finally
{
//clean up all the resources.
if (pvBuf != NULL)
ViutualFree(pvBuf, MEM_RELEASE | MEM_DECOMMIT);
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
}
//continue processing
return true;
}
精髓在于所有的清理工作都被放在并且只放在一个地方:finally块。否则,每个判断出错中都要处理这些清理工作。
4、Funcarama4终结版:
DWORD Funcarama4()
{
//IMPORTANT:initialize all variables to assume failure.
HANDLE hFile = INVALID_HANDLE_VALUE;
PVOID pvBuf = NULL;
//注意函数不能成功执行
BOOL bFunctionOK = FALSE;
_try
{
DWORD dwNumBytesRead;
BOOL bOk;
hFile = CreateFile(_T("SOMEDATA.DATA"), GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (INVALID_HANDLE_VALUE == hFile)
{
_leave;
}
pvBuf = VirtualAlloc(NULL, 1024, MEM_COMMIT, PAGE_EADWRITE);
if(NULL == pvBuf)
{
_leave;
}
bOk = ReadFile(hFile, pvBuf, 1024, &dwNumBytesRead, NULL);
if (!bOk || (dwNumBytesRead != 1024))
{
_leave;
}
//do some calculation on the data.
//表明整个函数执行成功
bFunctionOK = TRUE;
}
_finally
{
//clean up all the resources.
if (pvBuf != NULL)
ViutualFree(pvBuf, MEM_RELEASE | MEM_DECOMMIT);
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
}
//continue processing
return bFunctionOK;
}
关键字_leave会导致代码执行块跳转到try块的结尾(即闭花括号处)。因这种情况,代码执行将正常从try进入finally,所以不会产生额外开销。
此种方式在函数里使用终止处理程序时,最好在进入try块之前,将所有资源句柄都初始化为无效值。这样可在finally块里检查哪些资源得到了成功分配,从而得知哪些资源需要释放。另一个检查哪些资源需要释放的常见做法是,为成功分配的资源设置标志,然后在finally块里检查这些标志以决定资源是否需要释放。
四、上面的实例讲的差不多了,补充两点:1.引起finally块执行的情形。
1)从try块到finally的正常代码控制流。
2)局部展开:从try块中的提前退出(return、break、continue、got、longjump等引起)将程序控制流强制转入finally块。
3)全局展开。
2.使用终止处理的程序的好处:
Ø 清理工作集中在一个地方执行,且保证能得到执行,从而简化了错误处理。
Ø 提供代码的可读性。
Ø 让代码更容易维护。
Ø 若正确使用,对程序性能和体积的影响是微小的。