了解 MSIL 的尝试捕获最终
本文关键字:MSIL 了解 | 更新日期: 2023-09-27 18:35:07
>我有以下代码
using System;
class Pankaj
{
public static int Main()
{
int returnValue=0;
try
{
return returnValue;
throw new Exception();
}
catch(Exception ex){
return returnValue;
}
finally
{
returnValue++;
}
return returnValue;
}
}
上述代码生成的 MSIL 是:
.method public hidebysig static int32 Main() cil managed
{
.entrypoint
// Code size 18 (0x12)
.maxstack 2
.locals init (int32 V_0,
int32 V_1)
IL_0000: ldc.i4.0
IL_0001: stloc.0
.try
{
.try
{
IL_0002: ldloc.0
IL_0003: stloc.1
IL_0004: leave.s IL_0010
} // end .try
catch [mscorlib]System.Exception
{
IL_0006: pop
IL_0007: ldloc.0
IL_0008: stloc.1
IL_0009: leave.s IL_0010
} // end handler
} // end .try
finally
{
IL_000b: ldloc.0
IL_000c: ldc.i4.1
IL_000d: add
IL_000e: stloc.0
IL_000f: endfinally
} // end handler
IL_0010: ldloc.1
IL_0011: ret
} // end of method Pankaj::Main
我有以下问题:
- 为什么 try 捕获再次包含在 try 块中。
- 看起来离开.s 尝试和捕获块的最后一行指向最后,即IL_0010但是在第 IL_0010 行它的 ldloc.1 我相信这意味着在堆栈上加载局部变量 1,然后它指向最终阻止。是否类似于位置 1,我们有最终块的地址。
- 如果我从 catch 块中抛出或返回一些东西,那么调用语句为什么落到 finally 块,它已经从 catch 块返回,但 finally 块仍然被执行。
为什么 try 捕获再次包含在 try 块中。
不确定这个。这可能只是ildasm
选择反编译它的方式。ECMA-335 说在TryBlock
后如何指定SEHClause
元素有限制,但我还没有找到这些限制。
看起来 leave.s 尝试和捕获块的最后一行指向最终,即IL_0010但在它的 ldloc.1 IL_0010行,我相信这意味着在堆栈上加载本地变量 1,然后它如何指向最终块。是否类似于位置 1,我们有最终块的地址。
不,这是跳到finally
块之后 - 有效地返回值。有很多返回语句都返回相同的内容以及无法访问的代码,这无济于事,但我相信重点基本上只是将ret
移出try
并catch
。我认为编译器有效地为返回值设置了一个额外的局部变量。
如果我从 catch 块中抛出或返回一些东西,那么调用语句为什么落到 finally 块,它已经从 catch 块返回,但 finally 块仍然被执行。
这就是 C# 和 IL 的定义方式 - 无论您退出块,都会执行finally
块。
Jon Skeet 已经回答了最后两个问题,所以我只关注第一个问题。
为什么 try 捕获再次包含在 try 块中。
这有几个原因:
- 将 catch 处理程序
- 放在与 finally 处理程序关联的 try 块中意味着即使 catch 块内抛出异常,finally 也会发生(我相信这是 C# 规范所要求的 - 尽管我没有直接引用它在哪里这么说)。
- CLI 规范对重叠异常处理区域有一些严格的规则,这些规则排除了 catch 和 finally 块保护相同的代码,而 finally 块也保护 catch 块或 catch 块也保护 finally 块(规范用比这更通用的术语讨论了它, 您可以在 ECMA-335 的分区 1 第 12.4.2.7 节中找到详细信息)。