windows终止处理程序( __try __finally) 简单解析

本文大部分来自《windows核心编程》。

例1

//二话不说,直接上代码
int
Funcenstein2() { __try { return 3; } __finally { //在函数返回之前会处理__finally里的内容 cout<<"finally executed"<<endl; } return 4;//此函数返回3而不是4 }

 

通过使用终止处理程序可以防止过早的执行return语句。当return语句试图退出try块的时候,编译器会让finally代码在它。即编译器保证finally代码块在出try块的时候return之前执行。

者可以想知道,编译器是如何保证此功能的呢?原来当编译器检查程序代码时,会发现try代码里有一个return语句。于是,编译器就会生成一些代码先将返回值(例子中的 3)保存在一个由它创建的一个临时变量里,然后再执行finally语句块。这个过程被称之前为局部展开(LOCAL UNWIND)。更确切的说,当系统因try代码提前退出finally时就会发生局部展开。一旦finally代码块执行完毕,编译器所创建的临时变量值就会返回给函数调用者。

 

由此可见,为了让整个机制运行起来,编译器必须生成一些额外的代码,而系统也要很执行一些额外的工作。在不同的cpu结构上,让终止处理工作起来的步骤也不同。需要注意的是,应该避免在try代码中使用return语句,因为这是对程序性能有害的。__leave关键字,它可以帮助我们发现那些有局部展开开销的代码

 

 例2

int Funcenstein3()
{
    //在try中使用goto语句时,就会产生局部展开以执行finally代码块。
    //这一次当finally执行完之后。因为try和finally中都没有函数返回语句,
    //所以ReturnValue标签后面的代码也会执行。因此这函数返回4。
    //但是由于代码破坏了try块到finally的正常执行流程,可能有比较大的性能损失,其程序取决于cpu体系结构。

    int ret=0;
    __try
    {
        ret=5;
        goto ReturnValue;
    }
    __finally
    {
        cout<<"finally executed"<<endl;
    }
ReturnValue:
    return 4;
}

 

 例3

在这个例子中,终止处理将真正证明它的价值。首先看一下代码

DWORD Funcfurter1()
{
    DWORD dwTemp;

    //1. do any processing here

    __try
    {
        WaitForSingleObject(g_hSem,INFINITE);
        dwTemp=Funcinator(g_dwProtectedData);
    }
    __finally
    {
        ReleaseSemaphore(g_hSem,1,NULL);
    }
    return dwTemp;
}

假设try代码块中Funcinator函数存在一个缺陷会导致程序访问非法的内存。如果没有SEH这种情况下最络导致Windows错误报告(WER)弹出一个对话框:“Application has stopped working”。这个对话框在Windows上经常可以见到。一旦用户取消这个对话框,进程就会终止。但信号量依然被占用并再也得不到释放。其他进程中的纯种就会因为无休止的等待这个信号量而得不到CPU时间片。如果把信号量放在finally之中,即使用try中调用的函数发生了内存访问违规这样的异常,这个信号量也可以被释放。但是,请注意,从Windows Vista开始,须显式地保护try/finally框架,以确保在异常抛出时,finally代码会执行。

 

 例4

现在不防做一个实验,判断一下这个函数的返回值

DWORD FuncalDoodLeDoo()
{
    DWORD dwTemp=0;

    while (dwTemp<10)
    {
        __try
        {
            if(dwTemp==2)
                continue;
            if(dwTemp==3)
                break;
        }
        __finally
        {
            dwTemp++;
        }
        dwTemp++;
    }
    dwTemp+=10;
    return dwTemp;
}

让我们逐步分析这个函数执行的过程:一开始将dwTemp赋值为0,然后try块中的代码开始执行,但是两个if语句都为false。于是程序正常进入到finally代码块,在这里给dwTemp的值上加1,而finally块后面的代码又将dwTemp加1。

 

下一次循环开时,dwTemp=2,第一个if为true,程序试如果没有__finally程序会跳到while条件判断处执行,但dwTemp值班不会改变,这将会是一个死循环。但是现在我们有终止处理程序,系统注意到continue语句将会导致提前跳出try块,于是强制执行finally语句块。 在finally语句块中dwTemp被增加到3.这次finally之后的代码块没有机会执行。因此finally执行完之后程序跳到循环顶部执行。

 

现在我们分析第三次迭代,这次第一个if判断表达式的值为false,第二个表达式的值为true,系统再次侦测到程序流想要提前跳出try块,于是调用finally代码块,这里的dwTemp增加到4.因为break语句的执行程序控制流从whhile循环后开始继续。因而finally块之后循环以内的代码就不会被执行了。而循环之后的代码将dwTemp的值设置为14。这是程序最终返回的结果。不用我指明,请教也不会写出FuncalDoodleDoo这样的代码。此处只是为了演示终止处理程序是如何工作的。‘

 

绝大多数部情况下,try块中的提前退出都会被终止处理程序所捕获,但是在进程或线程提前终止的情况下,系统没法保证finally代码块的执行。调用ExitThread或者ExitProcess可以马上终止纯种或进程,而不会引发finally执行。同样如果当前纯种或者进程因为另一个程序调用TerminateThread或TerminateProcess而不得不结束,finally代码块也不会被执行,有一些c运行期函数比如(abort),因为在其内部最络调用的是ExitProcess,也会导致finally块不能执行。我们没法阻止别的线程杀死我们的线程或进程,但是可以在自己的代码中尽量避免对ExitThread或ExitProces的草率调用。

 

例5

DWORD Funcenstein4()
{
    DWORD dwTemp;
    
    //1. do any processing here    
    __try
    {
        WaitForSingleObject(g_hSem,INFINITE);
        
        g_dwProcectedData=5;
        dwTemp=g_dwProcectedData;
        //return the new value
        return dwTemp; 
    }
    __finally
    {
        ReleaseSemaphore(g_hSem,1,NULL);
        return 103;
    }
    dwTemp = 9;
    return dwTemp;
}

在上面的函数中,try中的代码试图用return 返回给调用者。正如我们前面提到的那样,试图在try块中提前退出函数导致编程器生成一些额外代码,将函数返回结果保存在一个临时变量中,然后执行finally中又多了一个return,那么导致103被写入到编译器生成的临时变量时。从而覆盖了原先的值5。而返回103

 

posted @ 2012-08-01 19:56  瓜蛋  阅读(3411)  评论(0编辑  收藏  举报