讓我們乘坐時光機,回到初學 C 語言的那一刻。老師在講台上敲下第一個程式:
/* 檔案名 First.c */
main() {
printf("Hello world!");
}
這時並未包含任何 .h 檔案。萬物皆有從無到有的過程,理解頭文件也應從這最簡單的起點出發。當程式極簡時,確實不需要頭文件。但隨著複雜度增加,頭文件的必要性便浮現了。
若我們將打印邏輯抽離為 printStr() 函數,並放在 main() 之前:
/* 例程-2 */
printStr() { printf("Hello world!"); }
main() { printStr(); }
編譯通過。但若調換順序,將 main() 放在前面:
/* 例程-3 */
main() { printStr(); }
printStr() { printf("Hello world!"); }
編譯將失敗。原因在於 C 語言的頂層作用域:函數從定義處延伸至檔案結尾。例程-3 中 main() 調用時,printStr() 尚未進入作用域。
解決順序相依有兩條路徑:
play1 呼叫 play2,play2 又呼叫 play1),無法單純靠順序解決。隨著專案規模擴大,數百上千個函數聲明若都寫在 .c 檔案頂部極不易維護。於是,將聲明集中管理的 .h 頭文件 應運而生:
/* First.h */ play1(); play2(); /* First.c */ #include "first.h" /* 函數定義... */
大型專案中,second.c 需要呼叫 first.c 定義的 play1()。此時在 second.h 中聲明 play1(); 即可,但背後機制涉及儲存類別指定符:
extern:表示該識別符具有靜態存活期,連結器可見其名稱,可跨檔案連結。static:限制連結器可見性,僅限本檔案內部。C99 規定頂層聲明預設為 extern,因此未顯式寫出也能跨檔案連結。但為了程式碼清晰,建議採用人為規範:
若函數在對應 .c 檔有定義,聲明時不加 extern;若僅引用外部定義,必須顯式加 extern。
如此頭文件中一眼可辨:帶 extern 者為引用外部函數,不帶者為本模組定義並導出。
頭文件亦可管理全局變量。同樣面臨「哪個是定義、哪個是引用」的問題。常見兩種模型:
頂層聲明帶初始化即為定義,全專案僅能有一處:
/* first.h */ int G_test = 1; /* 定義聲明 */ /* second.h */ extern int G_test; /* 引用聲明 */
定義聲明省略 extern,引用聲明必須顯式加 extern。與函數處理一致。
錯誤示範:
/* 定義 */ int G_glob[100]; /* 錯誤引用 */ int *G_glob; /* 編譯通過,但連結器視為兩個無關符號,執行期記憶體錯誤 */
正確寫法:
/* 引用聲明保持陣列語法 */ extern int G_glob[]; /* 尺寸可省略,利於維護 */
頭文件是 C 語言模組化設計的基石。善用 extern、static、聲明與定義分離規範,能讓龐大專案的符號管理井然有序。C 語言雖歷經數十載,仍是底層核心編程的主力,掌握其精髓,方能行遠。