鑽牛角尖之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.

 

鑽完收工。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM