首页 > 解决方案 > 在 IL 中 try-catch 是如何工作的?

问题描述

考虑这段代码:

void Main()
{
    try {
        Console.Write("try ");
        throw new NotImplementedException();
    } 
    catch (NotImplementedException) {
        Console.Write("catch");
    }
}

使用 LINQPad,我看到代码编译为:

IL_0000:  nop         
IL_0001:  nop         
IL_0002:  ldstr       "try"
IL_0007:  call        System.Console.WriteLine
IL_000C:  nop         
IL_000D:  newobj      System.NotImplementedException..ctor
IL_0012:  throw       
IL_0013:  pop         
IL_0014:  nop         
IL_0015:  ldstr       "catch"
IL_001A:  call        System.Console.WriteLine
IL_001F:  nop         
IL_0020:  nop         
IL_0021:  leave.s

上面的代码 prints ,如果我改为try catchthrow ,则会打印并且程序由于未处理的异常而退出(如预期的那样),IL代码保持不变,除了这一行:Exceptiontry

IL_000D:  newobj      System.Exception..ctor

这背后的逻辑是什么?除了检查异常类型以决定是否进入 catch 块的逻辑之外,我将除外。

标签: c#.net-corecil

解决方案


我这里有点死灵。我花了几个小时阅读有关该主题的规范,这里有一些有趣的细节没有在评论中涉及。引用来自Standard ECMA-335 - Common Language Infrastructure (CLI)

可执行文件中的每个方法都与一个(可能为空的)异常处理信息数组相关联。数组中的每个条目都描述了一个受保护的块、它的过滤器和它的处理程序。发生异常时,CLI 会在数组中搜索第一个受保护的块

  • 保护包括当前指令指针的区域和
  • 谁的过滤器希望处理异常

如果在当前方法中未找到匹配项,则搜索调用方法,依此类推。如果未找到匹配项,CLI 将转储堆栈跟踪并中止程序

需要注意的一些事情是:

  • 异常处理程序可以访问捕获异常的例程的局部变量和局部内存池,但是在抛出异常时评估堆栈上的任何中间结果都将丢失
  • 描述异常的异常对象由 CLI 自动创建,并在进入过滤器或 catch 子句时作为第一项推送到评估堆栈

所以“一个(可能是空的)异常处理信息数组”实际上是每个方法都有的一个部分

在方法主体之后的下一个 4 字节边界可以是额外的方法数据部分。目前,方法数据部分仅用于异常表。

该部分包含有关块从哪里开始、在哪里结束、从哪里开始以及一个特定字段的clauses存储信息——它被描述为应该是关于可以由该特定 catch 块处理的异常类型的信息。trycatchClassTokenMeta data token for a type-based exception handler

不幸的是,ILDASM 不显示原始方法标头并从那里插入信息作为.try反汇编代码视图中的指令,但是 IL 本身没有用于异常类型处理的特定指令,因为它没有插入特定的中间代码用于相同目的。


推荐阅读