良好的 C 标头样式
我的 C 头文件通常类似于以下样式以避免多重包含:
#ifndef <FILENAME>_H
#define <FILENAME>_H
// define public data structures / prototypes, macros etc.
#endif /* !<FILENAME>_H */
但是,在他的 Notes onProgramming in C,Rob Pike 对头文件提出了以下论点:
有一个涉及
#ifdef
的小技巧可以防止文件被读取两次,但在实践中通常会出错 -#ifdef
位于文件本身,而不是包含它的文件。结果通常是数千行不必要的代码通过词法分析器,这是(在好的编译器中)最昂贵的阶段。
一方面,Pike 是我真正钦佩的唯一程序员。另一方面,将多个 #ifdef
放入多个源文件中,而不是将一个 #ifdef
放入单个头文件中,会感觉不必要的尴尬。
处理多重包含问题的最佳方法是什么?
My C headers usually resemble the following style to avoid multiple inclusion:
#ifndef <FILENAME>_H
#define <FILENAME>_H
// define public data structures / prototypes, macros etc.
#endif /* !<FILENAME>_H */
However, in his Notes on Programming in C, Rob Pike makes the following argument about header files:
There's a little dance involving
#ifdef
's that can prevent a file being read twice, but it's usually done wrong in practice - the#ifdef
's are in the file itself, not the file that includes it. The result is often thousands of needless lines of code passing through the lexical analyzer, which is (in good compilers) the most expensive phase.
On the one hand, Pike is the only programmer I actually admire. On the other hand, putting several #ifdef
s in multiple source files instead of putting one #ifdef
in a single header file feels needlessly awkward.
What is the best way to handle the problem of multiple inclusion?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
在我看来,使用需要较少时间的方法(这可能意味着将 #ifdefs 放入头文件中)。如果我生成的代码更干净,我真的不介意编译器是否必须更加努力地工作。如果您正在开发一个数百万行的代码库,并且需要不断地完全重建,那么额外的节省也许是值得的。但在大多数情况下,我怀疑额外的成本通常并不明显。
In my opinion, use the method that requires less of your time (which likely means putting the #ifdefs in the header files). I don't really mind if the compiler has to work harder if my resulting code is cleaner. If, perhaps, you are working on a multi-million line code base that you constantly have to fully rebuild, maybe the extra savings is worth it. But in most cases, I suspect that the extra cost is not usually noticeable.
继续做你所做的事情 - 它很清晰,不易出现错误,并且为编译器编写者所熟知,因此不像一两年前那样效率低下。
您可以使用非标准的
#pragma Once
- 如果您进行搜索,可能至少有一个书架的关于 include Guards 与 pragma Once 的讨论,所以我不会推荐其中一个。Keep doing what you do - It's clear, less bug-prone, and well known by compiler writers, so not as inefficient as it maybe was a decade or two ago.
You could use the non-standard
#pragma once
- If you search, there's probably at least a bookshelf's worth of include guards vs pragma once discussion, so I'm not going to recommend one over the other.Pike 在 https://talks.golang.org/2012/splash.article< 中写了更多相关内容/a>:
从那时起,编译器变得相当聪明: https://gcc.gnu.org/onlinedocs/ cppinternals/Guard-Macros.html,所以现在这不再是一个问题。
关于二进制大小的观点仍然相关。编译器(链接器)对于剥离未使用的符号非常保守。 如何使用 GCC 和 ld 删除未使用的 C/C++ 符号?
这是一个可能的解决方案。另一种可能性是拥有一个为您管理包含的工具,例如 MakeDeps。
还有统一构建,有时称为 SCU,单一编译单元构建。有一些工具可以帮助管理它,例如 https://github.com/sakra/cotire
使用构建针对增量编译速度进行优化的系统也可能是有利的。我说的是 Google 的 Bazel 和类似的。但是,它不能保护您免受大量其他文件中包含的头文件的更改。
最后,有一项关于 C++ 模块的提案正在进行中,很棒的东西 https://groups.google.com/a/isocpp.org/forum/#!forum/modules。另请参阅C++ 模块到底是什么?
Pike wrote some more about it in https://talks.golang.org/2012/splash.article:
Compilers have become quite clever since: https://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html, so this is less of an issue now.
The point about binary sizes is still relevant. Compilers (linkers) are quite conservative regarding stripping unused symbols. How to remove unused C/C++ symbols with GCC and ld?
This is a possible solution. Another possiblity is to have a tool that manages the includes for you, for example MakeDeps.
There is also unity builds, sometimes called SCU, single compilation unit builds. There are tools to help manage that, like https://github.com/sakra/cotire
Using a build system that optimizes for the speed of incremental compilation can be advantageous too. I am talking about Google's Bazel and similar. It does not protect you from a change in a header file that is included in a large number of other files, though.
Finally, there is a proposal for C++ modules in the works, great stuff https://groups.google.com/a/isocpp.org/forum/#!forum/modules. See also What exactly are C++ modules?
您当前执行的方式是常见的方式。 Pike 的方法减少了一点编译时间,但对于现代编译器来说可能不会减少很多(当 Pike 写他的笔记时,编译器不受优化器限制),它使模块变得混乱并且容易出现错误。
您仍然可以通过不包含标头中的标头来减少多重包含,而是使用“在包含此标头之前包含
”来记录它们。The way you're currently doing it is the common way. Pike's method cuts a bit on compilation time, but with modern compilers probably not very much (when Pike wrote his notes, compilers weren't optimizer-bound), it clutters modules and its bug-prone.
You could still cut on multi-inclusion by not including headers from headers, but instead documenting them with "include
<foodefs.h>
before including this header."我建议您将它们放在源文件本身中。无需抱怨实际 PC 上数千行不必要的解析代码。
此外,如果您检查包含标头的每个源文件中的每个标头,则需要更多的工作和源代码。
并且您必须处理与默认头文件和其他第三方头文件不同的头文件。
I recommend you put them in the source-file itself. No need to complain about some thousand needless parsed lines of code with actual PCs.
Additionally - it is far more work and source if you check every single header in every source-file that includes the header.
And you would have to handle your header-files different from default- and other third-party-headers.
他在写这篇文章时可能已经发生了争执。如今,优秀的编译器足够聪明,可以很好地处理这个问题。
He may have had an argument the time he was writing this. Nowadays decent compilers are clever enough to handle this well.
我同意你的方法 - 正如其他人评论的那样,它更清晰、自记录且维护成本更低。
我对 Rob Pike 为何提出他的方法的理论是:他谈论的是 C,而不是 C++。
在 C++ 中,如果您有很多类并且在其自己的头文件中声明每个类,那么您将拥有很多头文件。 C 并没有真正提供这种细粒度的结构(我不记得见过很多单结构 C 头文件),并且 .h/.c 文件对往往更大并且包含诸如模块或子系统之类的东西。因此,头文件更少。在这种情况下,罗布·派克的方法可能会奏效。但我认为它不适合重要的 C++ 程序。
I agree with your approach - as others have commented, its clearer, self-documenting, and lower maintenance.
My theory on why Rob Pike might have suggested his approach: He's talking about C, not C++.
In C++, if you have a lot of classes and you are declaring each one in its own header file, then you'll have a lot of header files. C doesn't really provide this kind of fine-grained structure (I don't recall seeing many single-struct C header files), and .h/.c file-pairs tend to be larger and contain something like a module or a subsystem. So, fewer header files. In that scenario Rob Pike's approach might work. But I don't see it as suitable for non-trivial C++ programs.