6

我刚开始稍微研究一下 IL,我很好奇我从编译器输出中删除多余代码的尝试(如下所示)是否有任何意外的副作用。

关于结果的几个问题:

  1. 原文中nop操作的目的是什么?
  2. 原始方法末尾的 br.s 的目的是什么?
  3. 重新编写的版本是否有任何不当之处?

原始 C# 代码:

class Program {
    public static int Main() {
        return Add(1, 2);
    }
    public static int Add(int a, int b) {
        return a + b;
    }
}

用(Original)编译csc.exe和反汇编:ildasm.exe

  .method public hidebysig static int32  Main() cil managed
  {
    .entrypoint
    .maxstack  2
    .locals init (int32 V_0)
    IL_0000:  nop
    IL_0001:  ldc.i4.1
    IL_0002:  ldc.i4.2
    IL_0003:  call       int32 Program::Add(int32, int32)
    IL_0008:  stloc.0
    IL_0009:  br.s       IL_000b
    IL_000b:  ldloc.0
    IL_000c:  ret
  } 
  .method public hidebysig static int32  Add(int32 a,
                                             int32 b) cil managed
  {
    .maxstack  2
    .locals init (int32 V_0)
    IL_0000:  nop
    IL_0001:  ldarg.0
    IL_0002:  ldarg.1
    IL_0003:  add
    IL_0004:  stloc.0
    IL_0005:  br.s       IL_0007
    IL_0007:  ldloc.0
    IL_0008:  ret
  }

重写(产生相同的输出):

  .method public hidebysig static int32  Main() cil managed
  {
    .entrypoint
        .maxstack  2
    ldc.i4.1
    ldc.i4.2
    call int32 Program::Add(int32, int32)
    ret
  }

  .method public hidebysig static int32  Add(int32 a, int32 b) cil managed
  {
    .maxstack  2
      ldarg.0
      ldarg.1
    add
    ret
  }
4

2 回答 2

6

您看到的所有“多余”代码都特定于调试构建(并且通常会针对发布构建进行优化),并允许您执行通常在发布构建中无法执行的操作。

调试构建代码允许在调试会话期间最大程度地独立设置断点和更改/检查堆栈值。此外,IL 代码应尽可能模仿更高级别的代码,以便每个“原因”和“效果”都可以映射到更高级别的代码行。

现在具体回答您的问题:

原文中nop操作的目的是什么?

NOP 允许您在未“执行”的地方设置断点。例如,方法、循环或 if 语句的左大括号。在这些不可执行的指令中,在左大括号处中断允许您在块开始之前修改/检查堆栈(尽管您可以很容易地通过在块执行的第一行中断而不是左大括号来实现这一点,但它仍然允许您独立地在左大括号处折断)

原始方法末尾的 br.s 的目的是什么?

查看原始代码,您可能会发现“跳转”到下一行而不是让代码自然“下降”到下一行是荒谬的。但读作:

“在调试构建中,每当方法需要返回时,跳转到方法的末尾,从堆栈中读取返回值,然后返回值”

那么它对调试有什么好处呢?

如果您的代码中有多个 return 语句,它们都会在从堆栈中读取返回值之前“跳转”到代码的末尾。这允许您在一个地方(方法的右大括号)放置一个断点并在返回值实际返回到调用方法之前对其进行修改。很有帮助不是吗?

重新编写的版本是否有任何不当之处?

您的代码没有任何不当之处。事实上,如果您在发布模式下构建原始文件并检查生成的 CIL,您会注意到它与您的大部分相同。

于 2012-10-01T12:04:57.670 回答
2

免责声明:无论如何,我都不是 IL 专家。

  1. nop操作的目的是什么?

    不久前,programmers.stackexchange.com 上就 x86 ASM 进行了一次大讨论,请参见此处:Purpose of NOP instruction and align statement in x86 assembly。基本上是一样的。

  2. 原始方法末尾的 br.s 的目的是什么?

    这只是方法结束的一个分支。如果您要在此函数中有多个返回路径,那么查看起来会更有意义。就目前而言,编译器已经包含它而不是优化它(可能编译器开关会将它优化掉)。

  3. 重新编写的版本是否有任何不当之处?

    不是我能看到的。您刚刚剥离了如此简单的应用程序不需要的大部分编译器工作。如果您要对此代码进行任何进一步的添加,则需要额外的 IL 来完成它的任务。

于 2012-10-01T03:21:52.820 回答