第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.使用终止处理的程序的好处:
Ø 清理工作集中在一个地方执行,且保证能得到执行,从而简化了错误处理。
Ø 提供代码的可读性。
Ø 让代码更容易维护。
Ø 若正确使用,对程序性能和体积的影响是微小的。