如何在C语言中组织#include头文件

6
假设我有一个C程序,它被分成了一组*.c和*.h文件。如果一个文件的代码使用另一个文件的函数,应该在哪里包含头文件?应该在使用该函数的*.c文件中包含,还是在该文件的头文件中包含?
例如,文件foo.c包括foo.h,其中包含了所有foo.c的声明;bar.c和bar.h也是如此。foo.c中的函数foo1()调用bar1(),后者在bar.h中声明并在bar.c中定义。现在的问题是,我应该在foo.h中包含bar.h,还是在foo.c中包含bar.h?
对于这种问题,有哪些好的经验法则呢?
6个回答

7
你应该在foo.c中包含foo.h。这样,包含foo.h的其他c文件就不会不必要地携带bar.h。以下是我关于包含头文件的建议:
  • 在c文件中添加include定义-这样,在阅读代码时,文件依赖关系更加明显。
  • 将foo.h分成两个单独的文件,例如foo_int.h和foo.h。第一个文件仅包含foo.c所需的类型和前向声明。foo.h包括外部模块需要的函数和类型。这类似于foo的私有和公共部分。
  • 避免交叉引用,即foo引用bar,bar引用foo。这可能会导致链接问题,也是设计不良的迹象。

3
创建头文件的唯一原因是多个源文件需要使用相同信息。foo_int.h头文件应该只在foo.h内部使用,因此不应创建或使用它——可以将其内容直接放在foo.c中。 - Jonathan Leffler
继续:当您有多个源文件提供“foo.c”的功能时(比如foo1.c和foo2.c),您将使用foo_int.h。然后,内部头文件就有意义了。但是,您应该努力保持足够紧凑,以避免C文件分割是不必要的。 - Jonathan Leffler
我发现当你有很长的类型、常量或宏定义只需要在foo.c中使用时,将文件分割是一个不错的技巧。最好将它们放在头文件中而不是直接放在源文件中。Adam Liss提出了一个更好的解决方案,即将所有内容都放在同一个.h文件中。 - kgiannakakis

5
正如其他人所指出的那样,头文件foo.h应该声明使用源文件foo.c提供的设施所需的信息。这将包括由foo.c提供的类型、枚举和函数。(你不使用全局变量吧?如果使用了,则也在foo.h中声明。)
头文件foo.h应该是自包含的和幂等的。自包含意味着任何用户都可以包含foo.h,而不需要担心可能需要哪些其他头文件(因为foo.h包含了这些头文件)。幂等意味着如果头文件被包含多次,不会造成任何损坏。这是通过经典技术实现的:
 #ifndef FOO_H_INCLUDED
 #define FOO_H_INCLUDED
 ...rest of the contents of foo.h...
 #endif /* FOO_H_INCLUDED */

所提出的问题是:

文件foo.c包含了foo.h,其中包含了所有foo.c的声明;bar.c和bar.h同理。在foo.c中定义的函数foo1()调用bar.h中声明并在bar.c中定义的函数bar1()。现在问题是,我应该将bar.h包含在foo.h中还是包含在foo.c中?

这取决于foo.h提供的服务是否依赖于bar.h。如果使用foo.h的其他文件需要bar.h定义的类型或枚举来使用foo.h的功能,则foo.h应确保包含bar.h(通过包含它)。然而,如果bar.h的服务仅在foo.c中使用,并且不需要使用foo.h的人需要它们,则foo.h不应包含bar.h。


3
我认为只有在需要的情况下,才应该在 *.h 文件中包含头文件。在我看来,源文件所需的头文件应该包含在源文件中,以便从源代码中明显地看出它们之间的依赖关系。头文件应该被构建为可以处理多次包含,以便于需要时可以在两个文件中都包含,以提高清晰度。

3

使用 foo.cfoo.h 的例子,我发现以下准则很有帮助:

  • 记住,foo.h 的目的是为了方便使用 foo.c,因此尽可能简单、有组织和易于理解。大量添加注释以说明如何以及何时使用 foo.c 的功能,以及不应该使用它们的情况。

  • foo.h 声明了 foo.c 的公共特性:函数、宏、typedef 和全局变量(令人发抖)。

  • foo.c 应该包含 #include "foo.h" -- 请参见下面的讨论和 Jonathan Leffler 的评论。

  • 如果 foo.c 编译需要其他头文件,则在 foo.c 中包含它们。

  • 如果编译 foo.h 需要外部头文件,则在 foo.h 中包含它们。

  • 利用预处理器防止多次包含 foo.h。(见下文。)

  • 如果由于某种原因,另一个 .c 文件需要使用 foo.c 的功能,则在 foo.h 中包含所需的头文件,以免下一个开发人员进行不必要的调试。如果您不喜欢这样做,请考虑添加宏,在编译时显示说明,如果未包含所需的头文件。

  • 不要在另一个 .c 文件中包含 .c 文件,除非您有一个非常好的理由,并清楚地记录它。

正如 kgiannakakis 所指出的那样,将公共接口与仅在 foo.c 中需要的定义和声明分离很有帮助。但是,有时候不是创建两个文件,而是让预处理器为您完成:

// foo.c
#define _foo_c_         // Tell foo.h it's being included from foo.c
#include "foo.h"
. . .

 

// foo.h
#if !defined(_foo_h_)   // Prevent multiple inclusion
#define _foo_h_

// This section is used only internally to foo.c
#ifdef _foo_c_
. . .
#endif

// Public interface continues to end of file.

#endif // _foo_h_       // Last-ish line in foo.h

foo.c 可以引用 foo.h -- 不,foo.c 必须引用 foo.h,并将其作为第一个头文件。这是一种自动检查机制,用于验证 foo.h 是自足的,并因此可以被其他模块使用。 - Jonathan Leffler
此外,在头文件中包含仅在实现源文件内部使用的信息是没有意义的。头文件中只包含其他文件需要的公共信息。 - Jonathan Leffler
有时候,我会根据foo.h是直接由foo.c包含还是避免重复的原型/声明来不同地定义宏,当维护的好处大大超过任何导致的混淆时。 - Adam Liss

2

我在.h文件中包含最小的头文件集,并在.c文件中包含其余部分。这样做有时可以减少编译时间。以你的例子为例,如果foo.h并不真正需要bar.h但仍然包含它,而某个其他文件包含foo.h,那么如果bar.h发生更改,即使它实际上并不需要或使用bar.h,该文件也将被重新编译。


2

.h文件应该定义.c文件中函数的公共接口(也称为API)。

如果file1的接口使用file2的接口,则在file1.h中#include file2.h。

如果file1.c中的实现使用file2.c中的内容,则file1.c应该#include file2.h。

我必须承认,因为我总是在file1.c中#include file1.h,所以如果file2.h已经在file1.h中#include了,我通常不会费心在file1.c中直接#include file2.h。

如果您发现两个.c文件互相#include彼此的.h文件,则说明模块化已经崩溃,您应该考虑重新构建一下。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接