重新認識標頭檔(C/C++ Header Files)


讓我們來複習一下標頭檔的用途:對小型程式而言,基本上我們只需要把所有的原始檔一起編譯,就可以產生出所要的執行檔。但是如果到了大型的專案呢?很多程式勢必是朝向模組化並且講究可再使用性高的方向去設計。其中最關鍵的就是可再使用性(Reusability),但別人要怎重複使用這段程式呢?

首先,回想一下我們如果完全不使用標頭檔又要把一個程式切成好幾個原始檔完成,勢必得在各個原始檔前面加上必要的「事先宣告(Forware Declaration)」,這些東西對我們來說是相依性高的,因為這些事先宣告可能跟原始碼本身的實作沒有關係。所以為了可再使用性,我們把這些事先宣告都放進了標頭檔,如此一來,不論是動態函式庫或是靜態鏈結,我們都可以透過引用這個標頭檔而再用該函式庫。

但是也不是每種語言都有標頭檔的設計,例如 Java,就透過 package 的命名機制(有興趣可以詳讀這篇文章),避免歧義,讓編譯器可以直接找到所需要的 Symbol。

標頭檔的概念很容易理解,要照著做也只是把變數拉出去而已。那麼到底還有什麼值得寫出一篇文章的地方呢?

不要把標頭檔當垃圾桶

有些人可能會為了方便,把所有的東西一股腦兒往標頭檔塞。來假設最極端的例子,全部塞到 common.h 一個標頭檔。Makefile 的設計是這樣:一旦標頭檔的內容被更改了,那麼所有跟此標頭檔相依的檔案都會被重新編譯。所以在這個例子中,即使你只是在 common.h 中加了一行註解,也會導致整個程式都得重新編譯。

儘量降低各個標頭檔的相依性

再次強調:Reusability。為了降低相依性,有多少個標頭檔、什麼東西要放在標頭檔,自然也是個學問。目前比較常見的用法,是一個原始檔搭配一個標頭檔。
除此之外:

  • 不要把非公開的函數(例如一些 helper functions)宣告或是變數也拉到標頭檔去。
  • 全域變數的宣告儘量的少,儘可能使用 static 變數。
  • 不要把函數主體放在標頭檔,除非是 inline function(非公開使用依舊除外)

標頭檔內含有靜態陣列宣告可能會使程式肥大

因為已知陣列元素內容,所以有些人可能習慣直接在標頭檔就順便把陣列值給一起宣告了(包括我自己QQ),如果你並沒有加上 static 的話,那還無礙。因為預設會當做 extern variable,只允許一份存在,否則會有 link error。
但是如果你宣告為 static,那事情就完全不一樣了,對 C/C++ 來說,static 的 file scope 是所以引用此宣告的原始檔,也就是說編譯器對於各個有引用該標頭檔的原始檔,都給一份複製。而當你有很多個檔案都引用此含有陣列值宣告得標頭檔,程式大小可能是成倍數增長(尤其是在嵌入式系統,待寫入 RAM 的資料算是相對龐大的)。
Referfence: [http://www.eetimes.com/discussion/barr-code/4215934/What-belongs-in-a-header-file](http://www.eetimes.com/discussion/barr-code/4215934/What-belongs-in-a-header-file)