在哪里放置include语句,标题或源?


106

我应该将包含文件放在头文件还是源文件中?如果头文件包含include语句,那么如果我在源文件中包含该头文件,那么我的源文件中是否将包含头文件中的所有包含文件?还是仅将它们包括在源文件中?


2
SO上的许多以前的副本,例如应在C ++中放置“包括”的位置
Paul R 2010年

Answers:


141

如果标题本身需要,则仅将put包含在标题中。

例子:

  • 您的函数返回type size_t。然后#include <stddef.h>文件中。
  • 您的函数使用strlen。然后#include <string.h>文件中。

2
如果我的函数接受类型参数size_t怎么办?
andrybak

同样的问题扩展到C ++:如果我的结构/类有类型的字段/成员size_tstd::string
andrybak

10
理由是什么?
Patrizio Bertoni,2016年

我有一个有线的情况,C ++类A有另一个类B的对象,并且我不能使用B的前向声明以及最终在A标头中包含B标头的声明。(使用指针不会出现此问题)
shuva

@andrybak您的源文件应包括您的标头文件,因此任何包含您的标头的源文件也将获得。
杰里米·特里菲洛

27

多年来,对此存在很多分歧。一次,通常标头声明与之相关的模块中的内容,因此许多标头有特定的要求,因此您需要#include一组标头(按特定顺序)。一些非常传统的C程序员仍然遵循此模型(至少在某些情况下是宗教上的)。

最近,正在朝着使大多数标头独立的方向发展。如果该标头需要其他内容,则标头本身会进行处理,确保包括所需的所有内容(如果存在排序问题,请以正确的顺序)。就我个人而言,我更喜欢这样做-特别是当标题的顺序很重要时,它可以解决一次问题,而不是要求使用它的每个人都可以再次解决问题。

请注意,大多数标头应仅包含声明。这意味着添加不必要的标头通常不会对最终的可执行文件产生任何影响。发生的最坏情况是它会稍微减慢编译速度。


2
如果所有标头均采用第二种样式编写,则根本不会出现排序问题。标头中存在排序问题通常意味着您没有在标头中包括所需的所有内容。
再见SE

12

您的#includes应该是头文件,并且每个文件(源文件或头文件)都应该#include是所需的头文件。头文件应该#include是必需的最小头文件,源文件也应该,尽管它对源文件不那么重要。

源文件将具有它#include的标头和标头#include,依此类推,直到最大嵌套深度。这就是为什么你不想多余的#include头文件中有 s的原因:它们可能导致源文件包含很多可能不需要的头文件,从而减慢了编译速度。

这意味着完全有可能两次包含头文件,这可能是个问题。传统方法是在头文件中放置“ include guards”,例如对于文件foo.h:

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

我知道这个答案太老了,但是从那时起,他们就添加了#pragma一次,因此在声明#includes时不要包含#ifndef。
Dogunbound猎犬

6

我发展到二十多年的方法是这样的;

考虑一个图书馆。

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

从编译器POV可以看到,它在编译C文件时有一个层次结构。

外部->内部-> C代码

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


4

如果头文件#includes头文件B和C,然后每源文件#includes一会还可以得到B和C #included。预处理器实际上只是执行文本替换:在任何找到表示将#include <foo.h>其替换为foo.h文件文本的文本的位置。

关于是否应放置#includes头文件或源文件有不同的意见。就个人而言,我更喜欢#includes默认情况下将它们全部放在源文件中,但是如果没有其他先决条件的标头,那么#include这些标头本身就不能编译。

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


4

在某些环境中,如果仅包含一个需要的头文件,则编译速度最快。在其他环境中,如果所有源文件都可以使用相同的主标头集合(某些文件可能在公共子集之外还具有其他标头),则将优化编译。理想情况下,应构造标头,以使多个#include操作无效。将#include语句包含在要包含的文件的include-guard的检查中可能会比较好,尽管这会导致依赖于该guard的格式。此外,根据系统的文件缓存行为,目标可能被完全#ifdef移走的不必要的#include可能不会花费很长时间。

要考虑的另一件事是,如果一个函数需要一个指向结构的指针,则可以将原型编写为

无效foo(struct BAR_s * bar);

没有定义BAR_s的范围。避免不必要的非常方便的方法包括。

PS –在我的许多项目中,都会有一个文件,期望每个模块都会包含#include文件,其中包含诸如整数大小的typedef以及一些常见的结构和联合之类的内容[例如

typedef union {
  unsigned long l;
  无符号短lw [2];
  无符号字符磅[4];
} U_QUAD;

(是的,我知道如果我改用big-endian架构会很麻烦,但是由于我的编译器不允许在联合体中使用匿名结构,因此对联合体中的字节使用命名标识符要求将它们作为theUnion.b.b1等,这似乎很烦人。


3

制作所有文件,以便仅使用包含的文件即可构建它们。如果您的标题中不需要包含,请将其删除。在一个大型项目中,如果您不遵守这一纪律,那么当有人从该文件的使用者(甚至是该头文件的使用者)使用的头文件中删除包含文件时,您就很容易破坏整个构建。


1

如果将源文件放在标题中,则源文件将包含include语句。但是,在某些情况下,最好将它们放在源文件中。

请记住,如果您在其他任何来源中包含该标头,他们也会从该标头中获得包含,但这并不总是令人满意的。您应该只在使用它的地方包括东西。


1

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

#ifndef NAME_OF_HEADER_H
#define NAME_OF_HEADER_H

...definition of header file...

#endif

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.