在调试模式下不存在发​​行版中的错误的常见原因


67

错误和程序异常行为的典型原因是什么仅在发行版编译模式下才会表现出来,而在调试模式下却不会出现?


3
Atleast告诉我们您使用什么语言?
马修·沙利

您的语言/框架是什么?
2009年

6
你们这是怎么回事?这是一个了不起的问题!

同意,这是一个很好的问题。涉及到如此多的微妙之处,它们在最糟糕的时刻(例如,按照定义,当您不再具有完整的调试支持来解决问题时)将您咬在背后。
stusmith

Answers:


32

很多时候,在C ++中的调试模式下,所有变量都被初始化为空,而在发布模式下,除非明确说明,否则不会进行任何初始化。

检查是否有任何调试宏和未初始化的变量

您的程序是否使用线程,然后优化也会在发布模式下引起一些问题。

还要检查所有异常,例如与发布模式没有直接关系,但是有时我们只是忽略一些关键异常,例如VC ++中的mem访问冲突,但至少在其他操作系统(如Linux,Solaris)中,这同样可能是一个问题。理想情况下,您的程序不应捕获诸如访问NULL指针之类的关键异常。


5
我总是发现这种行为完全倒退。调试模式的工作肯定是暴露问题,而不是隐藏问题吗?
walkytalky

这在C ++中很奇怪,但是幸运的是,在C#中,默认情况下所有内容都是NULL初始化的。
Priyank Bolia 09年

5
有一点要注意:通常,调试模式下的变量不是用空值填充的,而是用自然界中很少出现的特定值填充的(例如,对于MSVC为0xCCCCCCCC)。
atzz

1
是的,你说得对,扩大你的答案:priyank.co.in/...
Priyank Bolia

为了扩展atzz的答案,MSVC用0xCC填充统一的堆栈数据,用0xCD填充统一的堆数据,并使用0xDD填充已删除的对象。更神奇的价值
rustyx

19

一个常见的陷阱是在ASSERT中使用带有副作用的表达式。


这会在gcc中产生警告,但Visual Studio不会警告您。一个示例是:assert(MyObj->LoadFromFile(File));。在发行版中,根本不会调用LoadFromFile,并且不会在编译时通知您。
Benlitz 2012年

9

其他差异可能是:

  • 在垃圾收集语言中,收集器在释放模式下通常更具攻击性。
  • 内存的布局通常可能会有所不同;
  • 内存的初始化方式可能不同(例如,可以在调试模式下清零,或者在发布时更积极地重复使用);
  • 可能会提升本地变量在释放中注册值,这可能会导致浮点值出现问题。

2
“用垃圾收集的语言,收集器通常在发布模式下更具攻击性”,这听起来很荒谬。一个对象是否可达。如果gc删除了一个可访问的对象,那简直是错误的,如果它不删除一个不会引起错误的不可访问的对象,那么该对象还是无法访问的。
idmean

是否荒谬,这似乎是事实。很久以前,在.NET 2.0时代,我们有一些托管的C ++代码。我们发现在调试模式下,“ this”似乎被视为GC的根,但在发行版中,即使在运行其自己的实例方法之一的情况下,也可以收集对象,只要该方法代码没有进一步引用其成员即可。那一点。在这种情况下,有些GC::KeepAlive帮助:msdn.microsoft.com/en-us/library/…–
stusmith

2
@idmean一点都不荒谬。创建调试二进制文件的唯一目的是中断执行,查看所有作用域内的变量以及维护代码以实现二进制对称。创建发布是为了提高速度和/或最小尺寸。如果知道不需要它们,可以忽略整个函数调用或变量定义。这将创建一个非常不同的内存空间。
diox8tony

8

过去,我被许多错误所困扰,这些错误在Debug版本中很好,但在Release版本中崩溃。有许多根本原因(当然包括该线程中已经概述的那些原因),并且我被以下所有方面吸引住了:

  • 中的成员变量或成员函数#ifdef _DEBUG,以便在调试版本中类的大小不同。有时#ifndef NDEBUG在发布版本中使用
  • 同样,有一个不同的 #ifdef只有两种版本之一存在
  • 调试版本使用系统库的调试版本,尤其是堆和内存分配功能
  • 发布版本中的内联函数
  • 头文件的包含顺序。这不应该引起问题,但是如果您有类似#pragma pack尚未重置的,则可能会导致令人讨厌的问题。使用预编译的标头和强制包含也会发生类似的问题
  • 缓存:您可能拥有诸如仅在发行版本中使用的缓存之类的代码,或者不同的缓存大小限制
  • 项目配置:调试和发布配置可能具有不同的构建设置(使用IDE时可能会发生这种情况)
  • 由于仅调试代码而导致出现竞争情况,时序问题和其他副作用

我多年来积累的一些技巧,可以帮助您深入了解调试/发布错误:

  • 如果可以,甚至可以编写一个单元测试来捕获异常,请尝试在调试版本中重现异常行为。
  • 考虑一下两者之间的区别:编译器设置,缓存,仅调试代码。尝试暂时最小化这些差异
  • 创建优化版本已关闭的发布版本(这样一来,您更有可能在调试器中获得有用的数据)或优化的调试版本。通过最大程度地减少调试和发行之间的更改,您更有可能隔离导致该错误的差异。

3

是的,如果您有条件地进行编译,则可能会出现计时错误(优化的发行版代码,未优化的调试代码),内存重用与调试堆。


3

它可以,尤其是在C领域中。

原因之一可能是DEBUG版本可能添加代码以检查杂散指针,并以某种方式保护您的代码免于崩溃(或行为不正确)。如果是这种情况,则应仔细检查从编译器获得的警告和其他消息。

另一个原因可能是优化(通常对于发行版为开,对于调试而言为关)。代码和数据布局可能已经过优化,例如在调试程序刚刚访问未使用的内存时,发行版现在正在尝试访问保留的内存甚至指向代码!

编辑:我看到其他人提到了它:当然,如果不以DEBUG模式进行编译,则可能有条件地排除了整个代码段。如果是这样,我希望那实际上是调试代码,而不是对程序本身的正确性至关重要的东西!


3

CRT库函数在调试与发行(/ MD与/ MDd)中的行为有所不同。

例如,调试版本通常会预先填充缓冲区,然后传递给指定的长度以验证您的要求。例子包括strcpy_sStringCchCopy等等。即使串较早终止,您szDest最好是ň长字节!



2

在.NET中,即使您不使用条件编译#if DEBUG器(如),编译器在发布模式下的优化仍然比调试模式下更为自由,这也可能导致仅发布错误。


1

您需要提供更多信息,但是可以。这取决于您的调试版本做什么。您可能会进行日志记录或其他检查,而这些检查或检查没有被编译为发行版。这些仅调试代码路径可能会产生意想不到的副作用,这些副作用会以奇怪的方式更改状态或影响变量。调试版本通常运行速度较慢,因此这可能会影响线程并隐藏竞争条件。对于来自发布编译的直接优化,这也是相同的,但发布编译有可能(尽管现在不太可能)使某些优化工作短路。


1

如果没有更多详细信息,我将假定“不正常”意味着它要么无法编译,要么在运行时引发某种错误。通过#if DEBUG语句或标记有Conditional属性的方法,检查是否有依赖于编译版本的代码。


1

如果您有条件地进行编译,从而使调试代码和发布代码不同,并且代码中存在仅在发布模式下使用的错误,则有可能。

除此之外,这是不可能的。调试代码和发布代码的编译方式有所不同,以及是否在调试器下运行,如何执行代码也有所不同,但是如果这些差异中的任何一个导致了性能差异以外的其他问题,那么问题就一直存在。

在调试版本中,可能不会发生错误(因为时序或内存分配不同),但这并不意味着该错误不存在。可能还有其他与调试模式无关的因素会改变代码的时序,从而导致错误发生与否,但这全都归结为以下事实:如果代码正确,则不会发生错误。在任何情况下

因此,不,调试版本不行,只是因为您可以运行它而不会出现错误。如果在发布模式下运行时发生错误,这不是因为发布模式,而是因为错误从一开始就存在。


1

有一些编译器优化可能会破坏有效代码因为它们过于激进。

尝试在打开较少优化的情况下编译代码。


3
永远不要认为这是编译器的错误。您偶尔会是对的,但请先探索所有其他途径。在我的整个职业生涯中,我只能让一个编译器负责一个bug。我不再使用Metrowerks编译器。
deft_code

我自己还没有看到这样的情况。:)
GeorgSchölly09年


1
@deft_code是的!完全一样的经历!仅一次,带&*#!CodeWarrior!
泰森·雅各布斯

1

在非无效函数中,所有执行路径均应以return语句结尾。

在调试模式下,如果您忘记以return语句结尾这样的路径,则该函数通常默认情况下返回0。

但是,在发布模式下,您的函数可能返回垃圾值,这可能会影响程序的运行方式。


0

这是可能的。如果发生这种情况,并且不涉及任何条件编译,则可以确定程序是错误的,并且仅由于偶然的内存初始化或什至是内存布局而在调试模式下工作!


0

我刚刚体验到,当我调用一个汇编函数时并没有恢复寄存器的先前值。

在“发布”配置中,VS使用/ O2进行编译,这可以优化代码的速度。因此,与上述功能共享的仅映射到CPU寄存器(用于优化)的一些局部变量会导致严重的内存损坏。

无论如何,看看您是否没有在代码中的任何地方间接搞乱CPU寄存器。


0

另一个原因可能是数据库调用。您是否在同一线程中多次保存和更新同一记录,有时是为了更新。由于先前的create命令仍在处理中,因此更新可能失败或没有按预期方式工作,并且对于更新,db调用无法找到任何记录。这不会在调试中发生,因为调试器确保在着陆之前完成所有待处理的任务。


0

我记得前一阵子,当时我们在c / c ++中构建dll和pdb。

我记得这个:

  • 添加日志数据有时会使错误移动或消失,或者使其他错误完全出现(因此实际上不是一种选择)。
  • 其中许多错误与strcpy和strcat中的char分配以及char []等的数组有关...
  • 我们通过运行边界检查器并简单地解决了内存分配/释放问题来清除了一些问题。
  • 很多时候,我们系统地检查了代码并修复了char分配。
  • 我的两分钱是与内存分配和管理以及调试模式和发布模式之间的约束以及差异有关的。

然后继续经历那个周期。

有时,我们暂时将发行版换为dll的调试版本,以便在处理这些错误时不会拖延生产。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.