【问题标题】:Conditional compilation of a source file that must be included only in debug builds必须仅包含在调试版本中的源文件的条件编译
【发布时间】:2025-12-14 11:00:01
【问题描述】:

我一直在阅读条件编译的最佳实践,但我没有找到适合我的情况的示例。

我有一个 C 项目,其目标平台是特定设备(不同于 PC)。我有一个源文件,它只包含集成测试等功能。我希望仅在 DEBUG 构建中编译和链接此文件,而不是在 RELEASE 构建中。

我的问题是以下哪个选项更好:

A*.c 文件如下:

Tests.c
---------
#ifndef NDEBUG
// All testing functions
...
#endif

并将该文件包含在 DEBUGRELEASE 构建中。

或者

检查NDEBUG 是否是从项目的Makefile / CMakeLists.txt 中定义的,因此包括提到的源文件。

【问题讨论】:

  • 专业(但无用)的答案是:您的应用程序中根本不应该有任何调试编译器开关。调试与发布由版本控制 100% 处理。在实践中,我也使用这些开关......但混合调试和发布代码是一个坏习惯,因为它很容易在发布中滑倒并留下调试遗留物,尤其是在维护期间。
  • @Lundin 我不同意。如果您有相同事物的两个副本,它们肯定会不同步。最好的方法是使用像 ASSERT() 这样的宏,它不会自动编译发布的调试代码。
  • @shawnhcorey 将主干中的更改合并到调试分支中相当简单。对于某些系统,要求在生产版本中不存在调试代码。一般来说,阅读带有大量编译器开关的代码是一件痛苦的事。说“只使用断言”很容易,但通常你想要的东西比单行布尔测试更复杂。
  • @Lundin 然后为它写一个宏。不要忘记ASSERTs 也是文档。
  • @shawnhcorey 我通常的方法是将错误处理程序集成到程序本身中,然后使用它进行调试。但是,这是否可行当然取决于应用程序的性质。对于几乎所有的嵌入式系统,它都是。

标签: c makefile macros embedded conditional-compilation


【解决方案1】:

我对此的个人意见(!):

第一种方法——#ifndef NDEBUG——更可取。

一开始是cc *.c

然后是添加适当的选项。

然后是构建系统,它会找出哪些*.c 文件实际上需要重新编译,并让您不必记住哪些适当的选项

然后出现了更复杂的构建系统,它可以为您找出合适的选项。

随着时间的推移,构建系统变得更加智能,并且可以拥有重要的逻辑。但是,我认为这种智能应该继续专注于它们的主要功能(见上文),并且 - 最后 - cc *.c 应该仍然在做它的工作。

构建系统已过时或被替换。下一个人可能甚至不知道您选择的构建系统;他应该仍然能够从您的项目中脱颖而出,而无需深入研究您的构建系统的逻辑。

设置/检查NDEBUG 是C,任何对该语言(和<assert.h>)稍有了解的人都会立即认出您打算在那里做什么。

从你的构建系统中弄清楚为什么一个特定的源文件应该只包含在一个特定的构建类型中而不应该包含在其他构建类型中,并不是那么直观,并且当有人站出来扔掉你的CMakeLists.txt 时可能会完全迷失因为他更喜欢 Jam 并从头开始构建它。那人可能最终会想知道为什么所有这些测试都弄乱了他的发布代码,以及为什么你不够聪明,无法让它们仅调试(没有意识到你在构建系统中确实这样做了) .

【讨论】:

  • 根据我的经验,最终这一切都取决于项目/代码库的大小。当#ifdef 开关开始变得不足时,存在一个大小阈值。此外,有人可以删除构建文件的论点并不那么令人信服。构建文件应该受到源代码控制,对于大型项目,构建系统与源代码本身同样重要。单独使用ifdefs 需要大量的纪律来避免ifdef-hell
【解决方案2】:

我认为第一种方法 (#ifndef NDEBUG) 更好,为什么?

因为我相信每个代码的封装或依赖都应该在尽可能低的级别上执行。如果构建系统可以在不知道该文件仅为 DEBUG 构建编译的情况下继续其工作,那么我们刚刚成功删除了项目中的依赖项。

在最后一个参数的基础上,如果将来有其他项目需要此文件,您将有两个有条件的地方,而不是一个地方。

【讨论】:

  • 问题(在我看来)是您引入了一个编译器必须构建的文件,即使它不需要。当您拥有数十个文件时,这并不是什么大问题,但是成百上千的文件开始构建越来越长的版本。如果你愿意的话,一千次剪纸就死了。
  • @RussSchultz,好吧,如果你有成百上千的 #ifdef-ed 源文件,我猜你应该将一些代码提取到共享资源中,无论是库或类似的东西。我想不出任何合理的情况会发生这种情况......
  • 完全正确。您在构建系统中做一些事情来隔离该捆绑的东西,以便它被包含或不包含。这是我的观点。
【解决方案3】:

其实我不明白为什么只选一个?在我看来,这两个选项可以并且应该一起使用。

如果某个文件在发布构建中完全不需要,那么您有充分的理由将其完全排除在构建过程之外。但在源代码中加入一些预处理器保护(#ifdef#error)无论如何都非常有用。

【讨论】:

    最近更新 更多