Phi*_*lip 9 c coding-style header-files
我的C标头通常类似于以下样式,以避免多重包含:
#ifndef <FILENAME>_H
#define <FILENAME>_H
// define public data structures / prototypes, macros etc.
#endif /* !<FILENAME>_H */
Run Code Online (Sandbox Code Playgroud)
但是,在他的C语言编程中,Rob Pike对头文件做了如下论证:
有一个小小的舞蹈涉及
#ifdef
到可以防止文件被读取两次,但它在实践中通常是错误的 -#ifdef
它们在文件本身,而不是包含它的文件.结果往往是成千上万的不必要的代码行通过词法分析器,这是(在良好的编译器中)最昂贵的阶段.
一方面,派克是我唯一真正钦佩的程序员.另一方面,将多个#ifdef
s放在多个源文件中而不是将其#ifdef
放在单个头文件中会感到不必要的尴尬.
处理多重包含问题的最佳方法是什么?
Mar*_*ins 11
在我看来,使用需要较少时间的方法(这可能意味着将#ifdefs放在头文件中).如果我的结果代码更清晰,编译器必须更加努力,我真的不介意.或许,如果您正在开发一个数百万行代码库,而您经常需要完全重建,那么额外的节省可能是值得的.但在大多数情况下,我怀疑额外费用通常并不明显.
继续做你做的事情 - 它很清楚,不易出错,并且编译器编写者都知道,所以不像十年或两年前那样低效.
你可以使用非标准#pragma once
- 如果你搜索,一旦讨论,可能至少有一个书架的价值包括警卫和编译,所以我不会推荐一个而不是另一个.
Pike 在https://talks.golang.org/2012/splash.article中写了更多相关内容:
\n\n\n\n\n1984 年,
\nps.c
人们观察到对 Unix ps 命令源代码的编译\n#include <sys/stat.h>
当所有预处理完成时尽管这样做时内容会被丢弃 36 次,但大多数 C 实现都会打开文件、读取文件并扫描 37 次。事实上,如果没有很高的技巧,这种行为就是 C 预处理器潜在复杂的宏语义所需要的。
从那时起,编译器变得非常聪明:https://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html,所以现在这不再是一个问题。
\n\n\n\n\nGoogle 构建的单个 C++ 二进制文件可以打开和读取数百个单独的头文件数万次。2007 年,Google 的构建工程师对主要 Google 二进制文件的编译进行了检测。该文件包含大约 2000 个文件,\n 如果简单地连接在一起,总计 4.2 兆字节。当 #include 扩展时,超过 8 GB 的数据被传送到编译器的输入,每个 C++ 源字节增加了 2000 个字节。
\n\n作为另一个数据点,2003 年,Google 的构建系统从单个 Makefile 转移到了按目录设计,具有更好的管理、更显式的依赖关系。仅仅因为记录了更准确的依赖关系,典型的二进制文件的文件大小就缩小了约 40%。即便如此,\n C++(或就此而言的 C)的属性使得自动验证\n 这些依赖关系变得不切实际,并且今天我们仍然没有\n 准确了解大型 Google\n C++ 二进制文件的依赖关系要求。
\n
关于二进制大小的观点仍然相关。编译器(链接器)对于剥离未使用的符号非常保守。如何使用 GCC 和 ld 删除未使用的 C/C++ 符号?
\n\n\n\n\n在计划 9 中,头文件被禁止包含进一步的
\n#include
子句;所有#includes
内容都必须位于顶级 C 文件中。这需要一些纪律,当然\xe2\x80\x94程序员需要\n以正确的顺序列出一次必要的依赖关系\xe2\x80\x94但是文档有所帮助,并且在实践中它运行得很好。
这是一个可能的解决方案。另一种可能性是拥有一个为您管理包含的工具,例如MakeDeps。
\n\n还有统一构建,有时称为 SCU,单一编译单元构建。有一些工具可以帮助管理它,例如https://github.com/sakra/cotire
\n\n使用针对增量编译速度进行优化的构建系统也可能是有利的。我说的是 Google 的 Bazel 和类似的。但是,它不能保护您免受大量其他文件中包含的头文件的更改。
\n\n最后,有一个关于 C++ 模块的提案正在进行中,很棒的东西https://groups.google.com/a/isocpp.org/forum/#!forum/modules。另请参阅C++ 模块到底是什么?
\n