前言
這篇重點介紹一下代碼編程規(guī)范的擴展要求-頭文件規(guī)范要求
對于C語言來說,頭文件的設計體現(xiàn)了大部分的系統(tǒng)設計。不合理的頭文件布局是編譯時間過長的根因,不合理的頭文件實際上反映了不合理的設計。
規(guī)范要求
【規(guī)范1】頭文件中適合放置接口的聲明,不適合放置實現(xiàn)
1 內部使用的函數(shù)(相當于類的私有方法)聲明不應放在頭文件中
2 內部使用的宏、枚舉、結構定義不應放入頭文件中
3 變量定義不應放在頭文件中,應放在.c文件中
4 變量的聲明盡量不要放在頭文件中,亦即盡量不要使用全局變量作為接口。變量是模塊或單元的內部實現(xiàn)細節(jié),不應通過在頭文件中聲明的方式直接暴露給外部,應通過函數(shù)接口的方式進行對外暴露。即使必須使用全局變量,也只應當在.c中定義全局變量,在.h中僅聲明變量為全局的
【規(guī)范2】頭文件應當職責單一,切忌依賴復雜
頭文件過于復雜,依賴過于復雜是導致編譯時間過長的主要原因。很多現(xiàn)有代碼中頭文件過大,職責過多,再加上循環(huán)依賴的問題,可能導致為了在.c中使用一個宏,而包含十幾個頭文件,其根本原因是因為偷懶,想省事,所以往往會包含一大堆頭文件,但是這種做法會導致編譯時間拉長
【規(guī)范3】頭文件應向穩(wěn)定的方向包含
頭文件的包含關系是一種依賴,一般來說,應當讓不穩(wěn)定的模塊依賴穩(wěn)定的模塊,從而當不穩(wěn)定的模塊發(fā)生變化時,不會影響(編譯)穩(wěn)定的模塊,而且能及時中止編譯,縮短因錯誤導致的編譯時間。
一般情況下為應用層頭文件 > 模塊層頭文件 > 驅動層頭文件 > 標準庫頭文件,根據(jù)代碼后期可能修改的頻率排序,如下代碼,關于同一層的頭文件排序方式,參考要求13
include "app.h" // 應用層頭文件
include "moudle.h" // 模塊層頭文件
include "device.h" // 驅動層頭文件
include <string.h> // 標準庫頭文件
【規(guī)范4】每一個 .c 文件應有一個同名 .h 文件,用于聲明需要對外公開的接口
- 如果一個.c文件不需要對外公布任何接口,則其就不應當存在,除非它是程序的入口,如main函數(shù)所在的文件。
- 現(xiàn)有某些產品中,習慣一個 .c 文件對應兩個頭文件,一個用于存放對外公開的接口,一個用于存放內部需要用到的定義、聲明等,以控制 .c 文件的代碼行數(shù),但是這種做法是不建議的。
- .h 文件可以不需要有對應的 .c 文件,如定義配置選項的一些頭文件、或者定義了寄存器地址的宏等頭文件可以不需要對應的 .c 文件。
【規(guī)范5】禁止頭文件循環(huán)依賴
原因:頭文件循環(huán)依賴,如 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 的源代碼重新編譯
【規(guī)范6】.c/.h文件禁止包含用不到的頭文件
很多系統(tǒng)中頭文件包含關系復雜,開發(fā)人員為了省事起見,可能不會去一一鉆研,直接包含一切想到的頭文件,甚至有些產品干脆發(fā)布了一個god.h,其中包含了所有頭文件,然后發(fā)布給各個項目組使用,這種只圖一時省事的做法,導致整個系統(tǒng)的編譯時間進一步惡化,并對后來人的維護造成了巨大的麻煩
【規(guī)范7】頭文件應當自包含
自包含就是任意一個頭文件均可獨立編譯。如果一個文件包含某個頭文件,還要包含另外一個頭文件才能工作的話,就會增加交流障礙,給這個頭文件的用戶增添不必要的負擔
【規(guī)范8】總是編寫內部 #include 保護符( #define 保護)
在編寫程序的頭文件的時候,要注意每個頭文件都應該用內部包含保護符來進行保護,以避免在多次包含時重新定義
#ifndef FOO_H_INCLUDED_
#define FOO_H_INCLUDED_
//....文件內容.....
#endif
定義包含保護符時,應該遵守如下規(guī)則:
- 保護符使用唯一名稱;
- 不要在受保護部分的前后放置代碼或者注釋(特殊情況:頭文件的版權聲明部分以及頭文件的整體注釋部分可以放在保護符(#ifndef XX_H)前面)
【規(guī)范9】禁止在頭文件中定義變量
原因:在頭文件中定義變量,將會由于頭文件被其他 .c 文件包含而導致變量重復定義編譯報錯
只能在源文件中定義變量,在頭文件中 extern 聲明
【規(guī)范10】只能通過包含頭文件的方式使用其他 .c 提供的接口,禁止在.c 中通過 extern 的方式使用外部函數(shù)接口、變量
原因:
1 若多處使用 extern 的方式聲明使用,則改變變量類型或者函數(shù)的返回值等時需要改動的地方很多
2 影響模塊的穩(wěn)定性,因為頭文件聲明的都是API接口,源文件(.c)中包含了私有函數(shù)和變量,有各自的執(zhí)行條件,若通過 extern 的方式聲明使用,則會降低模塊的穩(wěn)定性
如:若 a.c 使用了 b.c 定義的foo()函數(shù),則應當在b.h中聲明extern int foo(int input);并在a.c中通過#include 來使用foo。禁止通過在a.c中直接寫extern int foo(int input);來使用foo。
【規(guī)范11】禁止在 extern "C" 中包含頭文件
原因:在extern "C"中包含頭文件,會導致extern "C"嵌套
// 錯誤寫法
extern “C”
{
#include “xxx.h”
...
}
// 正確寫法
#include “xxx.h”
extern “C”
{
...
}
【規(guī)范12】一個模塊通常包含多個 .c 文件,建議放在同一個目錄下,目錄名即為模塊名。為方便外部使用者,建議每一個模塊提供一個 .h ,文件名為目錄名
需要注意的是,這個.h并不是簡單的包含所有內部的.h,它是為了模塊使用者的方便,對外整體提供的模塊接口。
如:產品普遍使用的VOS,作為一個大模塊,其內部有很多子模塊,他們之間的關系相對比較松散,就不適合提供一個vos.h。而VOS的子模塊,如Memory(僅作舉例說明,與實際情況可能有所出入),其內部實現(xiàn)高度內聚,雖然其內部實現(xiàn)可能有多個.c和.h,但是對外只需要提供一個Memory.h聲明接口
【規(guī)范13】同一產品統(tǒng)一包含頭文件排列方式
常見的包含頭文件排列方式:功能塊排序、文件名升序、穩(wěn)定度排序。
1 以功能塊方式排列頭文件可以快速了解涉及的相關功能模塊
2 以升序方式排列頭文件可以避免頭文件被重復包含
3 以穩(wěn)定度排序,如 product.h修改的較為頻繁,如果有錯誤,不必編譯platform.h就可以發(fā)現(xiàn)product.h的錯誤,可以部分減少編譯時間