我的C标题通常类似于以下样式,以避免多重包含:
#ifndef <FILENAME>_H
#define <FILENAME>_H
// define public data structures / prototypes, macros etc.
#endif /* !<FILENAME>_H */
然而,在他的Notes on Programming in C中,Rob Pike对头文件提出了以下论点:
有一个涉及
#ifdef
的小舞蹈可以防止文件被读取两次,但实际上通常做错了 -#ifdef
在文件本身,而不是文件包括它。结果往往是成千上万的不必要的代码行通过词法分析器,这是(在良好的编译器中)最昂贵的阶段。
一方面,派克是我唯一真正钦佩的程序员。另一方面,将多个#ifdef
放在多个源文件中而不是将一个#ifdef
放在单个头文件中会感觉不必要的尴尬。
处理多重包含问题的最佳方法是什么?
答案 0 :(得分:11)
在我看来,使用需要较少时间的方法(这可能意味着将#ifdefs放在头文件中)。如果我的结果代码更清晰,编译器必须更加努力,我真的不介意。或许,如果您正在开发一个数百万行代码库,而您经常需要完全重建,那么额外的节省可能是值得的。但在大多数情况下,我怀疑额外费用通常并不明显。
答案 1 :(得分:6)
继续做你做的事情 - 它很清楚,容易出错,而且编译器编写者也很熟悉,所以效率不如十年或两年前那么低。
你可以使用非标准#pragma once
- 如果你搜索,一旦讨论,可能至少有一个书架值得包括警卫和编译指示,所以我不打算推荐一个在另一个上面。
答案 2 :(得分:4)
Pike在https://talks.golang.org/2012/splash.article中写了更多关于它的内容:
1984年,Unix {00}命令的源代码
ps.c
汇编为 所有人都观察到#include <sys/stat.h>
37次 预处理已经完成。即使内容被丢弃36 这样做的时候,大多数C实现都会打开文件,读取 它,扫描所有37次。事实上,没有很大的聪明 行为是可能复杂的宏语义所要求的 C预处理器。
编译器因此变得非常聪明:https://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html,所以现在这不是一个问题。
在Google上构建单个C ++二进制文件可以打开和阅读 数百个单独的头文件数万次。在 2007年,Google的建筑工程师对该软件进行了编译 主要谷歌二进制。该文件包含大约两千个文件, 如果简单地连接在一起,总计4.2兆字节。到...的时候 #includes已经扩展,超过8千兆字节的交付 对于编译器的输入,每个C ++爆发2000个字节 源字节。
作为另一个数据点,2003年谷歌的构建系统从一个 单个Makefile到每个目录设计,管理更好,更多 显式依赖。典型的二进制文件缩小了大约40%的文件大小, 只是记录了更准确的依赖关系。即便如此, C ++(或C)的属性使得验证不切实际 那些依赖自动,今天我们仍然没有 准确理解大型Google的依赖性要求 C ++二进制文件。
关于二进制大小的观点仍然相关。编译器(链接器)在剥离未使用的符号方面相当保守。 How to remove unused C/C++ symbols with GCC and ld?
在Plan 9中,禁止进一步包含头文件
#include
条款;所有#includes
都必须位于顶级C文件中。这当然需要一些纪律 - 程序员 需要在列表中列出一次必要的依赖项 正确的订单 - 但文档有帮助,在实践中它非常有效 好。
这是一种可能的解决方案。另一种可能性是拥有一个管理包含的工具,例如MakeDeps。
还有单一构建,有时称为SCU,单个编译单元构建。有一些工具可以帮助您管理,例如https://github.com/sakra/cotire
使用优化增量编译速度的构建系统也是有利的。我说的是谷歌的Bazel和类似的。但是,它不能保护您免受包含在大量其他文件中的头文件的更改。
最后,在工作中有一个关于C ++模块的提议,很棒的东西https://groups.google.com/a/isocpp.org/forum/#!forum/modules。另请参阅What exactly are C++ modules?
答案 3 :(得分:2)
您目前的做法是常见的方式。 Pike的方法在编译时减少了一点,但是现代编译器可能不是很多(当Pike编写他的笔记时,编译器没有优化器限制),它会使模块变得混乱,并且容易出错。
您仍然可以通过不包含标题中的标题来削减多重包含,而是在包含此标题之前使用“include <foodefs.h>
对其进行记录。”
答案 4 :(得分:1)
我建议你把它们放在源文件中。无需抱怨实际PC上几千个不必要的解析代码行。
此外 - 如果你检查包含标题的每个源文件中的每个标题,那么它的工作和来源要多得多。
您必须处理与默认和其他第三方标题不同的标题文件。
答案 5 :(得分:1)
他写这篇文章的时候可能有争执。如今,体面的编译器能够很好地处理这个问题。
答案 6 :(得分:0)
我同意你的方法 - 正如其他人所评论的那样,更清晰,自我记录,维护更少。
我为什么Rob Pike可能提出他的方法的理论:他在谈论的是C,而不是C ++。
在C ++中,如果你有很多类,并且你在自己的头文件中声明每个类,那么你将拥有大量的头文件。 C并没有真正提供这种细粒度的结构(我不记得看到很多单结构C头文件),而.h / .c文件对趋势更大,包含类似模块或子系统的东西。因此,头文件更少。在那种情况下,Rob Pike的方法可能会奏效。但我认为它不适合非平凡的C ++程序。