14

C# コンパイラがコードを最適化する方法を調べるために、簡単なテスト アプリケーションを作成しました。テストを変更するたびに、アプリケーションをコンパイルし、バイナリを ILSpy で開きました。

私にとって、奇妙なことに気づきました。これは明らかに意図的なものですが、コンパイラがこれを行う正当な理由が思いつきません。

次のコードを検討してください。

static void Main(string[] args)
{
    int test_1 = 1;
    int test_2 = 0;
    int test_3 = 0;

    if (test_1 == 1) Console.Write(1);
    else if (test_2 == 1) Console.Write(1);
    else if (test_3 == 1) Console.Write(2);
    else Console.Write("x");
}

無意味なコードですが、ILSpy がステートメントをどのように解釈するかを確認するためにこれを書きましたif

しかし、このコードをコンパイル/逆コンパイルしたとき、頭を悩ませていることに気付きました。私の最初の変数は!test_1に最適化されました。test_C# コンパイラがこれを行う正当な理由はありますか?

完全な検査のために、これはMain()ILSpy で見ている出力です。

private static void Main(string[] args)
{
    int test_ = 1; //Where did the "1" go at the end of the variable name???
    int test_2 = 0;
    int test_3 = 0;
    if (test_ == 1)
    {
        Console.Write(1);
    }
    else
    {
        if (test_2 == 1)
        {
            Console.Write(1);
        }
        else
        {
            if (test_3 == 1)
            {
                Console.Write(2);
            }
            else
            {
                Console.Write("x");
            }
        }
    }
}

アップデート

IL を調べたところ、これは C# コンパイラではなく、ILSpy の問題であることがわかりました。Eugene Podskal は、私の最初のコメントと観察に対して適切な回答を提供してくれました。ただし、これが ILSpy 内のバグなのか、それとも意図的な機能なのかを知りたいと思っています。

4

2 に答える 2

15

おそらく逆コンパイラに問題があります。.NET 4.5 VS2013 では IL が正しいため:

.entrypoint
  // Code size       79 (0x4f)
  .maxstack  2
  .locals init ([0] int32 test_1,
           [1] int32 test_2,
           [2] int32 test_3,
           [3] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  stloc.0

編集: .pdb ファイル (この回答を参照) のデータを使用して、正しい名前変数を取得します。pdb がないと、フォームに変数が含まれますV_0, V_1, V_2

編集:

メソッド内のファイルNameVariables.cs内の変数名マングル:

public string GetAlternativeName(string oldVariableName)
{
    if (oldVariableName.Length == 1 && oldVariableName[0] >= 'i' && oldVariableName[0] <= maxLoopVariableName) {
        for (char c = 'i'; c <= maxLoopVariableName; c++) {
            if (!typeNames.ContainsKey(c.ToString())) {
                typeNames.Add(c.ToString(), 1);
                return c.ToString();
            }
        }
    }

    int number;
    string nameWithoutDigits = SplitName(oldVariableName, out number);

    if (!typeNames.ContainsKey(nameWithoutDigits)) {
        typeNames.Add(nameWithoutDigits, number - 1);
    }

    int count = ++typeNames[nameWithoutDigits];

    if (count != 1) {
        return nameWithoutDigits + count.ToString();
    } else {
        return nameWithoutDigits;
    }
}

NameVariablesクラスはthis.typeNamesディクショナリを使用して、逆コンパイルするメソッドでの出現のカウンターに関連付けられた変数の名前を末尾番号なしで格納します (そのような変数は ILSpy またはおそらく IL にとって特別なものを意味しますが、実際にはそれを疑っています)。

これは、すべての変数 ( test_1, test_2, test_3) が 1 つのスロット ("test_") で終了し、最初の 1 つのcountvar が 1 になり、結果として実行されることを意味します。

else {
    return nameWithoutDigits;
}

どこnameWithoutDigitsですかtest_

編集

まず、@HansPassant と、この投稿の欠点を指摘してくれた彼の回答に感謝します。

したがって、問題の原因:

ILSpy は ildasm と同じくらいスマートです。.pdb データも使用するためです (または、他の方法でtest_1, test_2名前を取得する方法もあります)。ただし、その内部動作は、デバッグ関連の情報なしでアセンブリで使用するために最適化されているため、V_0, V_1, V_2変数の処理に関連する最適化は、.pdb ファイルからの豊富なメタデータと矛盾して動作します。

_0私が理解しているように、犯人は孤独な変数から削除するための最適化です。

これを修正するには、.pdb データの使用状況を変数名生成コードに反映する必要があります。

于 2014-09-05T18:34:44.967 に答える
5

まあ、それはバグです。バグはそれほど多くなく、バグレポートを提出した人がいる可能性はほとんどありません。ユージーンの答えは非常に誤解を招くことに注意してください。ildasm.exe は、アセンブリの PDB ファイルを検索し、アセンブリのデバッグ情報を取得する方法を認識できるほどスマートです。これには、ローカル変数の名前が含まれます。

これは通常、逆アセンブラーが利用できる贅沢ではありません。これらの名前は実際にはアセンブリ自体には存在せず、常に PDB なしで作成する必要があります。ildasm.exe にも見られるものがあります。obj\Release および bin\Release ディレクトリにある .pdb ファイルを削除するだけで、次のようになります。

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       50 (0x32)
  .maxstack  2
  .locals init (int32 V_0,
           int32 V_1,
           int32 V_2)
  IL_0000:  ldc.i4.1
  // etc...

V_0などの名前V_1はもちろん素晴らしいものではありません。逆アセンブラーは通常、より良いものを考え出します。「ナンバー」のようなもの。

したがって、ILSpy のバグがどこにあるかは明らかです。これも PDB ファイルを読み取りますが、取得したシンボルをいじります。ベンダーにバグを報告することはできますが、ベンダーがそれを優先度の高いバグとして扱う可能性はほとんどありません。

于 2014-09-06T15:11:45.670 に答える