天天看點

深入了解include預編譯原理

你了解 #include 某個 .h 檔案後,編譯器做了哪些操作麼? 你清楚為什麼在 .h檔案中定義函數實作的話需要在函數前面加上 static 修飾麼?你知道 #ifndef……#define……#endif 這種防止頭檔案重複包含的精髓所在麼?本文就是來探讨這些問題,并給出我的了解和思考,歡迎大家留言交流。

1.  #include 指令的作用

1.1  什麼情況不使用 include

其實,這樣的工程,可以不用使用 include 預編譯指令。

1.2  什麼情況使用 include

如果工程裡面的函數特别多,那麼按照上面的做法,則必須在每一個 .c 檔案的開頭列出所有本檔案調用過的函數的聲明,這樣很不高效,而且一旦某個函數的形式發生變化,又得一個一個改 .c 開頭的函數聲明。 

是以,#include 預編譯指令誕生。

1.3  #include 起到什麼效果

上述代碼在編譯器進行預編譯的時候,遇到 #include "a.h" ,則會把整個 a.h 檔案都copy到 b.c 的開頭,是以,在實際編譯 b.c 之前,b.c 已經被修改為了如下形式:

由此可見,得到的效果和手動加 test_a() 函數聲明時的效果相同。

#tips# 在Linux下,可以使用 gcc -E b.c 來檢視預編譯 b.c 後的效果。

2. static 關鍵詞的使用

2.1  什麼叫函數重複定義

我們經常會遇到報錯,說變量或者函數重複定義。那麼,在此,首先我舉例說明一下什麼叫函數的重複定義。

那麼,在編譯的時候是不會報錯的,但是,在連結的時候,會出現報錯:

multiple definition of `test',因為在同一個工程裡面出現了兩個test函數的定義。

2.2  在.h裡面寫函數實作

如果在 .h 裡面寫了函數實作,會出現什麼情況?

預編譯後,會發現,b.c 被修改為如下形式:

當然,這樣目前是沒有什麼問題的,可以正常編譯連結成功。但是,如果有一個 c.c 也包含的 a.h 的話,怎麼辦?

同上,c.c 在預編譯後,也形成了如下代碼:

那麼,在連結器進行連結(link)的時候,會報錯:

multiple definition of `test_a'

是以,在 .h 裡面寫函數實作的弊端就暴露出來了。但是,經常會有這樣的需求,将一個函數設定為 内聯(inline) 函數,并且放在 .h 檔案裡面,那麼,怎樣才能防止出現上述 重複定義的報錯呢?

2.3  static 關鍵詞

應對上面的情況,static關鍵詞很好地解決了這個問題。

用static修飾函數,則表明該函數隻能在本檔案中使用,是以,當不同的檔案中有相同的函數名被static修飾時,不會産生重複定義的報錯。例如:

編譯工程時不會報錯,但是test()函數隻能被 a.c 和 b.c 中的函數調用,不能被 c.c 等其他檔案中的函數調用。

那麼,用static修飾 .h 檔案中定義的函數,會有什麼效果呢?

這樣的話,在預編譯後,b.c 和 c.c 檔案中,由于 #include "a.h" ,故在這兩個檔案開頭都會定義 static void test() 函數,是以,test_b() 和 test_c() 均調用的是自己檔案中的 static void test() 函數 , 是以不會産生重複定義的報錯。

是以,結論,在 .h 檔案中定義函數的話,建議一定要加上 static 關鍵詞修飾,這樣,在被多個檔案包含時,才不會産生重複定義的錯誤。

3.  防止頭檔案重複包含

經常寫程式的人都知道,我們在寫 .h 檔案的時候,一般都會加上

這樣做的目的是為了防止頭檔案的重複包含,具體是什麼意思呢?

它不是為了防止多個檔案包含某一個頭檔案,而是為了防止一個頭檔案被同一個檔案包含多次。具體說明如下:

這樣是沒有問題的,但下面這種情況就會有問題。

這樣就不小心産生問題了,因為 b.h 和 c.h 都包含了 a.h,那麼,在預編譯main.c 檔案的時候,會展開為如下形式:

在同一個 .c 裡面,出現了兩次 test_a() 的定義,是以,會出現重複定義的報錯。

但是,如果在 a.h 裡面加上了 #ifndef……#define……#endif 的話,就不會出現這個問題了。

例如,上面的 a.h 改為:

預編譯展開main.c則會出現:

在編譯main.c時,當遇到第二個 #ifndef  A_H ,由于前面已經定義過 A_H,故此段代碼被跳過不編譯,是以,不會産生重複定義的報錯。這就是 #ifndef……#define……#endif 的精髓所在。

繼續閱讀