程序在调试时正确运行但不是吗?

时间:2010-02-04 17:03:49

标签: c++ debugging heisenbug

  

可能重复:
  Common reasons for bugs in release version not present in debug mode

有时我会遇到一些奇怪的情况,即程序在正常运行时运行不正确,它会弹出终止对话框,但在调试时正确。当我想使用调试器查找代码中的错误时,这确实让我很沮丧

你遇到过这种情况吗?为什么?

更新

要证明有逻辑原因导致这种令人沮丧的情况

我认为一个很大的可能性是堆访问volidation。我曾经写过一个分配一个小缓冲区的函数,但后来我走出了boudary.It会在gdb,cdb等中运行正常(我不知道为什么,但是它运行正常);但在正常运行时异常终止。

我正在使用C ++。

我不认为我的问题与上述问题重复。

那个是发布模式和调试模式之间的比较,但我的是在调试和不调试之间,它有一个单词 heisenbug ,正如许多其他人所说的那样。

感谢。

11 个答案:

答案 0 :(得分:21)

您有heisenbug

调试器可能正在初始化值

某些环境将变量和/或内存初始化为已知值,例如调试版本中的零,但不是版本构建。

可以使用优化构建版本

现代编译器很好,但它可能假设发生优化代码的功能与非优化代码不同。 编辑:目前,编译器错误罕见。如果你发现自己认为自己有一个想法,那就先消除所有其他想法。

heisenbugs还有其他原因。

答案 1 :(得分:3)

这是一个常见的陷阱,可以导致Heisenbug(爱这个名字!):

    // Sanity check - this should never fail
    ASSERT( ReleaseResources() == SUCCESS);

在调试版本中,这将按预期工作,但在版本构建中忽略ASSERT宏的参数。通过忽略,我的意思是不仅不会报告结果,而且根本不会评估表达式(即不会调用ReleaseResources())。

这是一个常见的错误,这就是为什么除了VERIFY()宏之外,Windows SDK还定义了ASSERT()宏。如果参数的计算结果为false,它们都会在运行时在调试版本中生成一个断言对话框。但是,对于发布版本,它们的行为是不同的。这是区别:

    ASSERT( foo() == true );  // Confirm that call to foo() was successful
    VERIFY( bar() == true );  // Confirm that call to bar() was successful

在调试版本中,上述两个宏的行为相同。但是,在发布版本中,它们基本上等同于:

    ;       // Confirm that call to foo() was successful
    bar();  // Confirm that call to bar() was successful



顺便说一句,如果您的环境定义了ASSERT()宏,而不是VERIFY()宏,则可以轻松定义自己的宏:

    #ifdef _DEBUG
       // DEBUG build: Define VERIFY simply as ASSERT
    #  define VERIFY(expr)  ASSERT(expr)
    #else
       // RELEASE build: Define VERIFY as the expression, without any checking
    #  define VERIFY(expr)  ((void)(expr))
    #endif

希望有所帮助。

答案 2 :(得分:2)

当使用调试器时,有时内存被初始化(例如,零),而没有调试会话,内存可以是随机的。这可以解释你所看到的行为。

答案 3 :(得分:2)

您有对话框,因此应用程序中可能存在线程。如果有线程,则存在竞争条件的可能性。

假设您的主线程初始化另一个线程使用的结构。当您在调试器中运行程序时,可以在另一个线程之前调度初始化线程,而在您的实际情况中,在另一个线程实际初始化之前调度使用该结构的线程。

答案 4 :(得分:2)

显然stackoverflow不会让我发布只包含一个单词的回复:)

VALGRIND

答案 5 :(得分:1)

除了JeffH所说的,您还必须考虑部署的计算机(或服务器)是否具有相同的环境/库/ whatever_related_to_the_program。

如果使用其他条件进行调试,有时很难正确调试。

乔瓦尼

答案 6 :(得分:1)

来自Raymand Zhang的heisenbug的一个真实世界的例子。

/*-------------------------------------------------------------- 
GdPage.cpp : a real example to illustrate Heisenberg Effect
   related with guard page by Raymond Zhang, Oct. 2008
--------------------------------------------------------------*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
  LPVOID lpvAddr;               // address of the test memory
  lpvAddr = VirtualAlloc(NULL, 0x4096,
                         MEM_RESERVE | MEM_COMMIT,
                         PAGE_READONLY | PAGE_GUARD);
  if(lpvAddr == NULL) 
  {
    printf("VirtualAlloc failed with %ld\n", GetLastError());
    return -1;
  }

  return *(long *)lpvAddr;  
}

程序将异常终止,无论是使用Debug还是Release进行编译,因为 通过指定PAGE_GUARD标志将导致:

  

该地区的网页成为警卫   页面。任何尝试阅读或   写入保护页面导致   系统提出STATUS_GUARD_PAGE   异常并关闭防护页面   状态。因此,警卫页面充当了   一次性访问警报。

因此,在尝试访问STATUS_GUARD_PAGE时,您将获得*lpvAddr。但是,如果您使用调试器加载程序并观察*lpvAddv或执行最后一个语句return *(long *)lpvAddr汇编汇编时,调试器会预测保护页面以确定*lpvAddr的值。因此,在我们访问*lpvAddr之前,调试器会为我们清除警报警报。

答案 7 :(得分:1)

此外,调试器可能会在分配的内存周围添加一些填充,从而改变行为。这让我感到很多次,所以你需要注意它。在调试中获得相同的内存行为非常重要。

对于MSVC,可以使用env-var _NO_DEBUG_HEAP=1禁用此功能。 (调试堆很慢,所以如果您的调试运行速度非常慢,这会有所帮助..)。

获得相同功能的另一种方法是在调试器外部启动进程,以便正常启动,然后在main中的第一行等待并附加调试器进行处理。这适用于“任何”系统。只要你在主要之前没有崩溃。 (你可以等待一个静态的pre-mani构造对象然后......)

但我在这件事上没有gcc / gdb的经验,但事情可能与此类似......(评论欢迎。)

答案 8 :(得分:0)

您使用的是哪种编程语言。某些语言(如C ++)在发布和调试版本之间的行为略有不同。对于C ++,这意味着当您在调试版本中声明var(例如int i;)时,它将初始化为0,而在发布版本中,它可能会占用任何值(无论存储在其内存位置中的是什么)前)。

答案 9 :(得分:0)

一个重要原因是调试代码可能会定义_DEBUG宏,可以在代码中使用它来在调试版本中添加额外的东西。

对于多线程代码,优化可能会影响可能影响竞争条件的排序。

我不知道调试代码是否在堆栈上添加代码来标记堆栈帧。堆栈上的任何额外内容都可能隐藏缓冲区溢出的影响。

尝试使用与发布版本相同的命令选项,只需添加-g(或等效的调试标志)。 gcc允许调试选项和优化选项。

答案 10 :(得分:0)

如果您的逻辑依赖于系统时钟的数据,您可能会看到严重的探测效果。如果您进入调试器,显然会影响时钟函数(如timeGetTime())返回的值。如果您的程序执行时间较长,情况也是如此。正如其他人所说,debug build会插入NOOP。此外,只需在调试器下运行(不会遇到断点)可能会减慢速度。

可能发生这种情况的一个例子是基于已用系统时间的可变时间步长的实时物理模拟。这就是为什么有这样的文章: http://gafferongames.com/game-physics/fix-your-timestep/