看流星社区

 找回密码
 注册账号
查看: 2157|回复: 0

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

[复制链接]

该用户从未签到

发表于 2013-3-21 08:37:05 | 显示全部楼层 |阅读模式
本文大部分来自《windows核心编程》。
例1
  1. //二话不说,直接上代码
  2. int Funcenstein2()
  3. {
  4.     __try
  5.     {
  6.         return 3;
  7.     }
  8.     __finally
  9.     {
  10.         //在函数返回之前会处理__finally里的内容
  11.         cout<<"finally executed"<<endl;
  12.     }
  13.     return 4;//此函数返回3而不是4
  14. }
复制代码




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

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

例2

  1. int Funcenstein3()
  2. {
  3.     //在try中使用goto语句时,就会产生局部展开以执行finally代码块。
  4.     //这一次当finally执行完之后。因为try和finally中都没有函数返回语句,
  5.     //所以ReturnValue标签后面的代码也会执行。因此这函数返回4。
  6.     //但是由于代码破坏了try块到finally的正常执行流程,可能有比较大的性能损失,其程序取决于cpu体系结构。
  7.     int ret=0;
  8.     __try
  9.     {
  10.         ret=5;
  11.         goto ReturnValue;
  12.     }
  13.     __finally
  14.     {
  15.         cout<<"finally executed"<<endl;
  16.     }
  17. ReturnValue:
  18.     return 4;
  19. }
复制代码



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

  1. DWORD Funcfurter1()
  2. {
  3.     DWORD dwTemp;
  4.     //1. do any processing here
  5.     __try
  6.     {
  7.         WaitForSingleObject(g_hSem,INFINITE);
  8.         dwTemp=Funcinator(g_dwProtectedData);
  9.     }
  10.     __finally
  11.     {
  12.         ReleaseSemaphore(g_hSem,1,NULL);
  13.     }
  14.     return dwTemp;
  15. }
复制代码



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

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

  1. DWORD FuncalDoodLeDoo()
  2. {
  3.     DWORD dwTemp=0;
  4.     while (dwTemp<10)
  5.     {
  6.         __try
  7.         {
  8.             if(dwTemp==2)
  9.                 continue;
  10.             if(dwTemp==3)
  11.                 break;
  12.         }
  13.         __finally
  14.         {
  15.             dwTemp++;
  16.         }
  17.         dwTemp++;
  18.     }
  19.     dwTemp+=10;
  20.     return dwTemp;
  21. }
复制代码



让我们逐步分析这个函数执行的过程:一开始将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

  1. DWORD Funcenstein4()
  2. {
  3.     DWORD dwTemp;
  4.    
  5.     //1. do any processing here   
  6.     __try
  7.     {
  8.         WaitForSingleObject(g_hSem,INFINITE);
  9.         
  10.         g_dwProcectedData=5;
  11.         dwTemp=g_dwProcectedData;
  12.         //return the new value
  13.         return dwTemp;
  14.     }
  15.     __finally
  16.     {
  17.         ReleaseSemaphore(g_hSem,1,NULL);
  18.         return 103;
  19.     }
  20.     dwTemp = 9;
  21.     return dwTemp;
  22. }
复制代码



在上面的函数中,try中的代码试图用return 返回给调用者。正如我们前面提到的那样,试图在try块中提前退出函数导致编程器生成一些额外代码,将函数返回结果保存在一个临时变量中,然后执行finally中又多了一个return,那么导致103被写入到编译器生成的临时变量时。从而覆盖了原先的值5。而返回103
点击按钮快速添加回复内容: 支持 高兴 激动 给力 加油 苦寻 生气 回帖 路过 感恩
您需要登录后才可以回帖 登录 | 注册账号

本版积分规则

小黑屋|手机版|Archiver|看流星社区 |网站地图

GMT+8, 2024-5-6 03:55

Powered by Kanliuxing X3.4

© 2010-2019 kanliuxing.com

快速回复 返回顶部 返回列表