为什么编译器在从方法返回字符串时创建似乎什么也不做的指令?

kmp*_*kmp 17 .net c#

我正在研究一个非常简单的方法生成的IL,因为我想做一些自我反射,我遇到了这个问题的评论中提到的东西(但不是问题):使用Br_S OpCode指向使用Reflection.Emit.Label的下一条指令,没有人回答它,我想知道它.所以...

如果我有这样的方法:

    public string Test()
    {            
        return "hello";
    }
Run Code Online (Sandbox Code Playgroud)

然后我运行ILDASM我看到IL是这样的:

.method public hidebysig instance string 
        Test() cil managed
{
  // Code size       11 (0xb)
  .maxstack  1
  .locals init ([0] string CS$1$0000)
  IL_0000:  nop
  IL_0001:  ldstr      "hello"
  IL_0006:  stloc.0
  IL_0007:  br.s       IL_0009
  IL_0009:  ldloc.0
  IL_000a:  ret
}
Run Code Online (Sandbox Code Playgroud)

我觉得好奇的部分是:

  IL_0007:  br.s       IL_0009
  IL_0009:  ldloc.0
Run Code Online (Sandbox Code Playgroud)

第一行是对第二行进行无条件转移.这次手术的原因是什么,它什么都不做?

编辑

看来我的问题很严厉,因为我想知道的事情有些混乱.最后一句应该是这样的:

当编译器似乎没有任何目的时输出这个无条件转移语句的原因是什么?

UPDATE

关于断点的建议让我想到尝试在发布模式下编译它,确定我感兴趣的部分消失了,而IL就变成了这个(这就是为什么我跳起来认为断点回答是原因):

.method public hidebysig instance string 
        Test() cil managed
{
  // Code size       6 (0x6)
  .maxstack  8
  IL_0000:  ldstr      "hello"
  IL_0005:  ret
} 
Run Code Online (Sandbox Code Playgroud)

"我为什么会在那里"的问题仍然在我脑海中浮现 - 如果它不是编译器始终工作的方式而且它不是为了一些有用的调试原因(比如有一个放置断点的地方)为什么要这么做呢?

我想答案可能是:"只是它的制作方式,没有坚实的理由,这并不重要,因为JIT最终会很好地对它进行整理."

我希望我现在不要问这个,这会破坏我的接受率!:-)

Guf*_*ffa 13

两条指令中的第一条是该return语句的标准代码的一部分,第二条指令是该方法的样板代码的一部分.

return语句将返回值放在局部变量中,然后跳转到方法的出口点:

IL_0001:  ldstr      "hello"
IL_0006:  stloc.0
IL_0007:  br.s       IL_0009
Run Code Online (Sandbox Code Playgroud)

方法的样板代码从局部变量获取返回值,然后从方法退出:

IL_0009:  ldloc.0
IL_000a:  ret
Run Code Online (Sandbox Code Playgroud)

在编译器创建的IL代码中,方法始终具有单个出口点.这就是return语句跳转到该位置而不是直接退出函数的原因.return语句的代码总是相同的,所以即使它跳转到下一条指令也总是有一个分支.

编译器经常生成看起来效率低下的IL代码,因为JIT编译器会优化代码.编译器生成未经优化的,简单且可预测的代码,JIT编译器更容易优化.


Pet*_*ter 10

NOP指令在调试中的一个原因是使它能够设置断点.

VB.net内部,下载到debuging

Visual Basic .NET允许您在非执行的代码行上设置断点,例如End If,End Sub和Dim语句.为了促进这种流行的调试技术,编译器将nop指令作为占位符插入非执行代码行(因为非执行行不会转换为IL指令).nop指令是一个"无操作"指令 - 它不执行任何有意义的工作,但可以消耗处理周期.