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 之后,这是 ILSpy 的问题,而不是 C# 编译器的问题。Eugene Podskal 对我最初的评论和观察给出了很好的回答。但是,我很想知道这是否是 ILSpy 中的一个错误,或者这是否是故意的功能。

4

2 回答 2

15

这可能是反编译器的一些问题。因为 IL 在 .NET 4.5 VS2013 上是正确的:

.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,它将具有 form 变量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) 将在一个槽 ("test_") 中结束,并且对于第一个变量countvar 将是一个,从而导致执行:

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当然不是很好,反汇编程序通常会提出更好的东西。像“num”这样的东西。

所以,有点清楚 ILSpy 中的错误所在的位置,它也会读取 PDB 文件,但会摸索它检索到的符号。您可以将错误提交给供应商,但是他们不太可能将其视为高优先级错误。

于 2014-09-06T15:11:45.670 回答