C++ 反向Heisenbug-仅当附加了调试器时,单元测试才会失败

C++ 反向Heisenbug-仅当附加了调试器时,单元测试才会失败,c++,unit-testing,debugging,debug-build,C++,Unit Testing,Debugging,Debug Build,我最近修复了我们产品中的一个缺陷,其症状是访问悬空指针导致的访问冲突 为了获得良好的实践,我添加了一个单元测试,以确保bug不会再次出现。在编写单元测试时,我将始终退出我的缺陷修复,并确保单元测试失败,否则我知道它没有正常工作 退出缺陷修复后,我发现我的单元测试仍然通过(不好)。当我将调试器连接到单元测试以查看其通过的原因时,测试失败(即引发异常),我可以中断并观察到调用堆栈与我修复的原始缺陷中的调用堆栈匹配 我没有在VisualStudio2005中修改“异常中断”设置,这确实是一个导致测试线

我最近修复了我们产品中的一个缺陷,其症状是访问悬空指针导致的访问冲突

为了获得良好的实践,我添加了一个单元测试,以确保bug不会再次出现。在编写单元测试时,我将始终退出我的缺陷修复,并确保单元测试失败,否则我知道它没有正常工作

退出缺陷修复后,我发现我的单元测试仍然通过(不好)。当我将调试器连接到单元测试以查看其通过的原因时,测试失败(即引发异常),我可以中断并观察到调用堆栈与我修复的原始缺陷中的调用堆栈匹配

我没有在VisualStudio2005中修改“异常中断”设置,这确实是一个导致测试线束终止的关键Win32异常(即,没有异常处理程序)

例外情况的文本为:

Unhandled exception at 0x0040fc59 in _testcase.exe: 0xC0000005:
Access violation reading location 0xcdcdcdcd.
注意:位置并不总是
0xcdcdcdcd
()。有时是
0x00000000
,有时是另一个地址

这似乎与传统的海森堡相反,在海森堡,当通过调试器观察问题时,问题就消失了。在我的例子中,通过调试器观察它会使问题出现

我最初的想法是,这是由调试器中的时间差暴露出来的竞争条件。但是,当我向代码添加跟踪并与调试器分开运行时,我打印出来的数据向我指示应用程序应该以与在调试器下运行时类似的方式中止。但事实并非如此

有什么建议可以解释是什么原因造成的吗



更新:我正在缩小这个问题的原因。有关更多详细信息,请参阅。如果我找到了,将使用答案更新此问题。

我不知道这是否对您有任何帮助,但我曾经遇到一个错误,如果程序在Visual Studio调试器下运行,或者程序在外部运行,则会出现不同的情况,然后连接了调试器。

几年前,我遇到了相反的情况:只有当调试器未连接时,问题才会出现

事实证明,该代码破坏了先前方法激活的堆栈框架,并且使用调试器引入了一个中间堆栈框架


您可能会遇到类似的情况。

通常,当您删除指向该内存的指针时,VC++调试器会用一些已知值填充堆分配的内存。我已经有一段时间没有使用VisualStudio了,但对我来说0xCDCD可能是一个合理的值。在我看来,在调试器中运行时,应用程序很可能会正常崩溃。在释放模式下运行时,运行时不会浪费时间覆盖解除分配的内存,因此您会有一些时间“幸运”,并且存储在该内存中的数据仍然有效

您可以修改构建设置,以启用在释放模式下使用已知值填充解除分配内存的选项(完成后不要忘记再次将其关闭)。我猜如果你这样做,你的应用程序会在发布模式下崩溃


我知道该值并不总是0xCDCDCD,这可能意味着我错了,或者可能意味着指向悬挂指针的路径不止一条。

我已经找出了此问题的原因-有关详细信息,请参阅

在调试器下运行我的测试工具时,调试环境消耗的内存意味着同一对象的后续分配/解除分配总是分配在内存的不同部分。这意味着,当我的测试工具试图访问一个悬挂的指针时,它使测试崩溃(从技术上讲,这是未定义的行为,但这是测试代码,它似乎做了我需要它做的事情)


从命令行运行我的测试工具时,相同对象的后续分配/解除分配总是重复使用相同的内存块。这种共同的行为意味着,当我访问我的测试用例中实际上是一个悬挂指针的对象时,碰巧悬挂指针仍然指向一个有效的对象。这就是为什么我没有看到崩溃的原因。

我有一次遇到了一个有趣的bug,当附加调试器时,程序崩溃了。我终于发现我有一个线程在调用
sem_wait
时被阻塞了;调试器在线程连接时中断线程,导致
sem\u wait
返回错误
EINTR
。然后线程继续执行,坏事情发生了。可以说,我明白了为什么检查错误代码很重要……我看不出这有什么帮助。这很有趣,但毫无帮助:)@OJ:事实上,我也是。在我的例子中,它可能在程序启动时做了一些小的改变。(我知道C运行时会在初始化时检查调试器,可能会进行一些额外的分配或类似的工作)@Matthew:好吧,这只是一个答案,可能没有帮助。我可能会在短时间内删除它。这听起来像是一个更传统的海森堡。我的理论上应该更容易理解,但这让我很困惑。说清楚,我在这两种情况下都运行调试模式。我是否在运行它时附加了调试器,这决定了缺陷是否是可复制的。我在附加了调试器的情况下获得了崩溃,但是如果我通过命令行(即
c:\>testHarness.exe
)手动执行测试线束,则不会出现崩溃。这不是很可靠。是的,它们被分配到内存的不同部分,但是如果删除对象后,内存区域仍然映射到进程地址空间,该怎么办?你试图建立在非常不稳定的基础上——甚至对于测试来说都太不稳定了。