我最近越来越多地参与C ++编程并继续运行整个'debug vs release'编译版本。现在我觉得我对已编译代码的已发布版本和调试版本之间的一些差异有了相当不错的理解。对于代码的调试版本,编译器不会尝试优化代码,以便您可以运行调试器并逐行执行程序。基本上,编译后的代码与源代码的执行方式非常相似。在发布模式下进行编译时,编译器会尝试优化程序,使其具有相同的功能,但效率更高。
但是,我很好奇是否存在发布版本和调试版本之间的源代码可能不同的情况。也就是说,当我们提到debug和release时,我们总是只讨论编译代码,或者源代码中是否存在差异?
这个问题产生于我使用专有编程语言,其中不存在正式的逐步调试器,但确实存在串行监视器。因此,我们的许多“调试”与“发布”代码都是通过#defines实现的,如下所示:
#ifdef _DEBUG
check that error didn't occur...
SerialPrint("Error occurred")
#endif
总结一下我的问题,根据您的IDE,是否经常有设置来实现我所说明的内容?也就是说,当您尝试编译为调试版本时,它是否可以与源代码中的更改集成?或者发布与调试通常只是引用已编译的二进制文件?
谢谢!
答案 0 :(得分:4)
发布和调试编译程序的源代码是否存在差异?
这取决于源代码以及用于编译库或程序的选项。以下是我所知道的一些差异。
<强> ASSERTS 强>
最简单的&#34;调试和诊断&#34;是assert
。它们在 NDEBUG
未定义时生效。断言创建自调试代码,并在遇到意外情况时捕获。诀窍是你必须断言一切。无论你在哪里验证参数和状态,你都应该看到一个断言。在任何地方都有一个断言,你应该看到一个if
来验证参数和状态。
当我看到没有断言的代码库时,我笑了。我有点对自己说,如果开发人员在调试器下浪费它们,他们就会有太多时间。我常常问为什么你不使用断言,他们通常会回答以下问题......
Posix assert
很糟糕,因为它调用了abort
。如果您正在调试程序,那么通常需要执行代码以查看代码如何处理导致assert
触发的负面条件。通过&#34;调试和诊断&#34;终止程序。目的。它必须是C / C ++历史上最愚蠢的决定之一。似乎没有人回想起中止的原因(几年前我试图追踪各种C / C ++标准清单上的谱系)。
通常你用一些更有用的东西替换无用的Posix断言,比如assert
在Linux上引发SIGTRAP
或在Windows上调用DebugBreak
。例如,请参阅示例trap.h
。您将Posix assert
替换为断言以确保您使用的库获得更新的行为(如果它们已经编译,那么为时已晚)。
当像ISC的BIND(为互联网提供动力的DNS服务器)这样的项目时,我也会大笑它自己的断言(它们有自己的断言;它们不使用Posix断言) 。对于其自身造成的DoS,有很多CVE反对BIND。 DoS&#39;你和#34;让我们中止正在调试的程序&#34;。
为了完整起见,Microsoft基础类(MFC)过去常常使用16,000或20,000个断言来帮助尽早发现错误。那是在20世纪90年代末或21世纪中期。我不知道今天的状况。
<强> 的API 强>
存在一些专门为&#34;调试和诊断&#34; 构建的API。其他API也可以用于它,即使它们不一定安全地用于生产。
前者(有目的地构建)的一个示例是Logging和DebugPrint
API。 Apple成功使用它来出口用户的FileVault密码和密钥。另请参阅os x filevault debug print。
后者(不适合制作)的一个例子是Windows IsBadReadPointer
和IsBadWritePointer
。它不适合生产,因为它有竞争条件。但它通常很适合开发,因为你需要额外的审查。
当我们执行安全审核和审核时,我们经常要求/建议删除所有非必要的日志记录;并确保在运行时无法更改日志记录级别。当应用程序投入生产时,调试时间结束。没有理由记录所有内容。
<强> 库 强>
有时会使用特殊库来帮助调试诊断程序。我想起了Linux的Electric Fence和微软的CRT Library。两者都是带有API的内存检查器。在这种情况下,你的链接命令也会有所不同。
<强> 选项 强>
有时您需要其他选项或定义来帮助进行调试和诊断。想到了Glibc ++和-D_GLIBCXX_DEBUG
。另一个是概念检查,曾经由定义-D_GLIBCXX_CONCEPT_CHECKS
启用。它的Boost代码和它的破坏,所以你不应该使用它。在这些情况下,您的编译标志会有所不同。
我常常嘲笑的另一个是缺少 NDEBUG
定义的发布版本。这包括Debian和Ubuntu作为政策问题。 NSA,GHCQ和其他3个字母的代理商感谢他们获取敏感信息(如服务器密钥),剥离加密(将其写入未受保护的文件),然后解压缩敏感信息(发送Windows错误报告,Apport错误)报告等)。
<强> 初始化 强>
某些开发环境在未显式初始化值时使用特殊位模式执行初始化。它实际上只是工具的一个特性,比如编译器或链接器。微软的工具浮现在脑海中;看When and why will an OS initialise memory to 0xCD, 0xDD, etc. on malloc/free/new/delete? GCC有一个功能请求,但我不认为有任何事情要做。
当我反汇编生产DLL并看到Microsoft调试位模式时,我经常笑,因为我知道他们正在发送调试DLL。我笑了,因为它经常表明Release DLL有一个内存错误,开发团队无法清除。 Adobe因这样做而臭名昭着({3}}并不奇怪,即使他们不提供Apple或Microsoft等操作系统。
#ifdef _DEBUG
check that error didn't occur...
SerialPrint("Error occurred")
#endif
这让我想哭,但你仍然需要在2016年这样做。在Aarch64,X32和S / 390下,GDB被打破了,所以你必须使用printf来调试你的代码
答案 1 :(得分:0)
C ++标准通过the assert
macro支持一种调试与发布,其行为取决于是否定义了NDEBUG
宏符号。但这不是一个应用程序范围的设置。该标准明确指出,每次包含<assert.h>
或<cassert>
时,无论已经包含了多少次,它都会根据assert
的当前定义状态更改NDEBUG
的有效定义{1}}。
编译器供应商对标准库的实现可能依赖于其他符号。
应用程序框架可能依赖于其他符号,例如_DEBUG
,它是指定(调试库)/MTd
或/MDd
选项时由Visual C ++编译器定义的符号。
答案 2 :(得分:0)
关于IDE设置,您可以自由地执行所需操作。是的,某些IDE(如MS Visual Studio)或像CMake这样的工具专门为调试配置添加_DEBUG
宏定义,但如果它缺失,您也可以自己定义一个。此外,_DEBUG
名称不是一成不变的,您可以定义MY_PROJECT_DEBUG
或其他名称。
如果版本和调试版本在主要功能方面保持一致,那么你很好。只要程序生成的最终结果相同,您就可以添加#ifdef _DEBUG
(或其他#ifndef _DEBUG
)中包含的任何代码。
通常的错误是调试代码被认为是可选的,会产生副作用。考虑其他人给出的assert
示例;近似实现如下:
#ifdef NDEBUG
#define assert(x) ((void)0)
#else
#define assert(x) ((x) ? (void)0 : abort())
#endif
请注意assert
在发布模式下如何评估x
(前提是NDEBUG
仅在发布模式下定义)。这意味着如果作为宏参数传递的条件有副作用,则代码在调试和释放模式下的行为会有所不同:
#include <assert.h>
int main()
{
int x = 5;
assert(x-- == 5);
return x; // returns 5 in release mode, 4 in debug mode
}
上面的行为不是你想要的,因为它会改变最终结果。现实世界的代码可能更复杂并且不太明显以引入副作用,例如, assert(SomeFunctionCall())
等。
请注意,断言可能不是最好的例子,因为有些人甚至喜欢在发布版本中启用它们。