为什么在这种情况下使用’br.s’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执行’goto’。

为什么是这样? 为什么没有IL_0004线,所以这可以省略?

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

关于IL_0004 ,如@hvd状态, br.s有一个地址,不适合“一行”,这里有一个字节(我不知道你对寻址有多熟悉,但一条指令通常是一个字节,即8位,以及地址或偏移量,通常为8位,16位或32位。在这种情况下,我们有一个8位操作码和8位偏移量。 维基百科有一篇很好的文章CIL-OP代码 )。

另外,假设你的方法有多个返回,例如if -branches,它们全部跳到最后,在你的情况下是IL_0005 ,因此在函数返回时只需要一个断点。

这是递归下降解析器的一个非常常见的工件,就像C#编译器使用的那样。 摆脱这些分支需要一个窥视孔优化器 。

当编译器本身优化了一个简单的操作时,可能会发生,其结果可以在编译时确定。 C#编译器没有窥视孔优化器,因为没有必要,抖动负责消除这些不必要的分支。 将优化器放在抖动中通常是一种成功的策略,每个语言编译器都从中受益。 保持编译器非常简单,并且在一个(或几个)位置编写和维护代码优化器的费用相当可观。

这不是抖动优化器结束的地方,整个方法将在运行时消失。 由于您的方法的返回值在编译时是已知的,因此任何调用该方法的代码都将大大优化的可能性很大。 看到这种MSIL是一个强烈的暗示,你的代码很容易被简化或有bug 🙂

它不会发生在Visual Studio 2013上,似乎dev最终修复了它。 它在VS2013上看起来像这样。

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