【问题标题】:Different C# Output Between Release and Debug发布和调试之间的不同 C# 输出
【发布时间】:2015-06-20 14:07:27
【问题描述】:

我有一个小 C# 程序,它在 Debug 和 Release 版本之间产生不同的输出。我认为,发布版本的空输出与 C# 语言规范一致。这个程序不应该产生输出,但 Debug 版本会。

我已经从命令行(在 VS 环境之外)运行了 Release 和 Debug 版本,并获得了相同的不一致输出。我已经反编译了 Debug 版本(使用 ILDASM),然后使用 ILASM 重新编译了它。当我这样做时,新编译的程序的行为就像发布版本一样。我只能想象,当我反编译然后重新编译时,有些东西被遗漏了,但我无法确定有什么不同。

关于EXE文件大小:VS生产的Release和Debug版本文件大小相同:5120字节。当我反编译并重新编译时,两个版本再次具有相同但更小的文件大小:3,072。

程序很小,我查看了 Reflector 中的 IL,但看不到任何会导致输出差异的内容。

有没有人(希望是详细的)解释为什么会有差异?

请注意,我并不是试图使调试和发布版本保持一致,我想了解它们为什么不一致。

回想一下我上面所说的——调试和发布版本都会产生不同的输出即使是从命令行运行。如果您告诉我运行时正在为发布版本而不是为调试版本进行某种优化,那么必须在调试/发布版本程序集中嵌入一些东西,告诉运行时打开/关闭优化。那个嵌入的“东西”是什么?为什么在使用 ILDASM/ILASM 时它不能继承?

代码如下:

using System;

class Test {
    static int value = 0;
    static int a = Initialize("Assigning a");
    static int b = Initialize("Assigning b");
    static String name = "Fred";
    static int c = Initialize("Assigning c");

    static int Initialize(String mssg) {
        ++value;
        Console.WriteLine("In Initialize() :: {0}, name={1}, returning {2}", mssg, name, value);
        return value;
    } // Initialize()

    static void Main() {
    } // Main()
} // class Test

这是 Visual Studio 生成的调试版本的输出:

In Initialize() :: Assigning a, name=, returning 1
In Initialize() :: Assigning b, name=, returning 2
In Initialize() :: Assigning c, name=Fred, returning 3

运行发布版本不会产生任何输出。

【问题讨论】:

  • 最有可能的是,在发布模式下,未加载测试类,因为没有对它的引用。尝试将 Console.WriteLine(Test.name); 添加到您的 main 方法中。
  • 我建议放置一个Console.WriteLine("In Main"); 以表明 main 实际上正在被调用。 (我刚试过你仍然得到不同的输出,但是如果你做更多,例如Console.WriteLine("In Main() :: {0}", name);,那么调试和发布开始表现相同)
  • 传呼飞碟先生,飞碟先生请到服务台
  • 如果你愿意,我发现this MSDN blog 展示了如何禁用调试优化,这样你仍然可以在调试器中看到不同的行为(如果你选中“启用地址级调试”框,你可以从 Disassemby 窗口看到汇编代码)。我知道发生了什么(JITed 代码不调用其他静态方法),但我不知道 为什么 它正在发生,所以我不打算发布回答只是猜测。

标签: c# debugging release language-lawyer


【解决方案1】:

您实际上并没有在应用程序(Main 内部)的任何地方使用变量 a、b 或 c,因此我假设它们正在被优化掉。当我在关闭优化的情况下在 LinqPad 中运行该代码时,它会显示您描述的输出,并且在启用优化的情况下,除了执行 Main() 之外什么也没有显示。如果我更新 Main() 中的代码以引用其中一个变量,则输出与构建时的优化一致。

【讨论】:

    【解决方案2】:

    静态类初始化器在需要时才会被调用。很明显,调试和发布版本对何时需要做出不同的决定。特别是我的猜测是发布版本完全优化了主要内容,因此从未加载过该类。似乎已经决定,由于 main 什么都不做,它可以优化所有内容 - 在这种情况下,这似乎是一个糟糕的决定

    【讨论】:

    • “特别是我的猜测是发布版本完全优化了 main,因此从未加载过类。” 事实并非如此,将 Console.WriteLine("In Main()"); 放在 main你仍然会得到相同的行为,但它会打印“In Main()”
    【解决方案3】:

    开启优化(发布模式)会影响生成的 IL 以及 JITter 的行为。

    您可能会看到 JITter 消除了未使用变量的初始化。

    这解释了 ILDASM/ILASM 行为,以及 IL 没有显着差异的事实。

    我怀疑这种行为是由 CLR 标头中某处的 CorDebugJITCompilerFlags 标志的值控制的。请参阅 Does C# compiler /optimize command line option affect JITter?

    【讨论】:

    • 如果您输入Console.WriteLine("In Main()");,它将在不调用静态函数的情况下打印。所以 Main 并没有被完全淘汰。
    • 所以我明白了。我想只是取消了未使用变量的初始化。
    【解决方案4】:

    经过更多研究,我找到了我正在寻找的答案(感谢 Blogbeard 为我指明了正确的方向)。

    事实证明,当你为 Debug 编译时,默认情况下,生成的程序集装饰有一个 DebuggableAttribute,其“调试模式”为

    DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.Default
    

    显然是这种标志组合似乎关闭了 JIT 优化,导致了我在程序的 Debug 版本中看到的输出。该程序的发布版本具有不同的“调试模式”,允许进行 JIT 优化。

    当然,如果您为 Debug 构建手动设置 AssemblyInfo 中的 DebuggableAttribute(就像我在测试期间所做的那样),您可以覆盖默认行为。

    我确信那里有一些 CLR/JIT 律师可以更详细地解释。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-12
      • 2012-10-07
      • 1970-01-01
      • 2016-12-16
      • 2011-08-12
      • 2018-11-23
      相关资源
      最近更新 更多