• 正文
  • 相关推荐
申请入驻 产业图谱

代码编程规范-扩展(头文件)

04/05 13:37
2088
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

前言

这篇重点介绍一下代码编程规范的扩展要求-头文件规范要求

对于C语言来说,头文件的设计体现了大部分的系统设计。不合理的头文件布局是编译时间过长的根因,不合理的头文件实际上反映了不合理的设计。

规范要求

【规范1】头文件中适合放置接口的声明,不适合放置实现

1 内部使用的函数(相当于类的私有方法)声明不应放在头文件中

2 内部使用的宏、枚举、结构定义不应放入头文件中

3 变量定义不应放在头文件中,应放在.c文件中

4 变量的声明尽量不要放在头文件中,亦即尽量不要使用全局变量作为接口。变量是模块或单元的内部实现细节,不应通过在头文件中声明的方式直接暴露给外部,应通过函数接口的方式进行对外暴露。即使必须使用全局变量,也只应当在.c中定义全局变量,在.h中仅声明变量为全局的

【规范2】头文件应当职责单一,切忌依赖复杂

头文件过于复杂,依赖过于复杂是导致编译时间过长的主要原因。很多现有代码中头文件过大,职责过多,再加上循环依赖的问题,可能导致为了在.c中使用一个宏,而包含十几个头文件,其根本原因是因为偷懒,想省事,所以往往会包含一大堆头文件,但是这种做法会导致编译时间拉长

【规范3】头文件应向稳定的方向包含

头文件的包含关系是一种依赖,一般来说,应当让不稳定的模块依赖稳定的模块,从而当不稳定的模块发生变化时,不会影响(编译)稳定的模块,而且能及时中止编译,缩短因错误导致的编译时间。

一般情况下为应用层头文件 > 模块层头文件 > 驱动层头文件 > 标准库头文件,根据代码后期可能修改的频率排序,如下代码,关于同一层的头文件排序方式,参考要求13

include "app.h" // 应用层头文件
include "moudle.h" // 模块层头文件
include "device.h" // 驱动层头文件
include <string.h> // 标准库头文件

【规范4】每一个 .c 文件应有一个同名 .h 文件,用于声明需要对外公开的接口

  • 如果一个.c文件不需要对外公布任何接口,则其就不应当存在,除非它是程序的入口,如main函数所在的文件。
  • 现有某些产品中,习惯一个 .c 文件对应两个头文件,一个用于存放对外公开的接口,一个用于存放内部需要用到的定义、声明等,以控制 .c 文件的代码行数,但是这种做法是不建议的。
  • .h 文件可以不需要有对应的 .c 文件,如定义配置选项的一些头文件、或者定义了寄存器地址的宏等头文件可以不需要对应的 .c 文件。

【规范5】禁止头文件循环依赖

原因:头文件循环依赖,如 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 的源代码重新编译

【规范6】.c/.h文件禁止包含用不到的头文件

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

【规范7】头文件应当自包含

自包含就是任意一个头文件均可独立编译。如果一个文件包含某个头文件,还要包含另外一个头文件才能工作的话,就会增加交流障碍,给这个头文件的用户增添不必要的负担

【规范8】总是编写内部 #include 保护符( #define 保护)

在编写程序的头文件的时候,要注意每个头文件都应该用内部包含保护符来进行保护,以避免在多次包含时重新定义

#ifndef FOO_H_INCLUDED_
#define FOO_H_INCLUDED_

//....文件内容.....

#endif

定义包含保护符时,应该遵守如下规则:

  • 保护符使用唯一名称;
  • 不要在受保护部分的前后放置代码或者注释(特殊情况:头文件的版权声明部分以及头文件的整体注释部分可以放在保护符(#ifndef XX_H)前面)

【规范9】禁止在头文件中定义变量

原因:在头文件中定义变量,将会由于头文件被其他 .c 文件包含而导致变量重复定义编译报错

只能在源文件中定义变量,在头文件中 extern 声明

【规范10】只能通过包含头文件的方式使用其他 .c 提供的接口,禁止在.c 中通过 extern 的方式使用外部函数接口、变量

原因:

1 若多处使用 extern 的方式声明使用,则改变变量类型或者函数的返回值等时需要改动的地方很多

2 影响模块的稳定性,因为头文件声明的都是API接口,源文件(.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。

【规范11】禁止在 extern "C" 中包含头文件

原因:在extern "C"中包含头文件,会导致extern "C"嵌套

// 错误写法
extern “C”
{
#include “xxx.h”
...
}

// 正确写法
#include “xxx.h”
extern “C”
{
...
}

【规范12】一个模块通常包含多个 .c 文件,建议放在同一个目录下,目录名即为模块名。为方便外部使用者,建议每一个模块提供一个 .h ,文件名为目录名

需要注意的是,这个.h并不是简单的包含所有内部的.h,它是为了模块使用者的方便,对外整体提供的模块接口。

如:产品普遍使用的VOS,作为一个大模块,其内部有很多子模块,他们之间的关系相对比较松散,就不适合提供一个vos.h。而VOS的子模块,如Memory(仅作举例说明,与实际情况可能有所出入),其内部实现高度内聚,虽然其内部实现可能有多个.c和.h,但是对外只需要提供一个Memory.h声明接口

【规范13】同一产品统一包含头文件排列方式

常见的包含头文件排列方式:功能块排序、文件名升序、稳定度排序。

1 以功能块方式排列头文件可以快速了解涉及的相关功能模块

2 以升序方式排列头文件可以避免头文件被重复包含

3 以稳定度排序,如 product.h修改的较为频繁,如果有错误,不必编译platform.h就可以发现product.h的错误,可以部分减少编译时间

相关推荐