簡體   English   中英

為什么這個非常簡單的C#方法會產生這種不合邏輯的CIL代碼?

[英]Why does this very simple C# method produce such illogical CIL code?

我最近一直在挖掘IL,我注意到了C#編譯器的一些奇怪的行為。 以下方法是一個非常簡單且可驗證的應用程序,它將立即退出,退出代碼為1:

static int Main(string[] args)
{
    return 1;
}

當我使用Visual Studio Community 2015編譯它時,會生成以下IL代碼(添加注釋):

.method private hidebysig static int32 Main(string[] args) cil managed
{
  .entrypoint
  .maxstack  1
  .locals init ([0] int32 V_0)     // Local variable init
  IL_0000:  nop                    // Do nothing
  IL_0001:  ldc.i4.1               // Push '1' to stack
  IL_0002:  stloc.0                // Pop stack to local variable 0
  IL_0003:  br.s       IL_0005     // Jump to next instruction
  IL_0005:  ldloc.0                // Load local variable 0 onto stack
  IL_0006:  ret                    // Return
}

如果我要手寫這個方法,看起來使用以下IL可以獲得相同的結果:

.method static int32 Main()
{
  .entrypoint
  ldc.i4.1               // Push '1' to stack
  ret                    // Return
}

是否存在我不知道的潛在原因使這成為預期的行為?

或者只是組裝好的IL對象代碼進一步優化,所以C#編譯器不必擔心優化?

您顯示的輸出是用於調試版本。 使用發布版本(或基本上啟用了優化),C#編譯器會生成您手動編寫的相同IL。

我強烈懷疑這一切都是為了使調試器的工作變得更容易,基本上 - 使其更容易中斷,並在返回之前查看返回值。

道德:當你想運行優化的代碼時,請確保你沒有要求編譯器生成旨在調試的代碼:)

喬恩的回答當然是正確的; 這個答案是跟進這個評論:

@EricLippert本地很有意義,但有沒有任何理由可以用於br.s指令,還是只是出於方便的發射器代碼? 我想如果編譯器想在那里插入一個斷點占位符,它可能只是發出一個nop ...

如果你看一個更復雜的程序片段,看似無意義的分支的原因變得更加明智:

public int M(bool b) {
    if (b) 
      return 1; 
    else 
      return 2;
}

未經優化的IL是

    IL_0000: nop
    IL_0001: ldarg.1
    IL_0002: stloc.0
    IL_0003: ldloc.0
    IL_0004: brfalse.s IL_000a
    IL_0006: ldc.i4.1
    IL_0007: stloc.1
    IL_0008: br.s IL_000e
    IL_000a: ldc.i4.2
    IL_000b: stloc.1
    IL_000c: br.s IL_000e
    IL_000e: ldloc.1
    IL_000f: ret

請注意,有兩個return語句但只有一個ret指令。 在未經優化的IL中,代碼生成簡單返回語句的模式是:

  • 填充您要返回堆棧槽的值
  • 分支/離開到方法的末尾
  • 在方法結束時,從插槽中讀取值並返回

也就是說,未經優化的代碼使用單點返回形式。

在這種情況下和原始海報所示的簡單情況下,該模式導致產生“分支到下一個”情況。 生成未經優化的代碼時,“刪除任何分支到下一個”優化器不會運行,因此它仍然存在。

我要寫的不是特定於.NET的,而是一般的,我不知道.NET在生成CIL時識別和使用的優化。 語法樹(以及語法分析器本身)識別帶有以下語義的return語句:

returnStatement ::= RETURN expr ;

其中returnStatement和expr是非終端,RETURN是終端( 返回令牌),因此當訪問常量1的節點時,解析器的行為就像它是表達式的一部分一樣。 為了進一步說明我的意思,代碼為:

return 1 + 1;

對於使用表達式堆棧的(虛擬)機器,它看起來像這樣:

push const_1 // Pushes numerical value '1' to expression stack
push const_1 // Pushes numerical value '1' to expression stack
add          // result = pop() + pop(); push(result)
return       // pops the value on the top of the stack and returns it as the function result
exit         

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM