Release构建与Debug构建的运行方式不同的原因是什么

时间:2008-11-23 09:03:29

标签: c++ visual-studio visual-studio-2005

我有一个Visual Studio 2005 C ++程序,它在Release模式下的运行方式与在Debug模式下的运行方式不同。在发布模式下,发生(明显的)间歇性崩溃。在调试模式下,它不会崩溃。 Release构建与Debug构建的工作方式有何不同?

值得一提的是,我的程序相当复杂,并使用多个第三方库进行XML处理,消息代理等...

提前致谢!

11 个答案:

答案 0 :(得分:122)

Surviving the Release Version提供了一个很好的概述。

我遇到过的事情 - 大多数已经提到

变量初始化 到目前为止最常见的。在Visual Studio中,调试版本明确地将已分配的内存初始化为给定值,请参阅例如Memory Values在这里。这些值通常很容易被发现,当用作索引时会导致越界错误,或者当用作指针时会导致访问冲突。但是,未初始化的布尔值是正确的,并且可能导致未初始化的内存错误多年未被检测到。

在Release内部未明确初始化的版本中,它只保留以前的内容。这会导致“有趣的值”和“随机”崩溃,但往往是确定性的崩溃,需要在实际崩溃的命令之前执行明显不相关的命令。这是由第一个命令“设置”具有特定值的内存位置引起的,当内存位置被回收时,第二个命令将它们视为初始化。对于未初始化的堆栈变量而言,这比堆更常见,但后者也发生在我身上。

无论是从visual studio(附加调试器)还是从explorer开始,原始内存初始化在发布版本中也可能不同。这使得“最好”的版本构建错误从未出现在调试器下面。

有效优化在我的exeprience中排名第二。 C ++标准允许进行许多优化,这可能是令人惊讶的,但是完全有效,例如当两个指针别名相同的内存位置时,不考虑初始化顺序,或多个线程修改相同的内存位置,并且您期望线程B看到线程A所做的更改的某个顺序。通常,编译器被指责为这些。没那么快,年轻的yedi! - 见下文

时间由于各种原因(优化,提供线程同步点的日志记录功能,调试代码,如未执行的断言等),发布版本不只是“运行得更快”,也是相对的操作之间的时间变化很大。最常见的问题是竞争条件,但也有死锁和简单的“不同顺序”执行消息/定时器/基于事件的代码。即使它们是计时问题,它们可以在构建和平台上出乎意料地保持稳定,复制“总是有效,除了在PC 23上”。

警卫字节。调试版本通常在所选实例和分配周围放置(更多)保护字节,以防止索引溢出和有时下溢。在极少数情况下,代码依赖于偏移量或大小,例如序列化原始结构,它们是不同的。

其他代码差异某些说明 - 例如断言 - 在发布版本中评估为空。有时他们有不同的副作用。这在宏观技巧中很普遍,如经典(警告:多重错误)

#ifdef DEBUG
#define Log(x) cout << #x << x << "\n";
#else 
#define Log(x)
#endif

if (foo)
  Log(x)
if (bar)
  Run();

在发布版本中,评估为 if(foo&amp;&amp; bar) 对于正常的C / C ++代码和正确编写的宏,这种类型的错误非常罕见。

编译器错误这种情况从未发生过。嗯 - 确实如此,但是如果不这样做,那么你在职业生涯的大部分时间里会更好。在与VC6合作的十年中,我找到了一个我仍然相信这是一个未修复的编译器错误,与几十个模式(甚至数百个实例)相比,对经文的理解不足(a.k.a。标准)。

答案 1 :(得分:6)

在调试版本中,通常会启用断言和/或调试符号。这可能导致不同的内存布局。如果指针错误,数组溢出或类似的内存访问,则在一种情况下访问一个关键的坏内存(例如函数指针),在其他情况下可能只是一些非关键内存(例如,只是一个文档字符串被删除)

答案 2 :(得分:5)

未在发布版本中显式初始化的变量将会或可能不会归零。

答案 3 :(得分:2)

发布版本(希望)运行速度比调试版本快。如果您使用多个线程,您可能会看到更多的交错,或者只是一个线程比其他线程运行得更快,您可能在调试版本中没有注意到。

答案 4 :(得分:2)

发布版本通常是在编译器中启用优化的情况下编译的,而调试版本通常不是。

在某些语言中或使用许多不同的库时,这可能会导致间歇性崩溃 - 尤其是当所选的优化级别非常高时。

我知道gcc C ++编译器就是这种情况,但我不确定微软的编译器。

答案 5 :(得分:2)

我有a similar problem not long ago,最终导致堆栈在发布版本中被区别对待。其他可能不同的事情:

  • VS编译器中的调试版本(即,在清除内存上写入0xcc等)处理内存分配的方式不同。
  • 循环展开和其他编译器优化
  • 指针的下达

答案 6 :(得分:2)

它取决于编译器供应商和使用DEBUG标志编译的库。虽然DEBUG代码永远不会影响正在运行的代码(应该没有副作用),但它有时会这样做。

特别是,变量可能仅在DEBUG模式下初始化,在RELEASE模式下保持未初始化状态。 Visual Studio编译器中的STL在DEBUG和RELEASE模式下是不同的。我们的想法是在DEBUG中完全检查迭代器以检测可能的错误(使用无效的迭代器,例如,如果在检索到迭代器之后发生插入,则向量中的迭代器将失效。)

第三方库也是如此,我能想到的第一个是QT4,如果与创建图形对象的线程不同的线程执行绘制操作,它将使用断言终止程序。

通过所有更改,您的代码和内存占用量在两种模式下都会有所不同。如果该位置是可读的,则指针(读取位置通过数组的末尾)问题可能无法检测到。

断言是为了在DEBUG期间终止应用程序并从RELEASE构建中消失,所以我不认为断言是你的问题。一个流氓指针或访问结束将是我的第一个嫌疑人。

前段时间,某些编译器优化会破坏代码存在问题,但我最近没有阅读过投诉。那里可能存在优化问题,但这不是我的第一个嫌疑人。

答案 7 :(得分:1)

http://www.debuginfo.com/tips/userbpntdll.html

由于在调试版本中添加了保护字节这一事实,您可能能够“安全地”访问对于数组(特别是动态数组)超出范围的内存,但这会导致访问冲突发布版本。此错误可能会被忽视,导致损坏的堆,并可能在与原始错误无关的位置发生访问冲突。

使用PageHeap(或者,如果您安装了调试工具,则可以使用gflags)来发现与损坏的堆相关的错误。

http://support.microsoft.com/?id=286470

答案 8 :(得分:0)

根据我的经验,最常见的原因似乎是配置的不同之处在于发布/构建设置。例如。包含不同的库,或者调试版本的堆栈大小与发布版本不同。

为避免在Visual Studio 2005项目中出现这种情况,我们广泛使用属性表。这样,发布和调试配置可以共享常用设置。

答案 9 :(得分:0)

此帖子以及提供的链接非常有助于修复相关错误。 添加到上面的列表中,调用约定的差异也可能导致此行为 - 它在发布版本中失败,仅对我进行优化。 我声明为__stdcall并定义为__cdecl(默认情况下)。 [奇怪的是,即使在警告级别4 MSVC中也没有选择此警告?]

答案 10 :(得分:0)

在可以在Release模式下执行的优化(在Debug中不能执行)中,复制省略可以产生不同的结果。更具体地说,RVO(返回值优化)取决于构造函数的设计方式。

What are copy elision and return value optimization?