为什么编译器会发出一个 stloc 后跟一个 ldloca?

Vin*_*ent 4 il

当我试图了解 Round 实际上做了什么时,我最终查看了一个简单控制台程序的 IL(从 C# .NET 4.5 编译器发出):

.maxstack  2
.locals init (float64 V_0,
         float64 V_1)
IL_0000:  ldc.r8     2.0001232314135344
IL_0009:  stloc.0
IL_000a:  ldloca.s   V_0
IL_000c:  call       instance string [mscorlib]System.Double::ToString()
IL_0011:  call       void [mscorlib]System.Console::WriteLine(string)
IL_0016:  ldloc.0
IL_0017:  ldc.i4.5
IL_0018:  call       float64 [mscorlib]System.Math::Round(float64,
                                                          int32)
IL_001d:  stloc.1
IL_001e:  ldloca.s   V_1
IL_0020:  call       instance string [mscorlib]System.Double::ToString()
IL_0025:  call       void [mscorlib]System.Console::WriteLine(string)
IL_002a:  ret
Run Code Online (Sandbox Code Playgroud)

我注意到有关 IL_001d 和 IL_001e 的说明。它们对我来说似乎是多余的,将值存储在本地并在此之后立即加载相同的值。删除它们并重新组装 IL 给我一个 NullReferenceException,所以我猜它有一定的意义。但我想不通。

那么 NullReferenceException 是从哪里来的呢?为什么编译器会发出这两条指令?没有比上面给出的更多的 IL,当然除了一些元。

Ale*_*x F 5

ldloca将变量的地址加载到堆栈上(不是变量本身)。所以,这两个命令不是相反的:stloc.1弹出由 返回的变量值Math::Round,并压入调用所需ldloca.s的 堆栈地址。V_1Double::ToString

  • 谢谢您的回答。根据您的指示进行更多研究后,我现在完全明白了。“调用”操作码需要将引用压入堆栈,而在堆栈上压入双精度值并不能满足该要求。因此它需要存储在一个变量中,并且该变量的引用被压入堆栈,因此“调用”很高兴。而且我认为 NullReferenceException 的发生是因为双值指示的地址没有任何内容。 (2认同)
  • 关于`System.Double::ToString` 调用,这是实例方法,它需要堆栈上`Double` 结构的地址。由于 `double` 变量与 `Double` 结构具有相同的内存布局,这使得 `ToString` 方法很高兴。而且,顺便说一下,这段代码显示,为 `int、double` 和类似类型调用 `ToString` 不会导致装箱。 (2认同)