发布和调试编译程序的源代码有区别吗? [C/C++]
Is there a difference in source code for release and debug compiled program? [C/C++]
我最近越来越喜欢 C++ 编程,并且运行宁愿进入整个 'debug vs release' 编译版本。现在我觉得我对编译代码的发布版本和调试版本之间的一些差异有了相当不错的理解。对于代码的调试版本,编译器不会尝试优化代码,以便您可以 运行 调试器并逐行单步执行程序。本质上,编译后的代码在执行方式上与您的源代码非常相似。在发布模式下编译时,编译器会尝试优化程序,使其具有相同的功能,但效率更高。
但是,我很好奇是否存在发布版本和调试版本之间的源代码可能不同的情况。也就是说,当我们提到debug vs release时,我们是否总是在谈论编译后的代码,或者源代码是否存在差异?
出现这个问题是因为我使用专有编程语言工作,其中不存在正式的逐步调试器,但确实存在串行监视器。因此,我们的许多 'debug' 与 'release' 代码都是通过 #defines 实现的,看起来像这样:
#ifdef _DEBUG
check that error didn't occur...
SerialPrint("Error occurred")
#endif
总结一下我的问题,根据您的 IDE,是否经常有用于实现我所说明内容的设置?也就是说,当您尝试编译为调试版本时,它能否与源代码中的更改集成在一起?或者发布与调试通常只引用编译的二进制文件?
谢谢!
C++ 标准通过 the assert
macro 支持一种调试与发布,其行为取决于是否定义了 NDEBUG
宏符号。但这并不是一个应用程序范围的设置。该标准明确指出,每次包含 <assert.h>
或 <cassert>
时,无论已包含多少次,它都会根据 [的当前定义状态更改 assert
的有效定义=11=].
编译器供应商对标准库的实现可能依赖于其他符号。
并且应用程序框架可能依赖于其他符号,例如_DEBUG
,这是Visual C++编译器在指定(调试库)/MTd
或/MDd
选项时定义的符号。
关于 IDE 设置,您可以随心所欲。是的,一些 IDEs(如 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())
等。
请注意,断言可能不是最好的例子,因为有些人甚至喜欢在发布版本中启用它们。
Is there a difference in source code for release and debug compiled program?
这取决于源代码,以及用于编译库或程序的选项。以下是我知道的一些差异。
断言
"debugging and diagnostics" 中最简单的是 assert
。当 NDEBUG
未定义 时,它们生效。断言创建自调试代码,并在遇到意外情况时捕捉。诀窍是你必须断言一切。在你验证参数和状态的任何地方,你都应该看到一个断言。到处都有断言,你应该看到一个 if
来验证参数和状态。
当我看到没有断言的代码库时,我笑了。我有点对自己说,如果开发人员在调试器下浪费时间,他们手上的时间就太多了。我经常问你为什么不使用断言,他们通常回答如下...
Posix assert
很糟糕,因为它调用 abort
。如果您正在调试程序,那么您通常希望单步执行代码以查看代码如何处理导致 assert
触发的负面条件。终止程序与 "debugging and diagnostic" 目的不符。这一定是 C/C++ 历史上最愚蠢的决定之一。似乎没有人记得中止的原因(几年前我试图在各种 C/C++ 标准列表中寻找谱系)。
通常你用更有用的东西替换无用的 Posix 断言,比如 assert
在 Linux 上引发 SIGTRAP
或在 DebugBreak
上调用 DebugBreak
Windows。例如,参见示例 trap.h
。您将 Posix assert
替换为您的断言,以确保您使用的库获得更新的行为(如果它们已经被编译,则为时已晚)。
当像 ISC 的 BIND(为 Internet 提供动力的 DNS 服务器)之类的项目本身带有断言的 DoS(他们有自己的断言;他们不使用 Posix 断言)时,我也笑了。有许多 CVE 反对 BIND 的自我造成的 DoS。 DoS'ing 自己就在那里 "lets abort a program being debugged".
为了完整性,Microsoft 基础类 (MFC) 曾经有大约 16,000 或 20,000 个断言来帮助及早发现错误。那是在 1990 年代末或 2000 年代中期。我不知道今天是什么状态。
APIs
有些 API 是专门为 "debugging and diagnostics" 构建的。其他 API 可以用于它,即使它们在生产中使用不一定安全。
前者(特意构建)的一个示例是 Logging 和 DebugPrint
API。 Apple 成功地使用它来导出用户的 FileVault 密码和密钥。另见 os x filevault debug print.
后者的一个例子(对生产不安全)是 Windows IsBadReadPointer
and IsBadWritePointer
。它在生产中不安全,因为它存在竞争条件。但它通常适合开发,因为您需要额外的审查。
当我们执行安全审查和审计时,我们经常ask/recommend删除所有非必要的日志记录;并确保在运行时不能更改日志记录级别。当应用程序投入生产时,调试时间就结束了。没有理由记录所有内容。
图书馆
有时可以使用特殊的库来帮助调试诊断。想到 Linux 的 Electric Fence and Microsoft's CRT Library。两者都是带有 APIs 的内存检查器。在这种情况下,您的 link 命令也会有所不同。
选项
有时您需要额外的选项或定义来帮助调试和诊断。想到 Glibc++ 和 -D_GLIBCXX_DEBUG
。另一个是概念检查,它过去是由定义 -D_GLIBCXX_CONCEPT_CHECKS
启用的。它的 Boost 代码已经损坏,所以你不应该使用它。在这些情况下,您的编译标志会有所不同。
我经常嘲笑的另一个是缺少 NDEBUG
定义的发布版本。作为政策问题,这包括 Debian 和 Ubuntu。 NSA、GHCQ 和其他 3 个字母的机构感谢他们获取敏感信息(如服务器密钥)、剥离加密(将其写入不受保护的文件),然后导出敏感信息(向他们发送 Windows 错误报告、批准错误报告等)。
初始化
一些开发环境在未显式初始化值时使用特殊位模式执行初始化。它实际上只是工具的一个功能,例如编译器或 linker。微软的工具浮现在脑海中;看到 When and why will an OS initialise memory to 0xCD, 0xDD, etc. on malloc/free/new/delete? GCC 有一个功能请求,但我认为没有用它完成任何事情。
当我反汇编生产 DLL 并看到 Microsoft 调试位模式时,我经常笑,因为我知道他们正在运送调试 DLL。我笑是因为它通常表示 Release DLL 存在开发团队无法清除的内存错误。 Adobe 因这样做而臭名昭著(毫不奇怪,Adobe supplies some of the most insecure software on the planet,即使他们不提供像 Apple 或 Microsoft 这样的操作系统)。
#ifdef _DEBUG
check that error didn't occur...
SerialPrint("Error occurred")
#endif
这让我想哭,但你在 2016 年仍然必须这样做。GDB 在 Aarch64、X32 和 S/390 下被(曾经?)破坏了,所以你必须使用 printf 来调试你的代码.
我最近越来越喜欢 C++ 编程,并且运行宁愿进入整个 'debug vs release' 编译版本。现在我觉得我对编译代码的发布版本和调试版本之间的一些差异有了相当不错的理解。对于代码的调试版本,编译器不会尝试优化代码,以便您可以 运行 调试器并逐行单步执行程序。本质上,编译后的代码在执行方式上与您的源代码非常相似。在发布模式下编译时,编译器会尝试优化程序,使其具有相同的功能,但效率更高。
但是,我很好奇是否存在发布版本和调试版本之间的源代码可能不同的情况。也就是说,当我们提到debug vs release时,我们是否总是在谈论编译后的代码,或者源代码是否存在差异?
出现这个问题是因为我使用专有编程语言工作,其中不存在正式的逐步调试器,但确实存在串行监视器。因此,我们的许多 'debug' 与 'release' 代码都是通过 #defines 实现的,看起来像这样:
#ifdef _DEBUG
check that error didn't occur...
SerialPrint("Error occurred")
#endif
总结一下我的问题,根据您的 IDE,是否经常有用于实现我所说明内容的设置?也就是说,当您尝试编译为调试版本时,它能否与源代码中的更改集成在一起?或者发布与调试通常只引用编译的二进制文件?
谢谢!
C++ 标准通过 the assert
macro 支持一种调试与发布,其行为取决于是否定义了 NDEBUG
宏符号。但这并不是一个应用程序范围的设置。该标准明确指出,每次包含 <assert.h>
或 <cassert>
时,无论已包含多少次,它都会根据 [的当前定义状态更改 assert
的有效定义=11=].
编译器供应商对标准库的实现可能依赖于其他符号。
并且应用程序框架可能依赖于其他符号,例如_DEBUG
,这是Visual C++编译器在指定(调试库)/MTd
或/MDd
选项时定义的符号。
关于 IDE 设置,您可以随心所欲。是的,一些 IDEs(如 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())
等。
请注意,断言可能不是最好的例子,因为有些人甚至喜欢在发布版本中启用它们。
Is there a difference in source code for release and debug compiled program?
这取决于源代码,以及用于编译库或程序的选项。以下是我知道的一些差异。
断言
"debugging and diagnostics" 中最简单的是 assert
。当 NDEBUG
未定义 时,它们生效。断言创建自调试代码,并在遇到意外情况时捕捉。诀窍是你必须断言一切。在你验证参数和状态的任何地方,你都应该看到一个断言。到处都有断言,你应该看到一个 if
来验证参数和状态。
当我看到没有断言的代码库时,我笑了。我有点对自己说,如果开发人员在调试器下浪费时间,他们手上的时间就太多了。我经常问你为什么不使用断言,他们通常回答如下...
Posix assert
很糟糕,因为它调用 abort
。如果您正在调试程序,那么您通常希望单步执行代码以查看代码如何处理导致 assert
触发的负面条件。终止程序与 "debugging and diagnostic" 目的不符。这一定是 C/C++ 历史上最愚蠢的决定之一。似乎没有人记得中止的原因(几年前我试图在各种 C/C++ 标准列表中寻找谱系)。
通常你用更有用的东西替换无用的 Posix 断言,比如 assert
在 Linux 上引发 SIGTRAP
或在 DebugBreak
上调用 DebugBreak
Windows。例如,参见示例 trap.h
。您将 Posix assert
替换为您的断言,以确保您使用的库获得更新的行为(如果它们已经被编译,则为时已晚)。
当像 ISC 的 BIND(为 Internet 提供动力的 DNS 服务器)之类的项目本身带有断言的 DoS(他们有自己的断言;他们不使用 Posix 断言)时,我也笑了。有许多 CVE 反对 BIND 的自我造成的 DoS。 DoS'ing 自己就在那里 "lets abort a program being debugged".
为了完整性,Microsoft 基础类 (MFC) 曾经有大约 16,000 或 20,000 个断言来帮助及早发现错误。那是在 1990 年代末或 2000 年代中期。我不知道今天是什么状态。
APIs
有些 API 是专门为 "debugging and diagnostics" 构建的。其他 API 可以用于它,即使它们在生产中使用不一定安全。
前者(特意构建)的一个示例是 Logging 和 DebugPrint
API。 Apple 成功地使用它来导出用户的 FileVault 密码和密钥。另见 os x filevault debug print.
后者的一个例子(对生产不安全)是 Windows IsBadReadPointer
and IsBadWritePointer
。它在生产中不安全,因为它存在竞争条件。但它通常适合开发,因为您需要额外的审查。
当我们执行安全审查和审计时,我们经常ask/recommend删除所有非必要的日志记录;并确保在运行时不能更改日志记录级别。当应用程序投入生产时,调试时间就结束了。没有理由记录所有内容。
图书馆
有时可以使用特殊的库来帮助调试诊断。想到 Linux 的 Electric Fence and Microsoft's CRT Library。两者都是带有 APIs 的内存检查器。在这种情况下,您的 link 命令也会有所不同。
选项
有时您需要额外的选项或定义来帮助调试和诊断。想到 Glibc++ 和 -D_GLIBCXX_DEBUG
。另一个是概念检查,它过去是由定义 -D_GLIBCXX_CONCEPT_CHECKS
启用的。它的 Boost 代码已经损坏,所以你不应该使用它。在这些情况下,您的编译标志会有所不同。
我经常嘲笑的另一个是缺少 NDEBUG
定义的发布版本。作为政策问题,这包括 Debian 和 Ubuntu。 NSA、GHCQ 和其他 3 个字母的机构感谢他们获取敏感信息(如服务器密钥)、剥离加密(将其写入不受保护的文件),然后导出敏感信息(向他们发送 Windows 错误报告、批准错误报告等)。
初始化
一些开发环境在未显式初始化值时使用特殊位模式执行初始化。它实际上只是工具的一个功能,例如编译器或 linker。微软的工具浮现在脑海中;看到 When and why will an OS initialise memory to 0xCD, 0xDD, etc. on malloc/free/new/delete? GCC 有一个功能请求,但我认为没有用它完成任何事情。
当我反汇编生产 DLL 并看到 Microsoft 调试位模式时,我经常笑,因为我知道他们正在运送调试 DLL。我笑是因为它通常表示 Release DLL 存在开发团队无法清除的内存错误。 Adobe 因这样做而臭名昭著(毫不奇怪,Adobe supplies some of the most insecure software on the planet,即使他们不提供像 Apple 或 Microsoft 这样的操作系统)。
#ifdef _DEBUG
check that error didn't occur...
SerialPrint("Error occurred")
#endif
这让我想哭,但你在 2016 年仍然必须这样做。GDB 在 Aarch64、X32 和 S/390 下被(曾经?)破坏了,所以你必须使用 printf 来调试你的代码.