钻牛角尖之try return finally

 

       try catch finally是我们最常用的异常处理的流程,我们都知道执行try块代码,如果有异常发生就会被相应catch捕获到执行catch块代码,无论如何finally块的代码都会被执行。但是如果我们在try块中加入return语句,return和finally的执行顺序呢?

 

finally在return之前??

对此做过试验或者从finally总会被执行的作用来说,都会认为finally在return前执行。不过,看下面的例子。

js代码:

    function testtry() {
            var i = 0;
            try {
                i = 1;
                return i;
            } catch (e) {
                i = 2;
                return i;
            } finally {
                i = 3;
            }
        }

 

.net代码:

	private Int32 TestTry()
        {
            Int32 i = 0;
            try
            {
                i = 1;
                return i;
            }
            catch
            {
                i = 2;
                return i;
            }
            finally
            {
                i = 3;
            }
        }

 

结果应该是1还是3呢?如果finally在return之前那应该是3啊,但是上面两段代码是执行是一个结果:1。

难道函数或方法遇到return直接返回,finally根本就没有执行??这不是和finally总会被执行的作用矛盾吗?

 

finally执行了吗

看这段代码:

    function testtry() {
            var i = 0;
            try {
                i = 1;
                return i;
            } catch (e) {
                i = 2;
                return i;
            } finally {
                i = 3;
                return i;
            }
        }

 

因为.net不允许在finally中加return,因此没有了.net版本的这段代码。

这段js代码比之前的只是在return中多了一个return,结果应该是什么?1 or 3?

答案是3,这又能说明什么?它说明不管return和finally的执行顺序是怎样,finally肯定是被执行了。

 

那问题又来了,既然finally肯定被执行了,那我们的第一段代码结果就应该是3,而不应该是1啊?

原因揭秘

如何揭秘,我们就要借助第一段代码中.net代码的编译代码:

    18:         {
00000000 55                   push        ebp 
00000001 8B EC                mov         ebp,esp 
00000003 57                   push        edi 
00000004 56                   push        esi 
00000005 53                   push        ebx 
00000006 83 EC 38             sub         esp,38h 
00000009 8B F1                mov         esi,ecx 
0000000b 8D 7D C8             lea         edi,[ebp-38h] 
0000000e B9 0B 00 00 00       mov         ecx,0Bh 
00000013 33 C0                xor         eax,eax 
00000015 F3 AB                rep stos    dword ptr es:[edi] 
00000017 8B CE                mov         ecx,esi 
00000019 33 C0                xor         eax,eax 
0000001b 89 45 E4             mov         dword ptr [ebp-1Ch],eax 
0000001e 89 4D C4             mov         dword ptr [ebp-3Ch],ecx 
00000021 83 3D 10 29 DD 03 00 cmp         dword ptr ds:[03DD2910h],0 
00000028 74 05                je          0000002F 
0000002a E8 E8 52 DE 6A       call        6ADE5317 
0000002f 33 D2                xor         edx,edx 
00000031 89 55 C0             mov         dword ptr [ebp-40h],edx 
00000034 33 D2                xor         edx,edx 
00000036 89 55 BC             mov         dword ptr [ebp-44h],edx 
00000039 90                   nop 
    19:             Int32 i = 0;
0000003a 33 D2                xor         edx,edx 
0000003c 89 55 C0             mov         dword ptr [ebp-40h],edx 
    20:             try
    21:             {
0000003f 90                   nop 
    22:                 i = 1;
00000040 C7 45 C0 01 00 00 00 mov         dword ptr [ebp-40h],1 
    23:                 return i;
00000047 8B 45 C0             mov         eax,dword ptr [ebp-40h] 
0000004a 89 45 BC             mov         dword ptr [ebp-44h],eax 
0000004d 90                   nop 
0000004e C7 45 E0 00 00 00 00 mov         dword ptr [ebp-20h],0 
00000055 C7 45 E4 FC 00 00 00 mov         dword ptr [ebp-1Ch],0FCh 
0000005c 68 8D 18 E5 03       push        3E5188Dh 
00000061 EB 29                jmp         0000008C 
    24:             }
    25:             catch
00000063 90                   nop 
    26:             {
00000064 90                   nop 
    27:                 i = 2;
00000065 C7 45 C0 02 00 00 00 mov         dword ptr [ebp-40h],2 
    28:                 return i;
0000006c 8B 45 C0             mov         eax,dword ptr [ebp-40h] 
0000006f 89 45 BC             mov         dword ptr [ebp-44h],eax 
00000072 E8 61 0A B3 6A       call        6AB30AD8 
00000077 C7 45 E0 00 00 00 00 mov         dword ptr [ebp-20h],0 
0000007e C7 45 E4 FC 00 00 00 mov         dword ptr [ebp-1Ch],0FCh 
00000085 68 84 18 E5 03       push        3E51884h 
0000008a EB 00                jmp         0000008C 
    29:             }
    30:             finally
    31:             {
0000008c 90                   nop 
    32:                 i = 3;
0000008d C7 45 C0 03 00 00 00 mov         dword ptr [ebp-40h],3 
    33:             }
00000094 90                   nop 
00000095 58                   pop         eax 
00000096 FF E0                jmp         eax 
00000098 90                   nop 
    34:         }
00000099 8B 45 BC             mov         eax,dword ptr [ebp-44h] 
0000009c 8D 65 F4             lea         esp,[ebp-0Ch] 
0000009f 5B                   pop         ebx 
000000a0 5E                   pop         esi 
000000a1 5F                   pop         edi 
000000a2 5D                   pop         ebp 
000000a3 C3                   ret 
000000a4 C7 45 E4 00 00 00 00 mov         dword ptr [ebp-1Ch],0 
000000ab EB EB                jmp         00000098 
000000ad C7 45 E4 00 00 00 00 mov         dword ptr [ebp-1Ch],0 
000000b4 EB E2                jmp         00000098 

 

这其实是.net执行的真实路径。

1,首先第一个我们可以看到的是 倒数第5行的ret指令,这个是返回指令,也就是说我们表面的return其实并不是真实的方法出口的位置。

2,看下return i;的IL

00000047 8B 45 C0             mov         eax,dword ptr [ebp-40h] 
0000004a 89 45 BC             mov         dword ptr [ebp-44h],eax 
0000004d 90                   nop 
0000004e C7 45 E0 00 00 00 00 mov         dword ptr [ebp-20h],0 
00000055 C7 45 E4 FC 00 00 00 mov         dword ptr [ebp-1Ch],0FCh 
0000005c 68 8D 18 E5 03       push        3E5188Dh 
00000061 EB 29                jmp         0000008C 

 

我们看到除了一些mov操作之外,并没有中止方法执行,最后一句是jmp跳转的指令,而这个跳转的地址正好是finally块的开始的地址,也就是这句执行之后去执行了finally。

3,在具体分析return i的IL之前,我们先看一下方法开始和结束时那两段没有特定对应的C#代码的IL,它们分别是“开场”prologue code:负责在方法开始之前对方法进行初始化,其中最重要是为方法的局部变量在线程堆栈上分配内存,并且为返回值分配内存,从代码中可以看到其中分配了和初始化了[ebp-40h]  [ebp-44h]  这两块地址;“收场”代码epilogue code:方法完成清理并返回调用者。

4,接着上一块来说,我们可以看到每次操作i值时,都会有mov dword ptr [ebp-40h] 这样的操作,也就是说这块地址存储的是i的值。那么我们从return i;的IL代码可以看到它首先执行了两个操作:把[ebp-40h]的值给eax,然后又把eax值给了[ebp-44h],也就是其实返回值保存在了[ebp-44h]这个地址。

5,到最后,只是把[ebp-44h]这个地址的值放到数据寄存器,最后被调用者获取。

 

      从此真相大白,也就是变量和返回值是分别保存在两个不同的地方,return i;时只用i值填充返回值的地址,finally时再次改变i的值,却不会影响返回值。至于js finally里能再次return i;也可能是再次修改了返回值那块地址所保存的值。

 

引用类型呢

对于值类型,分配的地址保存直接是值,再次修改i值不能影响到返回值;而对于引用类型,地址里保存的是指针,是不是应该是另一番光景呢?

function testtry2() {
            var o = {};
            o.i = 0;
            try {
                o.i = 1;
                return o;
            } catch (e) {
                o.i = 2;
                return o;
            } finally {
                o.i = 3;
            }
        }

 

大胆猜一次返回的对象的i的值吧,对,就是3.

 

钻完收工。

posted @ 2012-11-22 16:20  for certain  阅读(3292)  评论(8编辑  收藏  举报