好C头样式

时间:2011-03-24 14:10:16

标签: c coding-style header-files

我的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放在单个头文件中会感觉不必要的尴尬。

处理多重包含问题的最佳方法是什么?

7 个答案:

答案 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 ++程序。

相关问题