一般我们知道写C代码的时候应该把函数的声明放在.h文件,定义放在.c文件,但是仅仅知道这一点还是远远不够的。随着工程的增长,如果include头文件策略不当,会导致依赖关系复杂,编译时间过长。
下面两点是在以上的基础上更好的策略:
- .h文件应该只include它依赖的头文件,即如果不include就会编译不过的头文件。
- .c文件应该include所有他需要的.h文件。而不是把需要的.h文件都交给同名.h文件去include。
对于C语言来说,头文件的设计体现了大部分的系统设计。不合理的头文件布局是编译时间过长的根因,不合理的头文件实际上就是不合理的设计。
依赖特指编译依赖。若x.h包含了y.h,则称作x依赖y。依赖关系会进行传导,如x.h包含y.h,而y.h又包含了z.h,则x通过y依赖了z。
不良的设计会导致整个系统的依赖关系无比复杂,使得任意一个文件的修改都要重新编译整个系统。
在一个设计良好的系统中, 修改一个文件,只需要重新编译数个,甚至是一个文件。
从编程规范的角度看,有一些通用的方法,用来合理规划头文件。这里介绍的一些方法,对于合理规划头文件会有一定的帮助。
原则1:头文件中适合放置接口的声明,不适合放置实现
头文件是模块( Module)的对外接口。头文件中应放置对外部的声明,如对外提供的函数声明、宏定义、类型定义等。
- 内部使用的宏、枚举、结构定义不应放入头文件中。
- 变量的声明尽量不要放在头文件中,亦即尽量不要使用全局变量作为接口 。变量是模块或单元的内部实现细节,不应通过在头文件中声明的方式直接暴露给外部,应通过函数接口的方式进行对外暴露
延伸阅读材料:《 C语言接口与实现》
原则2:头文件应当职责单一
原则3:头文件应向稳定的方向包含
头文件的包含关系是一种依赖,一般来说,应当让不稳定的模块依赖稳定的模块。
就产品来说,依赖的方向应该是:产品依赖于平台,平台依赖于标准库。若一平台的代码中包含了产品的头文件,导致平台无法单独编译、发布和测试, 这就是一种非常糟糕的情况。
建议1:一个模块通常包含多个.c文件,建议放在同一个目录下,目录名即为模块名。为方便外部使用者,建议每一个模块提供一个.h,文件名为目录名
说明:需要注意的是,这个.h并不是简单的包含所有内部的.h,它是为了模块使用者的方便,对外整体提供的模块接口。
以Google test(简称GTest)为例, GTest作为一个整体对外提供C++单元测试框架,其1.5版本的gtest工程下有6个源文件和12个头文件。
但是它对外只提供一个gtest.h,只要包含gtest.h即可使用GTest提供的所有对外提供的功能,使用者不必关系GTest内部各个文件的关系,即使以后GTest的内部实现改变了,比如把一个源文件c拆成两个源文件,使用者也不必关心,甚至如果对外功能不变,连重新编译都不需要。
网友评论