本文轉自 vipxiaotian(CSDN)
請參考下面一段簡單的語句塊:
1: try
2: {
3: throw new Exception("new exception");
4: }
5: catch(Exception ex)
6: {
7: return;
8: }
9: finally
10: {
11: Console.WriteLine("a");
12: }
大家有沒有想過執行到第七步之后會出現什么結果?line7是一條return語句,按道理來說return語句的作用就是跳出當前函數並繼續調用地址的下一條語句,那么說就不執行finally,但是我想大家應該都記得finally的作用:確保有無異常的情況下finally塊中的語句都能執行……於是,矛盾就這樣產生鳥。。。
在繼續下去看答案之前,親愛的讀者們,你們覺得會是什么結果呢?finally會不會執行呢?請先思考一下再看答案。
既然在程序層看不出貓膩,那么我們不妨深入IL層去挖掘一下,上面代碼的IL是這樣子地:
.try
{
.try
{
IL_0000: ldstr "new exception"
IL_0005: newobj instance void [mscorlib]System.Exception::.ctor(string)
IL_000a: throw
} // end .try
catch [mscorlib]System.Exception
{
IL_000b: stloc.0
IL_000c: leave.s IL_0019
} // end handler
} // end .try
finally
{
IL_000e: ldstr "a"
IL_0013: call void [mscorlib]System.Console::WriteLine(string)
IL_0018: endfinally
} // end handler
IL_0019: ret
請注意上面上面黃色部分代碼,在catch塊中,使用了leave.s這條命令來跳轉到IL_0019這一行,而0019這一行正是ret命令,看起來……應該不會執行到finally才對~~難道說,從catch塊就直接跳出了?!
我想該是時候去弄明白leave.s這條命令的作用了,看看Microsoft給出的定義:
格式 | 匯編格式 | 說明 |
---|---|---|
DE < int8 > | leave.s target | 退出受保護的代碼區域(短格式)。 |
沒有為此指令指定任何堆棧轉換行為。
leave.s 指令無條件將控制轉移到傳遞的目標指令,這表示為距當前指令之后的指令的開始處的 1 字節有符號偏移量。
leave.s 指令類似於 br 指令,但它可用於退出 try、filter 或 catch 塊,而一般分支指令只能在此類塊中使用以在其內部轉移控制。leave.s 指令清空計算堆棧並確保執行周圍適當的 finally 塊。
不能使用 leave.s 指令退出 finally 塊。為了簡化異常處理程序的代碼生成,一個有效的方法是在 Catch 塊的內部使用 leave.s 指令將控制轉移到關聯的 try 塊中的任何指令。
如果指令有一個或多個前綴代碼,則只能將控制轉移到其中的第一個前綴。
OK,至此,一切明了了~~leave.s會確保finally塊的執行,所以,即使在catch中使用了return語句想直接跳出,還是會先執行finally再return的