【问题标题】:Is there a difference in source code for release and debug compiled program? [C/C++]发布和调试编译程序的源代码有区别吗? [C/C++]
【发布时间】:2017-01-02 15:27:25
【问题描述】:

我最近对 ​​C++ 编程有了更多的了解,并且一直在运行整个“调试与发布”编译版本。现在我觉得我对已编译代码的发布版本和调试版本之间的一些差异有了相当不错的理解。对于代码的调试版本,编译器不会尝试优化代码,以便您可以运行调试器并逐行逐步执行程序。本质上,编译后的代码在执行方式上与您的源代码非常相似。在发布模式下编译时,编译器会尝试优化程序,使其具有相同的功能,但效率更高。

但是,我很好奇是否存在发布版本和调试版本之间的源代码可能不同的情况。也就是说,当我们提到debug vs release时,我们总是在谈论编译后的代码,还是源代码存在差异?

这个问题是由于我使用专有编程语言工作而产生的,其中不存在正式的逐步调试器,但确实存在串行监视器。因此,我们的很多“调试”与“发布”代码都是通过#defines 实现的,看起来像这样:

#ifdef _DEBUG

    check that error didn't occur...

    SerialPrint("Error occurred")

#endif

所以总结一下我的问题,根据您的 IDE,是否经常有设置来实现我所说明的内容?也就是说,当你尝试编译成调试版本时,它是否可以与源代码中的更改集成?还是发布与调试通常只是指已编译的二进制文件?

谢谢!

【问题讨论】:

  • 看到与您展示的序列相似的序列是很常见的,通常包含在其他宏中。事实上,在 C++ 标准中也有这样的规定:assert
  • 例如,Microsoft Visual C++ 标准库,在调试模式下编译时,使用容器的扩展数据结构(例如std::vector)及其迭代器到 e。 G。帮助检测使用无效的迭代器。
  • 我总是使用-g -fsanitize=address -fno-omit-frame-pointer 进行调试,并使用 gdb 单步执行代码。但是,该程序非常慢并且浪费了大量内存,因此我使用-O3 -flto -fsanitize=address -fno-omit-frame-pointer -DNDEBUG=1 进行编译以进行发布。要修复assert 中止,只需在代码前面加上#include<assert.h> 然后#undef assert 然后#define assert(cond) if(cond)__asm__ volatile("int $0x03"),但请确保在发布期间或不使用gdb 时删除此代码

标签: c++ c debugging release


【解决方案1】:

发布和调试编译程序的源代码有区别吗?

这取决于源代码,以及用于编译库或程序的选项。以下是我知道的一些差异。

断言

最简单的“调试和诊断”是assert。当 NDEBUG 未定义 时,它们生效。断言创建自调试代码,并且在遇到意外情况时它们会捕捉。诀窍是你必须断言一切。在您验证参数和状态的任何地方,您都应该看到一个断言。到处都有断言,您应该会看到 if 来验证参数和状态。

当我看到没有断言的代码库时,我笑了。我对自己说,如果开发人员在调试器下浪费时间,他们手上的时间就太多了。我经常问你为什么不使用断言,他们通常回答如下......

Posix assert 很烂,因为它调用 abort。如果您正在调试程序,那么您通常希望单步执行代码以查看代码如何处理导致assert 触发的负面条件。终止程序不符合“调试和诊断”的目的。这一定是 C/C++ 历史上最愚蠢的决定之一。似乎没有人记得中止的原因(几年前,我试图在各种 C/C++ 标准列表中查找谱系)。

通常您将无用的 Posix 断言替换为更有用的东西,例如在 Linux 上引发 SIGTRAP 或在 Windows 上调用 DebugBreakassert。例如,请参见示例 trap.h。您将 Posix assert 替换为您的断言,以确保您使用的库获得更新的行为(如果它们已经被编译,则为时已晚)。

当 ISC 的 BIND(为 Internet 提供动力的 DNS 服务器)DoS 本身带有断言(他们有自己的断言;他们不使用 Posix 断言)时,我也会笑。有许多 CVE 反对 BIND 的自我造成的 DoS。 DoS'ing 你自己就在那里,“让我们中止正在调试的程序”。

为了完整起见,Microsoft 基础类 (MFC) 曾经有 16,000 或 20,000 个断言来帮助及早发现错误。那是在 1990 年代后期或 2000 年代中期。我不知道今天的状态。

API

存在一些专门为“调试和诊断”而构建的 API。可以使用其他 API,即使它们在生产中使用不一定安全。

前者(特意构建)的一个示例是 Logging 和 DebugPrint API。 Apple 成功地使用它来导出用户的 FileVault 密码和密钥。另见os x filevault debug print

Windows IsBadReadPointerIsBadWritePointer 就是后者(生产环境不安全)的一个例子。它对生产不安全,因为它存在竞争条件。但它通常适合开发,因为您需要额外的审查。

当我们执行安全审查和审计时,我们经常要求/建议删除所有非必要的日志记录;并确保日志级别不能在运行时更改。当应用程序投入生产时,调试时间就结束了。没有理由记录所有内容。

有时可以使用特殊库来帮助调试诊断。想到 Linux 的 Electric Fence 和 Microsoft 的 CRT Library。两者都是带有 API 的内存检查器。在这种情况下,您的链接命令也会有所不同。

选项

有时您需要额外的选项或定义来帮助调试和诊断。我想到了 Glibc++ 和 -D_GLIBCXX_DEBUG。另一种是概念检查,它曾经由定义-D_GLIBCXX_CONCEPT_CHECKS 启用。它的 Boost 代码和它的损坏,所以你不应该使用它。在这些情况下,您的编译标志会有所不同。

我经常嘲笑的另一个问题是缺少 NDEBUG 定义的发布版本。作为政策问题,这包括 Debian 和 Ubuntu。 NSA、GHCQ 和其他 3 个字母的机构感谢他们获取敏感信息(如服务器密钥)、剥离加密(将其写入不受保护的文件),然后泄露敏感信息(向他们发送 Windows 错误报告、Appport 错误)报告等)。

初始化

某些开发环境在未显式初始化值时使用特殊位模式执行初始化。它实际上只是工具的一个特性,比如编译器或链接器。微软的工具浮现在脑海;请参阅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 来调试你的代码。

【讨论】:

    【解决方案2】:

    关于 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()) 之类的。

    请注意,断言可能不是最好的例子,因为有些人喜欢在发布版本中启用它们。

    【讨论】:

      【解决方案3】:

      C++ 标准支持通过the assert macro 进行调试与发布,其行为取决于是否定义了NDEBUG 宏符号。但这并不打算作为应用程序范围的设置。该标准明确指出,每次包含&lt;assert.h&gt;&lt;cassert&gt;时,无论已包含多少次,都会根据NDEBUG的当前定义状态更改assert的有效定义。

      编译器供应商对标准库的实现可能依赖于其他符号。

      并且应用程序框架可能依赖于其他符号,例如_DEBUG,它是 Visual C++ 编译器在指定(调试库)/MTd/MDd 选项时定义的符号。

      【讨论】:

        猜你喜欢
        • 2011-02-10
        • 1970-01-01
        • 1970-01-01
        • 2018-06-29
        • 2021-10-30
        • 2017-08-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多