65.9K
CodeProject 正在变化。 阅读更多。
Home

调试发布模式问题

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (75投票s)

1999年12月10日

viewsIcon

423416

一篇关于如何调试发布版本应用程序的经典文章。

概述

如果你正在阅读本文,你很可能刚刚发现自己犯了一个经典的开发者错误。你一直以为你的调试模式下的应用程序在发布模式下也能正常工作。现在你知道,调试代码在发布模式下并不一定能正常工作。

如果你和我一样,你可能已经开发了好几个月,从未构建或测试过发布模式版本,直到现在才发现这个问题。这就引出了我们的第一条规则

规则 1:务必经常测试你应用程序的调试模式和发布模式版本。

你发现发布模式问题的等待时间越长,解决起来就越困难。经常构建发布模式版本(至少每周一次)并进行测试。如果你的时间很紧,这将为你节省很多麻烦。

不要在发布模式下移除必需的代码

这听起来很明显,但很容易无意中这样做。有几个常用的宏在发布模式下编译时会被完全移除。例如,ASSERT 宏和 TRACE 宏在发布构建中会被悄悄移除。任何可能需要的、本应在 ASSERT 或 TRACE 命令中执行的代码也会被移除。

这就引出了我们的第二条规则

规则 2:务必非常小心,不要将必须执行的代码放在发布模式下会被编译掉的地方。

规则 3:不要使用规则 2 作为从代码中移除 ASSERT 的理由。ASSERT 是一个非常有价值的工具,但像任何工具一样,它也可能被误用。请谨慎使用它们。

让调试模式更像发布模式

如果你的发布模式问题是由代码被意外移除引起的,那么很可能你可以在调试模式下重现这个问题。

有些问题可能是由用于编译调试和发布项目的预处理器符号的差异引起的。你可以通过进行以下更改来使你的调试编译更像发布编译

  • 在“项目设置”(Alt-F7)的“C++/C”选项卡下,将类别设置为“常规”,然后将“_DEBUG”预处理器定义更改为“NDEBUG”。
  • 在“C++/C”选项卡下,将类别设置为“预处理器”,然后在“未定义的符号”编辑框中添加预处理器定义“_DEBUG”。
  • 使用“全部重新生成”进行重新编译

如果这能在你的调试版本中重现问题,请对你的代码进行以下更改

  • 将所有 ASSERT() 的出现替换为 VERIFY()。
  • 查找任何包含在“#ifdef _DEBUG”包装器中的代码,这些代码可能在发布版本中需要。
  • 查找 TRACE() 中的代码是否必须执行。TRACE 宏和 ASSERT 宏一样,在发布模式下会被编译掉。

如果这能解决你在调试模式下遇到的问题,请尝试重新构建发布模式版本。这次很可能就会成功。

错误的假设

你是否假设你的变量和分配的数据已初始化为某个值(可能是 0)?你是否假设你代码中引用的所有资源都存在于你的项目中?这些错误的假设可能导致调试模式和发布模式之间出现问题。

规则 4:除非你在代码中显式初始化变量,否则切勿假设它们已初始化。这包括全局变量、自动变量、malloc 的数据和 new 的数据。

规则 5:确保在从资源文件中删除资源后,所有对这些资源的引用都已移除。这包括 resource.h 中的引用。

变量和已分配的内存通常在调试版本和发布版本中初始化方式不同。如果你假设变量在声明时就被清零,这可能会导致 Win9x 上的发布模式出现奇怪的行为。你还可能引入仅限 Win9x 的 bug。出于安全原因,WinNT 会在所有内存使用前将其清零。

如果你引用了已从资源文件中删除的资源,你的调试版本可能正常工作,但发布版本可能会崩溃。

你是否听取了编译器的建议?

你的编译器警告级别设置为多少?通常,警告级别会被设置为一个非常低的水平,以减少构建中的噪音。

提高编译器警告级别可能会暴露你的问题。我总是将编译器警告设置为“级别 3”或“级别 4”,具体取决于我有多么“受虐”。遍历并解决所有编译器警告(在发布代码之前这样做也是一个非常好的主意)。这可以暴露初始化问题以及许多其他可能导致你问题的潜在错误。

规则 6:在开始一个项目时,将警告级别设置为“级别 3”或“级别 4”,并确保在代码签入之前,代码能无警告地编译。

最后的手段

在发布模式下进行调试

我曾多次听到我的 VC++ 开发人员同行重复一个迷思:在发布模式下无法进行调试。幸运的是,这是一个迷思。

规则 7:当其他方法都失效时,尝试在发布模式下进行调试。

在发布模式下进行调试是可能的。第一步是开启符号

  • 在“项目设置”(Alt-F2)的“C++/C”选项卡下,将类别设置为“常规”,然后将“调试信息”设置更改为“程序数据库”。
  • 在“链接”选项卡下,勾选“生成调试信息”选项卡。
  • 使用“全部重新生成”进行重新编译

这将使你能够在发布版本中看到符号。你还可以考虑以下几点

  • 在调试发布版本时,你可能需要禁用优化设置(尽管这并非绝对必要)。
  • 如果你在设置断点时遇到困难,可以使用“__asm {int 3}”命令使程序在该行停止(调试完成后务必从程序中删除这些行)。

在发布模式下调试有几个限制。

  • 最令人恼火的是,你无法轻松地查看 MFC,因为你没有包含 MFC DLL 的符号。
  • 你必须为你希望在发布应用程序中调试的所有库和 DLL 添加发布版本的符号。

编译器是否生成了错误的代码?

有可能你发现了 VC++ 中的代码生成问题。坦白说,人们常常过早地下结论。要测试这一点,只需在发布模式下关闭优化。

如果这解决了你的问题,你可能使用了一些可疑的 C++ 编码实践。信不信由你,有些 C++ 代码的求值方式可能存在歧义,或者根本就是错误的,但在某些情况下似乎有效。例如,以下代码在调试模式下似乎“正确”工作,但在发布模式下却不行

#include <stdio.h>

int* func1()
{
&#9;int retval = 5;
&#9;return &retval;
}

int main(int argc, char* argv[])
{
&#9;printf("%d\n", *func1());
&#9;return 0;
}

我相信如果你有创意,或者阅读过一些 PC-lint 的广告,你就能想出类似的例子。

规则 8:如果在发布模式下关闭优化使你的代码能够工作,请假定这是由于糟糕的编程实践造成的。这意味着你应该检查代码中关于堆栈、指针等的错误假设。这也意味着你的代码能够工作本身可能只是侥幸,应该在它将来带来麻烦(比如一个愤怒的客户因为神秘的程序崩溃而向你的上级抱怨)之前修复。

规则 9:如果你检查了代码但没有发现问题,你应该尝试开启优化器的某些元素来帮助缩小问题的范围。

顺便说一句,上面展示的问题代码会被 C++ 编译器注意到。如果你遵循了规则 6,你就不会在问题发生之前就解决它了。

根据我的个人经验,我很少遇到真正由糟糕代码生成的真正问题。我使用 VC++ 6.0 的经验是,编译器在生成代码时偶尔会断言(尤其是在使用模板时),但我没有遇到过与糟糕代码生成相关的代码问题。安装服务包 3 似乎解决了我在先前版本中看到的代码生成断言问题。

最终想法

我发现通过在我的日常工作中增加一些严谨性,我能够避免大多数最后一刻的调试模式与发布模式问题。这是我的做法。

  1. 签出要更改的文件。
  2. 进行更改,解决所有警告,然后构建调试版本和发布版本。
  3. 对两个版本进行“烟雾测试”(这意味着我单步调试每一行新代码,然后我真正试图在我的工作区域中破坏代码)。
  4. 修复发现的所有问题。
  5. 签入我的更改。
  6. 使用所有当前签入的代码进行完整构建(这确保我的代码与其他人的代码正确集成)。
  7. 再次对我的代码进行“烟雾测试”。
  8. 修复发现的任何其他问题(这可能需要暂时回退已签入的代码)。

这样做似乎解决了我在项目结束时遇到的绝大多数集成意外情况。

© . All rights reserved.