以“1”结尾的变量在ILSpy中删除了“1”。为什么?

时间:2014-09-05 18:27:56

标签: c# ilspy

为了探索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#编译器会这样做?

对于全面检查,这是我在ILSpy中看到的Main()的输出。

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中的错误还是故意功能。

2 个答案:

答案 0 :(得分:14)

反编译器可能存在一些问题。因为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文件中的数据(请参阅this answer)来获取正确的名称变量。如果没有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)将在一个插槽中结束(&#34; test _&#34;),并且对于第一个变量count var将为1,从而导致执行:< / p>

else {
    return nameWithoutDigits;
}

其中nameWithoutDigitstest_

编辑

首先,感谢@HansPassant and his answer指出这篇文章中的错误。

所以,问题的根源是:

ILSpy和ildasm一样聪明,因为它也使用.pdb数据(或者它如何获得test_1, test_2名称)。但它的内部工作方式已针对程序集使用进行了优化,没有任何与调试相关的信息,因此与处理V_0, V_1, V_2变量相关的优化与.pdb文件中丰富的元数据不一致。

据我了解,罪魁祸首是从单个变量中删除_0的优化。

修复它可能需要将.pdb数据使用的事实传播到变量名代码中。

答案 1 :(得分: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_0V_1等名称当然不是很好,反汇编程序通常会提供更好的东西。类似&#34; num&#34;。

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