C# 为什么';br.s';在这种情况下使用IL操作码?

C# 为什么';br.s';在这种情况下使用IL操作码?,c#,il,C#,Il,出于教育目的,我正在学习一点IL(主要是因为我很好奇引擎盖下的“%”会发生什么情况(结果是rem)并开始偏离主题…) 我写了一个方法,只是返回true来分解一些东西,并对'br.s'操作码感到疑惑: .method public hidebysig static bool ReturnTrue() cil managed { // Code size 7 (0x7) .maxstack 1 .locals init ([0] bool CS$1$0000) IL_

出于教育目的,我正在学习一点IL(主要是因为我很好奇引擎盖下的“%”会发生什么情况(结果是rem)并开始偏离主题…)

我写了一个方法,只是返回true来分解一些东西,并对'br.s'操作码感到疑惑:

.method public hidebysig static bool  ReturnTrue() cil managed
{
  // Code size       7 (0x7)
  .maxstack  1
  .locals init ([0] bool CS$1$0000)
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  stloc.0
  IL_0003:  br.s       IL_0005
  IL_0005:  ldloc.0
  IL_0006:  ret
} // End of method Primes::ReturnTrue
在ldc.i4.1将1推到堆栈上并且stloc.0将其放置在第0个本地之后,br.s基本上(据我所知)在第IL_0005行对ldloc.0执行“转到”


为什么会这样?为什么没有IL_0004行可以省略?

该分支用于调试目的,返回值已计算并存储,现在可以“调用”调试器。这与方法条目中的
NOP
相同

关于
IL_0004
,正如@hvd所述,
br.s
有一个地址,不适合“一行”,这里是一个字节(我不知道您对寻址有多熟悉,但一条指令通常是一个字节,即8位,以及地址或偏移量,通常是8、16或32位。在这种情况下,我们有一个8位操作码,偏移量为8位。)


此外,假设您的方法有多个返回,例如,通过
if
-分支,所有分支都跳到末尾,
IL_0005
,因此在函数返回时只需要一个断点。

这是a的一个非常常见的工件,就像C编译器使用的一样。要摆脱这些分支,需要一个断点

当编译器自身优化了一个可以在编译时确定结果的琐碎操作时,很可能会发生这种情况。C#编译器没有窥视孔优化器,因为它不是必需的,抖动会消除这些不必要的分支。将优化器放入抖动中通常是一个成功的策略,每一次语言编译器从中受益。使编译器非常简单,并且只需在一个(或几个)地方编写和维护代码优化器就可以节省大量的费用


抖动优化器不会就此结束,您的整个方法将在运行时消失。很有可能调用该方法的任何代码也会得到实质性优化,因为您的方法的返回值在编译时已知。看到这种MSIL是一个强烈的暗示,说明您的代码可以轻松简化或有错误:)

这在Visual Studio 2013上没有发生,似乎dev终于修复了它。 在VS2013上看起来是这样的

.method public hidebysig static bool  ReturnTrue() cil managed
{
  .maxstack  1
  ldc.i4.1
  ret
} // End of method Primes::ReturnTrue

没有IL_0004,因为
br.s
是一条双字节指令。我猜你禁用了优化?是的,那就期待奇怪的事情吧。当你打开优化时,你会得到什么?然后它会将“1”推到堆栈中,并立即返回。IL_0000:ldc.i4.1 IL_0001:在“IL_xxxx”中,xxxx是指令从方法开始的偏移量(以字节为单位)。br.s是一条单字节指令(其他指令也是),它接受一个单字节操作数,这是要跳转到的目标指令。嗯?它是字节数,只有一个字节用于
br.s
的操作数,地址通常不适合放入字节
br.s
采用8位相对偏移量,而不是地址。@hvd您是对的,我一直认为IL地址不是基于字节的,而是基于字长的,但您是正确的,先生;)尝试使用
debug
-标志集进行编译。