繁体   English   中英

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

[英]Common reasons for bugs in release version not present in debug mode

仅在发布编译模式下出现但在调试模式下不出现的错误和异常程序行为的典型原因是什么?

很多时候,在 C++ 的调试模式下,所有变量都是空初始化的,而除非明确说明,否则在发布模式下不会发生同样的情况。

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

您的程序是否使用线程,那么优化也会导致发布模式出现一些问题。

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

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

过去,我被许多错误所困扰,这些错误在 Debug 版本中很好,但在 Release 版本中会崩溃。 有许多根本原因(当然包括本主题中已经总结的那些),并且我已经被以下所有原因所吸引:

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

我多年来积累的一些用于深入调试/发布错误的技巧:

  • 如果可以,尝试在调试版本中重现异常行为,甚至更好的是,编写一个单元测试来捕获它
  • 想想两者之间的不同之处:编译器设置、缓存、仅调试代码。 尝试暂时最小化这些差异
  • 创建关闭优化的发布版本(这样您更有可能在调试器中获得有用的数据)或优化的调试版本。 通过最小化调试和发布之间的更改,您更有可能能够隔离导致错误的差异。

其他差异可能是:

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

是的!,如果您有条件编译,则可能存在计时错误(优化的发布代码节,未优化的调试代码)、内存重用与调试堆。

它可以,特别是如果您在 C 领域。

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

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

编辑:我看到其他人提到了它:当然,如果不在调试模式下编译,您可能有整个代码部分被有条件地排除在外。 如果是这样,我希望这真的是调试代码,而不是对程序本身的正确性至关重要的东西!

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

例如,调试版本通常会预填充您传递给指定长度的缓冲区以验证您的声明。 例子包括strcpy_sStringCchCopy ,等等。即使琴弦较早终止,您szDest最好是N久字节!

当然,例如,如果您使用类似的结构

#if DEBUG

//some code

#endif

在非 void 函数中,所有执行路径都应以 return 语句结束。

在调试模式下,如果您忘记用 return 语句结束这样的路径,那么该函数通常默认返回 0。

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

您需要提供更多信息,但是是的,这是可能的。 这取决于您的调试版本的作用。 您可能会进行日志记录或额外检查,这些检查不会被编译到发布版本中。 这些仅用于调试的代码路径可能会产生意想不到的副作用,这些副作用会以奇怪的方式改变状态或影响变量。 调试构建通常运行速度较慢,因此这可能会影响线程和隐藏竞争条件。 与发布编译的直接优化相同,发布编译可能(尽管现在不太可能)将某些内容作为优化短路。

没有更多细节,我会假设“不正常”意味着它要么不编译,要么在运行时抛出某种错误。 检查您是否有依赖编译版本的代码,通过#if DEBUG语句或通过标记有Conditional属性的方法。

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

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

尝试在启用较少优化的情况下编译您的代码。

这是可能的,如果你有条件编译,使得调试代码和发布代码不同,并且代码中有一个只在发布模式下使用的错误。

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

在调试版本中,错误可能不会发生(因为时间或内存分配不同),但这并不意味着错误不存在。 也可能还有其他与调试模式无关的因素改变了代码的时序,导致错误发生与否,但归根结底,如果代码正确,错误就不会发生在任何情况下。

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

这是可能的。 如果它发生并且不涉及条件编译,那么您可以非常确定您的程序是错误的,并且仅由于偶然的内存初始化甚至内存布局而在调试模式下工作!

我刚刚在调用未恢复寄存器先前值的汇编函数时遇到过这种情况。

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

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

我记得不久前我们在 c/c++ 中构建 dll 和 pdb。

我记得这个:

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

然后继续经历那个循环。

我们有时会临时交换 dll 的调试版本的发行版,以免在处理这些错误时推迟生产。

另一个原因可能是数据库调用。 您是否在同一线程中多次保存和更新同一记录,有时是为了更新。 更新失败或未按预期工作可能是因为之前的创建命令仍在处理中,而对于更新,db 调用未能找到任何记录。 这不会在调试中发生,因为调试器确保在登陆前完成所有挂起的任务。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM