代码整洁之道 — 3 头文件规范

目录

1 头文件作用

2 头文件包含方向

2.1 .c和.h文件成对出现

2.2 禁止头文件循环依赖

2.3 .c/.h文件禁止包含用不到的头文件。

2.4 头文件排列方式

3 头文件保护

3.1 编写内部#include保护符

3.2 禁止在头文件中定义变量

3.3 使用#include替代extern

3.4 禁止在extern "C"中包含头文件

3.5 模块包含多个.c文件


1 头文件作用

头文件中适合放置接口的声明,不适合放置实现:头文件是模块(Module)或单元(Unit)的对外接口。头文件中应放置对外部的声明,如对外 提供的函数声明、宏定义、类型定义等。 内部使用的函数声明、宏、枚举、结构定义不应放入头文件中。 变量定义不应放在头文件中,应放在.c文件中。 变量的声明尽量不要放在头文件中,亦即尽量不要使用全局变量作为接口。变量是模块或单元的内部 实现细节,不应通过在头文件中声明的方式直接暴露给外部,应通过函数接口的方式进行对外暴露。 即 使必须使用全局变量,也只应当在.c中定义全局变量,在.h中仅声明变量为全局的。

头文件应当职责单一: 头文件依赖过于复杂是导致编译时间过长的主要原因。很多现有代码中头文件过大, 职责过多,再加上循环依赖的问题,可能导致为了在.c中使用一个宏,而包含十几个头文件。 示例:如下是某平台定义WORD类型的头文件:

#include <VXWORKS.H> 
#include <KERNELLIB.H> 
#include <SEMLIB.H> 
#include <INTLIB.H> 
#include <TASKLIB.H> 
#include <MSGQLIB.H> 
#include <STDARG.H> 
#include <FIOLIB.H> 
#include <STDIO.H> 
#include <STDLIB.H> 
#include <CTYPE.H> 
#include <STRING.H> 
#include <ERRNOLIB.H> 
#include <TIMERS.H> 
#include <MEMLIB.H> 
#include <TIME.H> 
#include <WDLIB.H> 
#include <SYSLIB.H> 
#include <TASKHOOKLIB.H> 
#include <REBOOTLIB.H> 
…

typedef unsigned short WORD; 
…

这个头文件不但定义了基本数据类型WORD,还包含了stdio.h、syslib.h等等不常用的头文件。如果工程中有10000个源文件,而其中100个源文件使用了stdio.h的printf,由于上述头文件的职责过于庞大,而WORD又是每一个文件必须包含的,从而导致stdio.h/syslib.h等可能被不必要的展开了9900次,大大增加了工程的编译时间。

2 头文件包含方向

头文件应向稳定的方向包含。头文件的包含关系是一种依赖,一般来说,应当让不稳定的模块依赖稳定的模块,从而当不稳 定的模块发生变化时,不会影响(编译)稳定的模块。 就我们的产品来说,依赖的方向应该是:产品依赖于平台,平台依赖于标准库。某产品线平台的代码中已经包含了产品的头文件,导致平台无法单独编译、发布和测试,是一个非常糟糕的反例。除了不稳定的模块依赖于稳定的模块外,更好的方式是两个模块共同依赖于接口,这样任何一个模块 的内部实现更改都不需要重新编译另外一个模块。在这里,我们假设接口本身是最稳定的。

2.1 .c和.h文件成对出现

每一个.c文件应有一个同名.h文件,用于声明需要对外公开的接口。如果一个.c文件不需要对外公布任何接口,则其就不应当存在,除非它是程序的入口,如main 函数所在的文件。

现有某些产品中,习惯一个.c文件对应两个头文件,一个用于存放对外公开的接口,一个用于存放内 部需要用到的定义、声明等,以控制.c文件的代码行数。编者不提倡这种风格。这种风格的根源在于源文件过大,应首先考虑拆分.c文件,使之不至于太大。

2.2 禁止头文件循环依赖

头文件循环依赖,指a.h包含b.h,b.h包含c.h,c.h包含a.h之类导致任何一个头文件修改,都 导致所有包含了a.h/b.h/c.h的代码全部重新编译一遍。而如果是单向依赖,如a.h包含b.h,b.h包含 c.h,而c.h不包含任何头文件,则修改a.h不会导致包含了b.h/c.h的源代码重新编译。

2.3 .c/.h文件禁止包含用不到的头文件。

很多系统中头文件包含关系复杂,开发人员为了省事起见,可能不会去一一钻研,直接包含一切想到的头文件,甚至有些产品干脆发布了一个god.h,其中包含了所有头文件,然后发布给各个项目组使用,这种只图一时省事的做法,导致整个系统的编译时间进一步恶化,并对后来人的维护造成了巨大的麻烦。

2.4 头文件排列方式

常见的包含头文件排列方式:功能块排序、文件名升序、稳定度排序。以文件名升序方式排列头文件可以避免头文件被重复包含,如:

#include <a.h> 
#include <b.h> 
#include <c/d.h> 
#include <c/e.h> 
#include <f.h>

以稳定度排序,建议将不稳定的头文件放在前面,如把产品的头文件放在平台的头文件前面,如下:

#include <product.h> 
#include <platform.h> 

相对来说,product.h修改的较为频繁,如果有错误,不必编译platform.h就可以发现product.h的错误,可以部分减少编译时间。

3 头文件保护

3.1 编写内部#include保护符

多次包含一个头文件需要阻止头文件内容被多次包含的机制。 通常的手段是为每个文件配置一个宏,当头文件第一次被包含时就定义这个宏,并在头文件被再次包 含时使用它以排除文件内容。

方法是使用#define 防止头文件被多重包含,命名格式为FILENAME_H,为了保证唯一性, 更好的命名是PROJECTNAME_PATH_FILENAME_H。 例如,有一个项目名为MyProject,项目路径为src/include,头文件名为config.h。根据规则,宏定义保护应该如下所示:

#ifndef MYPROJECT_SRC_INCLUDE_CONFIG_H
#define MYPROJECT_SRC_INCLUDE_CONFIG_H

// config.h 的内容
#endif // MYPROJECT_SRC_INCLUDE_CONFIG_H

也可以使用如下简单方式保护:

#ifndef CONFIG_H
#define CONFIG_H

// config.h 的内容
#endif // MYPROJECT_SRC_INCLUDE_CONFIG_H

注:没有在宏最前面加上“_",即使用FILENAME_H代替_FILENAME_H_,是因为一般以"_"和”__"开头的 标识符为系统保留或者标准库使用,在有些静态检查工具中,若全局可见的标识符以"_"开头会给出告 警。 定义包含保护符时,应该遵守如下规则:

1)保护符使用唯一名称;

2)不要在受保护部分的前后放置代码或者注释。头文件的版权声明部分以及头文件的整体注释部分,可以放在保护符(#ifndef XX_H)前面。

3.2 禁止在头文件中定义变量

在头文件中定义变量,将会由于头文件被其他.c文件包含而导致变量重复定义。

3.3 使用#include替代extern

只能通过包含头文件的方式使用其他.c提供的接口,禁止在.c中通过extern的方式使用外部函数接口、变量。 若a.c使用了b.c定义的foo()函数,则应当在b.h中声明extern int foo(int input);并在a.c 中通过#include 来使用foo。禁止通过在a.c中直接写extern int foo(int input);来使用foo, 后面这种写法容易在foo改变时可能导致声明和定义不一致。

3.4 禁止在extern "C"中包含头文件

在extern "C"中包含头文件,会导致extern "C"嵌套,Visual Studio对extern "C"嵌套层次有限制,嵌套层次太多会编译错误。在extern "C"中包含头文件,可能会导致被包含头文件的原有意图遭到破坏。

3.5 模块包含多个.c文件

果一个模块由多个.c文件组成,建议将这些文件放在一个以模块名为命名的目录中。同时,为了便于外部使用者,每个模块应该提供一个头文件,其文件名与目录名相同。

解释:这个头文件的作用是作为模块的统一接口,它不是简单地包含模块内所有子模块的头文件,而是精心设计的,只暴露给外部使用者必要的接口和函数声明。

如果一个模块包含多个子模块,则建议每一个子模块提供一个对外的.h,文件名为子模块名。这样做可以简化接口使用者的工作,因为他们只需要包含对应子模块的头文件即可访问其功能,而无需了解模块内部的复杂性。

例如,如果有一个名为Network的模块,它包含tcp.c、udp.c和socket.c等文件,那么应该组织如下:

/Network

    /src

        /tcp.c

        /udp.c

        /socket.c

    /include

        /tcp.h

        /udp.h

        /socket.h

        /Network.h

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

几度春风里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值