调试Debug和Release版本之间差异的最佳实践和工具?

我已经看过post谈论可能导致 Debug和Release版本之间存在差异的内容,但我认为没有人从开发的角度解决了什么是解决问题的最有效方法。

我在Release版本中出现错误而不是在Debug中出现的第一件事就是我通过valgrind运行我的程序,希望能有更好的分析。 如果没有显示任何内容, – 而这在我之前发生过 – 那么我会尝试各种输入,希望在Debug版本中也能显示bug。 如果失败了,那么我会尝试跟踪更改,以找到两个版本在行为上分歧的最新版本。 最后我猜我会诉诸打印声明。

当Debug和Release版本不同时,是否有最佳的软件工程实践可以有效地进行调试? 此外,有哪些工具在比​​valgrind更基础的层面上运行以帮助调试这些案例?

编辑:我注意到很多回复表明一些一般的良好做法,如unit testing和回归测试,我同意这些做法很适合发现任何错误。 但是,是否有针对此Release与Debug问题专门定制的内容? 例如,是否有静态分析工具这样的东西说“嘿,这个宏或这个代码或这个编程实践是危险的,因为它有可能导致你的Debug / Release版本之间的差异?”

从调试的角度来看,两种配置的存在是一个问题。 适当的工程设计将使地面和空中的系统以相同的方式运行,并通过减少系统分辨差异的方式来实现这一目标。

调试和发布版本在3个方面有所不同:

  • _DEBUG定义
  • 优化
  • 不同版本的标准库

我经常工作的最佳方式是:

  • 禁用性能不重要的优化。 调试更重要。 最重要的是禁用函数自动内联,保持标准堆栈帧和变量重用优化。 这些烦恼调试最多。
  • 监视依赖于DEBUG定义的代码。 切勿使用仅调试断言或对DEBUG定义敏感的任何其他工具。
  • 默认情况下,编译和工作/发布。

另一个“最佳实践”,或者更确切地说是两个的结合:进行自动化unit testing,以及分而治之。

如果您有模块化应用程序,并且每个模块都有良好的unit testing,那么您可以快速隔离错误的部分。

当我遇到只在发布中发生的错误时,我一直在寻找的第一件事是在我正在处理的代码中使用未初始化的堆栈变量。 在Windows上,调试C运行时将自动将堆栈变量初始化为已知位模式,0xcdcdcdcd等。 在发布中,堆栈变量将包含最后存储在该内存位置的值,这将是一个意外的值。

其次,我将尝试确定调试和发布版本之间的不同之处。 我看一下编译器在DebugRelease配置中传递的编译器优化设置。 您可以看到这是Visual Studio中编译器设置的最后一个属性页。 我将从发布配置开始,并一次更改传递给编译器的命令行参数,直到它们与用于在调试中编译的命令行匹配。 每次更改后,我运行程序并重现错误。 这通常会导致我进入导致错误发生的特定设置。

第三种技术可以是采用行为不正常的function并使用预处理器禁用它周围的优化。 这将允许您使用在debug中编译的特定函数在发行版中运行该程序。 以这种方式构建的程序的行为将帮助您了解有关该错误的更多信息。

 #pragma optimize( "", off ) void foo() { return 1; } #pragma optimize( "", on ) 

根据经验,问题通常是堆栈初始化,内存分配器中的内存清理或奇怪的#define指令导致代码编译错误。

最明显的原因是使用#ifdef#ifndef指令关联DEBUG或在两个构建之间发生变化的类似符号。

在进入调试道路之前(这不是我个人的好玩的想法),我会检查两个命令行并检查哪个标志在一种模式下传递而不是另一种模式,然后grep我的代码以获取此标志并检查它们的用途。

我想到的一个特殊问题是宏:

 #ifdef _DEBUG_ #define CHECK(CheckSymbol) { if (!(CheckSymbol)) throw CheckException(); } #else #define CHECK(CheckSymbol) #endif 

也被称为软断言。

好吧,如果你用一个具有副作用的函数调用它,或者依靠它来保护一个函数(合同执行)并以某种方式捕获exception,它会抛出调试并忽略它……你会看到发布中的差异:)

当调试和发布不同时,它意味着:

  1. 你的代码取决于_DEBUG或类似的宏(在编译调试版本时定义 – 没有优化)
  2. 你的编译器有一个优化错误(我见过几次)

您可以轻松地处理(1)(代码修改)但是(2)您必须隔离编译器错误。 隔离bug之后,你会做一些“代码重写”,让编译器生成正确的二进制代码(我这样做了几次 – 最困难的部分是隔离bug)。

我可以说,当为发布版本启用调试信息时,调试过程可以工作……(尽管由于优化,您可能会在运行时看到一些“奇怪的”跳转)。

您需要为您的应用程序进行一些“黑盒”测试 – 在这种情况下,valgrind是一种解决方案。 这些解决方案可帮助您找到发布和调试之间的差异(这非常重要)。

最好的解决方案是设置类似自动化unit testing的东西来彻底测试应用程序的所有方面(不仅仅是单个组件,而是使用应用程序的真实世界测试,与普通用户对所有依赖项的使用方式相同)。 这使您可以立即知道何时引入了仅发布的错误,这可以让您很好地了解问题所在。

积极监控和寻找问题的良好做法胜过任何工具,以帮助您在发生问题后很长时间内解决问题。

然而,当你遇到其中一个案例时,为时已晚:太多的构建已经过去,无法一致地再现等等,那么我不知道任何一个工具。 有时摆弄你的发布设置可以提供一些关于错误发生原因的一些见解:如果你可以消除突然使bug消失的优化,那么可以为你提供一些有用的信息。

仅发布的错误可以分为不同的类别,但最常见的错误(除了滥用断言之外)是:

1)未初始化的内存。 我使用这个术语而不是未初始化的变量,因为变量可能被初始化但仍然指向尚未正确初始化的内存。 为此,像Valgrind这样的内存诊断工具可以提供帮助。

2)计时(例如:竞赛条件)。 这些可能是调试的噩梦,但有一些multithreading分析器和诊断工具可以提供帮助。 我无法提出任何建议,但有Coverity Integrity Manager就是一个例子。