在哪里放置包含语句、标题或源?

我应该把包含在头文件还是源文件?如果头文件包含 include 语句,那么如果我在源文件中包含这个头文件,那么源文件中是否包含头文件中包含的所有文件?还是应该只在源文件中包含它们?

76411 次浏览

Your source file will have the include statements if your put it in the header. However, in some cases it would be better to put them in the source file.

请记住,如果您在任何其他源中包含该标头,它们也将从标头中获得包含,这并不总是可取的。您应该只包含使用它的内容。

If header file A #includes header files B and C, then every source file that #includes A will also get B and C #included. The pre-processor literally just performs text substitution: anywhere it finds text that says #include <foo.h> it replaces it with the text of foo.h file.

关于是否应该将 #includes放在头文件或源文件中,存在不同的意见。就个人而言,我喜欢把所有的 #includes在源文件默认情况下,但任何头文件,不能编译没有其他先决条件的头应该 #include这些头本身。

并且每个头文件都应该包含一个 include 约束,以防止它被多次包含。

只有在标题本身需要的情况下才放入标题中的包含。

例子:

  • 函数返回类型 size_t,然后在 标题文件中返回 #include <stddef.h>
  • 函数使用 strlen,然后在 source文件中使用 #include <string.h>

您的 #include应该是头文件,每个文件(源文件或头文件)应该 #include它需要的头文件。头文件应该 #include的最低头文件必要的,源文件也应该,虽然它不那么重要的源文件。

源文件的头将是 #include,头将是 #include,以此类推,直到最大嵌套深度。这就是为什么您不希望在头文件中使用多余的 #include: 它们可能会导致源文件包含许多可能不需要的头文件,从而减慢编译速度。

This means that it's entirely possible that header files might be included twice, and that can be a problem. The traditional method is to put "include guards" in header files, such as this for file foo.h:

#ifndef INCLUDE_FOO_H
#define INCLUDE_FOO_H
/* everything in header goes here */
#endif

There's been quite a bit of disagreement about this over the years. At one time, it was traditional that a header 只有 declare what was in whatever module it was related to, so 很多 headers had specific requirements that you #include a certain set of headers (in a specific order). Some extremely traditional C programmers still follow this model (religiously, in at least some cases).

最近,出现了一种使大多数头部独立的运动。如果那个头需要其他东西,那么头本身会处理它,确保包含它所需要的任何东西(如果存在排序问题,则按正确的顺序)。就我个人而言,我更喜欢这样做——特别是当标题的顺序可能很重要时,它一次性解决了问题,而不是要求使用它的每个人再次解决问题。

请注意,大多数头文件应该只包含声明。这意味着添加一个不必要的头(通常)不应该对最终的可执行文件有任何影响。最糟糕的情况是,它会稍微减慢编译速度。

您应该只在头中包含需要声明常量和函数声明的文件。从技术上讲,这些包含也将包含在源文件中,但是为了清楚起见,您应该只在每个文件中包含您实际需要使用的文件。您还应该保护它们在您的头部免于多重包含,因此:

#ifndef NAME_OF_HEADER_H
#define NAME_OF_HEADER_H


...definition of header file...


#endif

这样可以防止多次包含标头,从而导致编译器错误。

创建所有文件,这样就可以只使用它们包含的内容来构建它们。如果您不需要在标题中包含它,请删除它。在一个大型项目中,如果你不坚持这个原则,当有人从一个头文件中删除了一个包含,这个头文件的使用者正在使用这个包含,而头文件甚至没有使用这个包含,那么你就有可能破坏整个构建。

The approach I have evolved into over twenty years is this;

Consider a library.

有多个 C 文件,一个内部 H 文件和一个外部 H 文件。C 文件包括内部 H 文件。内部 H 文件包括外部 H 文件。

您可以从编译器 POV 中看到,当它编译一个 C 文件时,有一个层次结构;

外部-> 内部-> C 代码

这是正确的顺序,因为外部的是第三方使用库所需要的一切。编译 C 代码需要内部的代码。

在某些环境中,如果只包含所需的头文件,编译将是最快的。在其他环境中,如果所有源文件都可以使用相同的头的主要集合(有些文件可能在公共子集之外还有其他头) ,那么编译将得到优化。理想情况下,头应该构造成多个 # include 操作不会产生任何效果。最好在 # include 语句周围检查待包含文件的 include-Guard,尽管这样做会产生对该监护格式的依赖性。此外,根据系统的文件缓存行为,不必要的 # include 的目标最终完全被 # ifdef 化可能不会花费很长时间。

另一件需要考虑的事情是,如果一个函数接受一个指向结构的指针,那么可以将原型写成

void foo(struct BAR_s *bar);

没有一个 BAR _ s 的定义必须在作用域中。

PS ——在我的许多项目中,都会有一个文件,预计每个模块都会 # include,其中包含整数大小的 typedef 和一些常见的结构和联合。

typedef union {
unsigned long l;
unsigned short lw[2];
unsigned char lb[4];
} U_QUAD;

(是的,我知道如果我转移到 big-endian 架构我会有麻烦,但是因为我的编译器不允许联合中的匿名结构,对联合中的字节使用命名标识符将要求它们被访问为 Union.b.b1等等,这看起来相当烦人。