在IL代码中,为什么在某些情况下不会出现nop操作码?为什么在某些情况下会出现br.s操作码?

7
假设我有以下代码:

假设我有以下代码:

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. 在 MethodA 中,为什么在 L_0006L_0007 之间没有 nop 代码?
    • 由于从 L_0001L_0006 与从 L_0007L_0009 不同,为什么没有 nop 操作码?
  2. 在 MethodB 中,L_0003 为什么是必需的?

1
你正在使用发布版编译吗?因为我听说在方法头上,调试版本有 NOP 代码。 - Ray
我正在使用调试模式编译。 - cm007
2个回答

10

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

BR.S 指令是编译器中的一个小缺陷,它没有 窥孔优化器 来消除这种多余指令。一般来说,C# 编译器的工作并不是优化代码,而是由 JIT 编译器 完成。后者可以轻松地移除这个指令。


我非常确定在发布模式下该分支将被删除。这种垃圾通常是由早期编译器传递产生的,因此删除它是微不足道的。 - usr
我的问题更多的是为什么在 L_000e 有一个 nop 操作码,但是在 L_0006L_0007 之间没有呢?我知道在 L_0000 需要一个 nop 操作码来设置断点在方法开始的大括号处,但为什么是在 L_000e 而不是在 L_0006L_0007 之间呢? - cm007
2
L_0006处没有花括号。L_000e与BR.S一样是不必要的。这并不重要,这不必完美无缺。错误会在需要修复时进行修复,而不是因为它们存在。从递归下降解析器中消除虚假代码生成可能很困难,修复它并不总是值得冒犯风险。如果您想追查原因,可以研究来自SSCLI20分发版csharp/sccomp子目录的C#编译器源代码。 - Hans Passant

1

你所看到的一切都是因为你以调试模式编译。冗余的跳转和nop指令是禁用的优化操作,同时也是调试支持(我相信)。

请以发布模式编译。


谢谢。在发布模式下编译,MethodB只有两个指令ldarg.1 ret - cm007

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接