6

IL を少し調べ始めたところですが、コンパイラの出力から余分なコードを削除する試み (以下を参照) に意図しない副作用があったかどうかに興味があります。

結果に関するいくつかの質問:

  1. オリジナルの nop 操作の目的は何ですか?
  2. 元のメソッドの最後の br.s の目的は何ですか?
  3. 書き直されたバージョンは何らかの点で不適切ですか?

元の C# コード:

class Program {
    public static int Main() {
        return Add(1, 2);
    }
    public static int Add(int a, int b) {
        return a + b;
    }
}

(オリジナル)でコンパイルcsc.exeおよび逆アセンブル:ildasm.exe

  .method public hidebysig static int32  Main() cil managed
  {
    .entrypoint
    .maxstack  2
    .locals init (int32 V_0)
    IL_0000:  nop
    IL_0001:  ldc.i4.1
    IL_0002:  ldc.i4.2
    IL_0003:  call       int32 Program::Add(int32, int32)
    IL_0008:  stloc.0
    IL_0009:  br.s       IL_000b
    IL_000b:  ldloc.0
    IL_000c:  ret
  } 
  .method public hidebysig static int32  Add(int32 a,
                                             int32 b) cil managed
  {
    .maxstack  2
    .locals init (int32 V_0)
    IL_0000:  nop
    IL_0001:  ldarg.0
    IL_0002:  ldarg.1
    IL_0003:  add
    IL_0004:  stloc.0
    IL_0005:  br.s       IL_0007
    IL_0007:  ldloc.0
    IL_0008:  ret
  }

書き直しました(同一の出力を生成します):

  .method public hidebysig static int32  Main() cil managed
  {
    .entrypoint
        .maxstack  2
    ldc.i4.1
    ldc.i4.2
    call int32 Program::Add(int32, int32)
    ret
  }

  .method public hidebysig static int32  Add(int32 a, int32 b) cil managed
  {
    .maxstack  2
      ldarg.0
      ldarg.1
    add
    ret
  }
4

2 に答える 2

6

表示されるすべての「余分な」コードはデバッグ ビルドに固有のものであり (通常、リリース ビルド用に最適化されます)、リリース ビルドでは通常実行できないことを実行できます。

デバッグ ビルド コードは、デバッグ セッション中にブレークポイントを設定し、スタック値を変更/検査する際に最大限の独立性を可能にするようなものです。また、IL コードは、すべての「原因」と「結果」を上位レベルのコード行にマッピングできるように、可能な限り上位レベルのコードを模倣する必要があります。

あなたの質問に具体的に言うと:

オリジナルの nop 操作の目的は何ですか?

NOP を使用すると、「実行」されていない場所にブレークポイントを設定できます。たとえば、メソッド、ループ、または if ステートメントの開き中かっこ。これらの実行不可能な命令の中で、開始ブレースでブレークすると、ブロックが開始する直前にスタックを変更/調べることができます (ただし、開始ブレースの代わりにブロックの実行の最初の行でブレークすることでこれを非常に簡単に達成できますが、開始ブレースで独立して改行することもできます)

元のメソッドの最後の br.s の目的は何ですか?

元のコードを見ると、コードが自然に次の行に「落ちる」のではなく、次の行に「ジャンプ」するのは無意味であることに気付くかもしれません。ただし、次のように読みます。

「デバッグビルドでは、メソッドが戻る必要があるときはいつでも、メソッドの最後にジャンプし、スタックから戻り値を読み取ってから値を返します」

では、デバッグにはどのような利点があるのでしょうか?

コードに複数の return ステートメントがある場合、両方ともスタックから戻り値を読み取る前にコードの最後に「ジャンプ」します。これにより、呼び出し元のメソッドに実際に返される前に、ブレークポイントを配置して戻り値を変更できる 1 つの場所 (メソッドの右中かっこ) が可能になります。かなり役に立ちませんか?

書き直されたバージョンは何らかの点で不適切ですか?

コードに不適切なものはありません。実際、オリジナルをリリース モードでビルドし、生成された CIL を確認すると、ほとんど同じであることがわかります。

于 2012-10-01T12:04:57.670 に答える
2

免責事項: 私は決して IL の専門家ではありません。

  1. nop 操作の目的は何ですか?

    少し前に、programmers.stackexchange.com で x86 ASM に関してこれについて大きな議論がありました。こちらを参照してください: Purpose of NOP instruction and align statement in x86 assembly . 本質的には同じだろう。

  2. 元のメソッドの最後の br.s の目的は何ですか?

    これは、メソッドの最後への単なる分岐です。この関数に複数の戻りパスがある場合は、それを確認する方が理にかなっています。現状では、コンパイラーはそれを最適化するのではなく、それを含めています (おそらく、コンパイラーのスイッチはそれを最適化します)。

  3. 書き直されたバージョンは何らかの点で不適切ですか?

    私が見ることができるわけではありません。このような単純なアプリケーションに必要のないコンパイラの作業の大部分を取り除いただけです。このコードにさらに追加を行う場合は、そのタスクを完了するためにここに追加の IL が必要になります。

于 2012-10-01T03:21:52.820 に答える