【问题标题】:understand MSIL of try catch finally终于明白 try catch 的 MSIL
【发布时间】:2014-12-22 11:24:45
【问题描述】:

我有以下代码

    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

我有以下问题:

  1. 为什么 try catch 再次包含在 try 块中。
  2. 看起来 leave.s 的最后一行 try and catch 块指向 finally 即 IL_0010 但是在 IL_0010 行,它的 ldloc.1 我认为这意味着将局部变量 1 加载到堆栈上,然后它指向最终阻塞的方式。 是不是在位置 1 我们有 finally 块的地址。
  3. 如果我从 catch 块中抛出或返回某些东西,那么调用语句怎么会落到 finally 块中,它已经从 catch 块中返回,但仍然执行 finally 块。

【问题讨论】:

  • 请注意 try/catch/finally 根本不会生成任何 MSIL 操作码。这些指令标识操作码的区域。这是它在后台实现方式的非常准确的表示,它生成了一个地址表。 CLR 通过查看该表来找到 catch 和 finally 子句代码。用引发异常的指令的地址对其进行索引。

标签: c# cil il


【解决方案1】:

为什么 try catch 再次包含在 try 块中。

不确定这个。这可能只是ildasm 选择反编译它的方式。 ECMA-335 表示在 TryBlock 之后如何指定 SEHClause 元素有一些限制,但我还没有找到这些限制。

看起来 leave.s 尝试和捕获块中的最后一行指向 finally 即 IL_0010 但在 IL_0010 行它的 ldloc.1 我相信这意味着将局部变量 1 加载到堆栈上,然后它指向 finally 块.是不是在位置 1 我们有 finally 块的地址。

不,这是跳转到之后 finally 块 - 有效地返回值。 lots 的 return 语句都返回相同的东西以及无法访问的代码并没有帮助,但我相信重点基本上只是将 ret 移到 @ 之外987654326@ 和catch。我认为编译器实际上是为返回值设置了一个额外的局部变量。

如果我从 catch 块中抛出或返回一些东西,那么 call 语句怎么会落到 finally 块中,它已经从 catch 块中返回,但仍然执行 finally 块。

这就是 C# 和 IL 的定义方式 - finally 块将被执行然而您退出该块。

【讨论】:

  • okay ... .locals init (int32 V_0,int32 V_1) 看起来像是两个整数变量的初始化值,我只有一个整数变量,其他的呢
  • @Pankaj:我刚刚编辑了我的答案 - 我认为从 IL 中的 .try 块返回可能存在限制,编译器只是为两者设置了一个额外的局部变量返回语句。如果您从 try 返回的内容与从 catch 不同的内容,您会看到它们被复制到额外的本地。
  • 就像 -> Main() -> 初始化返回值 -> 然后在 IL_0004 跳转到 IL_0010 -> 在 IL_0010 加载堆栈局部变量 1(在本例中为 returnValue)-> IL_0011 即返回,返回 0。在这个执行路径中最终被调用的位置
  • @Pankaj:是的,我相信。
【解决方案2】:

Jon Skeet 已经回答了最后两个问题,所以我只关注第一个问题。

为什么 try catch 再次包含在 try 块中。

这有几个原因:

  • 将 catch 处理程序放置在与 finally 处理程序关联的 try 块内意味着即使在 catch 块内抛出异常(我认为 C# 规范要求这样做 - 尽管我没有直接参考它所说的地方)。
  • CLI 规范有一些关于重叠异常处理区域的严格规则,这些规则阻止了 catch 和 finally 块保护相同的代码,而 finally 块也保护 catch 块或 catch 块也保护 finally 块(规范比这更笼统地讨论了它,您可以在 ECMA-335 的第 1 部分第 12.4.2.7 节中找到详细信息)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-10
    相关资源
    最近更新 更多