使用Visual Studio调试器和不使用调试器运行可执行文件之间的区别

17

我正在尝试调试一个问题,即当直接从Visual Studio执行可执行文件时会产生可重复的输出(这是我想要的),但是从命令提示符执行时不会产生可重复的输出。它是单线程应用程序,因此在时间方面不应该有任何奇怪的行为。

有人能列举一下两种环境之间可能存在的差异吗?

我确定实际的可执行文件是相同的——它们都是发布版本,并且运行相同的.exe文件。

以下是环境和结果:

  1. 从命令提示符(cmd)直接运行:不可重复的输出
  2. 使用调试运行Visual Studio(F5):可重复的输出
  3. 在没有调试的情况下从Visual Studio运行(Ctrl-F5):不可重复的输出

我知道工作目录可能不同,但我手动调整它以确保工作目录是相同的。

基于这些结果,看起来“使用调试运行”(即使在发布构建中)以某种方式修复了问题。这指向了一个可能的罪魁祸首吗?使用调试和不使用调试运行可执行文件之间有哪些差异?

解决方案:如最佳答案中所指出的,调试堆是问题所在。问题是我们代码深处有人在初始化数组的部分之前访问了数组的某些部分。他们使用malloc分配了内存,并未将内存初始化为0。我认为调试堆会用某个可重复的值填充数组,而当没有附加调试器时(即从命令行或使用Ctrl-F5运行时)值更随机,有时会导致程序行为的微小偏差。不幸的是,这种微调几乎不容易被注意到,而且涉及的内存在第一帧处理后已经正确重置,但是初始条件已经略有不同,损坏已经造成。这就是混沌理论!感谢指导。

一个很好的调试提示是:编写自定义 malloc,它会立即使用完全随机的数据填充内存。这样,您可以确保在使用之前正确初始化它,否则每次运行时您的结果将(希望是)非常奇怪 - 即使在调试模式下使用调试堆!


1
取决于你的代码。你可能会触发未定义行为,这只会在更高的优化级别下显现出来。 - Alex B
1
但从理论上讲,唯一的区别不是调试器没有连接吗?构建是相同的 - 两次都是发布构建。优化程度没有改变。 - aardvarkk
1
我的问题标题似乎并不准确--只要以两种不同的方式运行应用程序,就可以重现该问题。如果我使用F5运行,我得到了想要的结果。如果我使用Ctrl-F5运行,我就得不到想要的结果。因此,似乎与附加调试器有关的东西会改变可执行文件的行为。这应该消除了工作目录和链接库的问题,不是吗? - aardvarkk
1
@DanPuzey 嗯,更具体地说,我正在生成一个大型文件日志。我加载了一堆文件,对它们进行处理,然后输出一堆结果。当我不使用调试运行时,文件匹配了一段时间,然后结果突然略微“偏离”(仅有微小的差异)。当我使用调试运行时,文件完全匹配。 - aardvarkk
1
我已更新标题以使其更具描述性。 - Mats Petersson
显示剩余12条评论
2个回答

18

Windows堆在调试器下启动的进程中表现不同。为了在调试过程中查找问题,可以向环境变量中添加_NO_DEBUG_HEAP=1以禁用此行为(如此问题中所示)。

此外,您还可以在程序执行早期附加到进程。这时堆将不会进入调试模式。在执行开始处的某个位置添加DebugBreak() 行,使用Ctrl+F5运行,并在被要求时开始调试。


太好了,链接添加得很好。对我来说,这似乎是一个非常可能的问题——某种哨兵值在调试器附加时是可重复的,但在其他情况下只是垃圾数据(因此是不可重复的行为!) - aardvarkk
这指引了我正确的方向。我会编辑问题并提供一些详细信息。谢谢! - aardvarkk
第一个链接无法使用。有没有办法查看这篇文章?不过还是谢谢你的好提示。 - starriet
1
@starriet,我修复了链接。 - Dialecticus

0

嗯,如果不了解你的代码,很难说。但是,我曾遇到一个类似的问题,那是因为程序执行了大量浮点运算(双精度数)。

当处理那些在机器上在数值上几乎无法区分但略有不同的数字时,这个问题就会出现。如果两个双精度数之间的差异小于numeric_limits<double>::epsilon(),它们会被视为机器上的同一个数字。因此,以下类型的表达式可能出现问题:

if (num1==num2)...

或者

if (num1<num2)...
...

可以产生丰富多彩的效果。

这些丰富多彩的效果在调试或发布模式下可能会有所不同。原因是调试/发布运行时库不同。此外,关键的是,编译使用不同的代码优化。命令行调试版本和调试窗口版本(F5)之间的差异也可以通过微妙的优化差异来解释。

如果您正在使用VS,则可以查看C/C++链接器部分的属性菜单中不同编译选项和优化的效果。

为避免这个问题,我建议使用<limits> STL库中的numeric_limits工具。例如,小于运算符的实现应该像这样:

bool operator<(double num1, double num2) {
    double difference=fabs(num1-num2);
    if (difference>numeric_limits<double>::epsilon()) {
        if (num1 < num2) return true;
        return false;
    }
    return false;
}

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