在ILSpy中,以“1”结尾的变量会被移除其末尾的“1”。为什么?

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#编译器这样做有没有好的理由呢?

为了全面检查,下面是我在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内部的一个bug,还是刻意的功能。


1
是的,它们是。这就是为什么您可以编译/反编译程序集并查看指定反编译代码的完整代码。这是由IL解释器(如ILSpy)完成的,它可以将编译的.Net程序集输出到您选择的任何.Net语言。 - RLH
1
那绝对不能发生,至少不是自动或编译器造成的。 - Afzaal Ahmad Zeeshan
让我在ildasm中检查IL代码... - RLH
@AfzaalAhmadZeeshan,我使用发布的代码成功地复制了这个问题。 - Slippery Pete
1
这是ILSpy或更一般的反编译器的问题。dotPeek显示变量名为num1num2num3 - Habib
显示剩余3条评论
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)将以一个插槽结束(“test_”),对于第一个变量 count,变量将为 1,导致执行:

else {
    return nameWithoutDigits;
}

其中nameWithoutDigitstest_

编辑

首先,感谢@HansPassant及其回答指出本帖的错误。

所以问题的源头是:

ILSpy跟ildasm一样聪明,因为它也使用.pdb数据(否则它怎么会得到test_1,test_2等名称)。但是它的内部工作针对没有任何调试相关信息的程序集进行了优化,因此它处理V_0,V_1,V_2变量的优化与来自.pdb文件的大量元数据不一致。

据我所知,罪魁祸首是一种优化,可以从单独的变量中删除_0

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


谢谢。这就是答案——我应该再进一步。当我检查IL时,我自己刚发现了这个问题。:/ 看来这是ILSpy内部的一个错误。 - RLH
看到这个,我们可能正在看到反编译器删除了“1”,因为它将某些带下划线的名称视为“maical” - 例如由“property {get; set;}”或lambda生成的其他匿名字段。 - David
我本来想删除这个问题,但由于很多人使用ILSpy,我会将其保留作为未来的参考。你将在大约5分钟内获得答案标记。 - RLH
@EugenePodskal 我已经更新了我的问题,以反映在ILSpy中的好奇心。而且,事实上,有人给我点了踩?有时候我真的不明白。 - RLH

5
好的,这是一个bug。不算太严重的bug,很少有人会为此提交bug报告。请注意,尤金的回答非常误导人。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 文件但却搞砸了检索到的符号。您可以向供应商报告此漏洞,但他们很可能不会将其视为高优先级漏洞。


2
谢谢Hans。你的回答总是非常有信息量! - RLH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接