一个大型C语言项目中的头文件应该包含什么内容?想必做C/C++开发的程序员都有过这样的疑问。密歇根大学的EECS 381课程中的C Header File GuidelinesC++ Header File Guidelines 是两个不错的总结。如下11条规则摘取自 C Header File Guidelines

Rule 1

每个模块的 .h 和 .c 文件都应该有明确的功能点。不要把具有不同功能函数揉在一个模块,也不要把属于同一模块的函数分开。

Rule 2

在所有头文件中都使用 include guards。且定义的 guard 不要以下划线开头。

Rule 3

使用一个模块所需的所有声明必须出现在模块的头文件中,并且该文件始终用于访问模块。

Rule 4

头文件应该只包含函数声明(structure type declarations, function prototypes, and global variable extern declarations),函数的定义(function definitions and global variable definitions and initializations)应该放在该模块的 .c 文件中。.c 文件必须 include .h 文件以便编译器检测是否一致。

Rule 5

对于整个程序都可见的全局变量,使用 extern 在头文件件中进行声明,其定义声明放在 .c 文件中。

.h
extern int g_number_of_entities;

.c
int g_number_of_entities = 0;  // 既是定义又是声明

Rule 6

仅在本模块中使用的声明应该避免放在头文件中。如果只是在 .c 文件中使用的结构体、全局变量以及函数,应该将它们的声明或定义放在 .c 文件的顶部,另外可以在全局变量和函数前加上 static,以便赋予它们内部链接的特性。

Rule 7

头文件应该只包含能使它通过编译的最少的其他头文件,如果 A.h 中定义的结构体 A 中使用了 X.h 中定义的结构体 X 作为它的成员变量,那么应该在 A.h 中应该 #include “X.h”。不要包含只被 .c 文件需要的头文件,如 “math.h” 就应该只被 .c 文件包含。

Rule 8

如果前向声明(incomplete declaration / “forward” declaration)能搞定,就用它代替头文件。例如在结构体 A 中只用 X 结构体的指针作为它的成员变量,那么就不用在 A.h 中包含 X.h。

struct X;   /* incomplete ("forward") declaration */

struct A {
    int i;
    struct X* x_ptr;    
};

通常情况下,在函数的具体实现中才会去访问 X 中的成员变量,因此需要在 A.c 中包含 X.h。

Rule 9

头文件中包含的内容应该能保证本身能够通过编译。通过编译一个只包含该头文件的 test.c 文件可以检验这点。

Rule 10

A.c 文件应该最先 #include “A.h”,然后在包含其它头文件。

Rule 11

永远不要 include .c 文件。