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