【问题标题】:In IL code, why is there not a nop opcode in a given situation? Why is there a br.s opcode in a given situation?在 IL 代码中,为什么在给定情况下没有 nop 操作码?为什么在给定情况下会有 br.s 操作码?
【发布时间】:2013-03-22 22:23:09
【问题描述】:

假设我有以下代码:

public class Class1
{
    private Class2 obj;

    public void MethodA()
    {
        var class2 = new Class2();
        class2.PropertyI = 2;
        obj = MethodB(class2);
    }

    public Class2 MethodB(Class2 class2)
    {
        return class2;
    }
}

public class Class2
{
    public int PropertyI { get; set; }
}

使用 Visual Studio 2010 编译为 .NET 2.0 程序集生成的 IL 代码如下:

.method public hidebysig instance void MethodA() cil managed
{
    .maxstack 3
    .locals init (
        [0] class ClassLibrary1.Class2 class2)
    L_0000: nop 
    L_0001: newobj instance void ClassLibrary1.Class2::.ctor()
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: ldc.i4.2 
    L_0009: callvirt instance void ClassLibrary1.Class2::set_PropertyI(int32)
    L_000e: nop 
    L_000f: ldarg.0 
    L_0010: ldarg.0 
    L_0011: ldloc.0 
    L_0012: call instance class ClassLibrary1.Class2 ClassLibrary1.Class1::MethodB(class ClassLibrary1.Class2)
    L_0017: stfld class ClassLibrary1.Class2 ClassLibrary1.Class1::obj
    L_001c: ret 
}

.method public hidebysig instance class ClassLibrary1.Class2 MethodB(class ClassLibrary1.Class2 class2) cil managed
{
    .maxstack 1
    .locals init (
        [0] class ClassLibrary1.Class2 CS$1$0000)
    L_0000: nop 
    L_0001: ldarg.1 
    L_0002: stloc.0 
    L_0003: br.s L_0005
    L_0005: ldloc.0 
    L_0006: ret 
}

我的问题如下:

  1. 在方法A中,为什么L_0006L_0007之间没有nop代码?
    • 既然L_0001L_0006L_0007L_0009 不同,为什么没有nop 操作码?
  2. 在方法B中,为什么需要L_0003

【问题讨论】:

  • 您是否在 Release 中编译?因为我听说在方法头,调试编译有NOP代码。
  • 我正在调试中编译。

标签: .net .net-2.0 cil opcode


【解决方案1】:

C# 编译器在大括号处发出 NOP 指令。这使得在代码中设置断点变得更容易很多。调试器只允许在代码上设置断点,而大括号通常不会产生任何代码。所以这只是一个简单的调试帮助,这些 NOP 不会在发布版本中生成。

BR.S 指令是编译器中的一个小缺陷,它没有peephole optimizer 来摆脱这些无关指令。一般来说,优化代码不是 C# 编译器的工作,而是由 the jitter 完成的。这将轻松轻松地删除指令。

【讨论】:

  • 我很确定分支将在发布模式下被删除。这种垃圾通常是由早期的编译器传递产生的,删除它是微不足道的。
  • 我的问题更多的是,为什么L_000e 处有一个nop 操作码,但L_0006L_0007 之间没有?我看到L_0000 需要一个nop 操作码来在方法开始的大括号处设置断点,但为什么在L_000e 而不是在L_0006L_0007 之间?
  • L_0006 处没有大括号。 L_000e 是不必要的,就像 BR.S 是不必要的一样。真的没关系,这不一定是完美的。错误在需要修复时得到修复,而不是因为它们存在。从一个像样的递归解析器中摆脱虚假代码生成可能很困难,并且修复它并不总是值得冒险破坏它。如果你想追究原因,那么你可以从SSCLI20发行版的csharp/sccomp子目录中研究C#编译器的源代码。
【解决方案2】:

您看到的所有内容都是因为您在调试模式下进行编译。多余的跳转和 nop 是禁用的优化通道以及调试支持(我相信)。

在发布模式下编译。

【讨论】:

  • 谢谢。在 Release 模式下编译只给 MethodB 两条指令ldarg.1ret
猜你喜欢
  • 2015-09-06
  • 2019-02-27
  • 2011-11-26
  • 2015-09-17
  • 1970-01-01
  • 2017-04-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多