你了解 #include 某個 .h 文件後,編譯器做了哪些操作麼? 你清楚為什麼在 .h文件中定義函數實現的話需要在函數前面加上 static 修飾麼?你知道 #ifndef……#define……#endif 這種防止頭文件重復包含的精髓所在麼?本文就是來探討這些問題,並給出我的理解和思考,歡迎大家留言交流。
1. #include 命令的作用
1.1 什麼情況不使用 include
- //a.c文件
- void test_a()
- {
- return;
- }
- //b.c文件
- void test_a(); // 函數聲明
- void test_b()
- {
- test_a(); // 由於上面已經聲明了,所以可以使用
- }
其實,這樣的工程,可以不用使用 include 預編譯命令。
1.2 什麼情況使用 include
如果工程裡面的函數特別多,那麼按照上面的做法,則必須在每一個 .c 文件的開頭列出所有本文件調用過的函數的聲明,這樣很不高效,而且一旦某個函數的形式發生變化,又得一個一個改 .c 開頭的函數聲明。
因此,#include 預編譯命令誕生。
- //a.c文件
- void test_a()
- {
- return;
- }
- //a.h文件
- void test_a();
- //b.c文件
- #include "a.h" // 包含含有 test_a() 函數聲明的頭文件
- void test_b()
- {
- test_a();
- }
1.3 #include 起到什麼效果
上述代碼在編譯器進行預編譯的時候,遇到 #include "a.h" ,則會把整個 a.h 文件都copy到 b.c 的開頭,因此,在實際編譯 b.c 之前,b.c 已經被修改為了如下形式:
- //b.c 預編譯後的臨時文件
- void test_a();
- void test_b()
- {
- test_a();
- }
由此可見,得到的效果和手動加 test_a() 函數聲明時的效果相同。
#tips# 在Linux下,可以使用 gcc -E b.c 來查看預編譯 b.c 後的效果。
2. static 關鍵詞的使用
2.1 什麼叫函數重復定義
我們經常會遇到報錯,說變量或者函數重復定義。那麼,在此,首先我舉例說明一下什麼叫函數的重復定義。
- //a.c文件
- void test()
- {
- return;
- }
- //b.c文件
- void test()
- {
- return;
- }
那麼,在編譯的時候是不會報錯的,但是,在鏈接的時候,會出現報錯:
multiple definition of `test',因為在同一個工程裡面出現了兩個test函數的定義。
2.2 在.h裡面寫函數實現
如果在 .h 裡面寫了函數實現,會出現什麼情況?
- //a.h文件
- void test_a()
- {
- return;
- }
- //b.c文件
- #include "a.h"
- void test_b()
- {
- test_a();
- }
預編譯後,會發現,b.c 被修改為如下形式:
- //b.c 預編譯後的臨時文件
- void test_a()
- {
- return;
- }
- void test_b()
- {
- test_a();
- }
當然,這樣目前是沒有什麼問題的,可以正常編譯鏈接成功。但是,如果有一個 c.c 也包含的 a.h 的話,怎麼辦?
- //c.c文件
- #include "a.h"
- void test_c()
- {
- test_a();
- }
同上,c.c 在預編譯後,也形成了如下代碼:
- // c.c 預編譯後的臨時文件
- void test_a()
- {
- return;
- }
- void test_c()
- {
- test_a();
- }
那麼,在鏈接器進行鏈接link)的時候,會報錯:
multiple definition of `test_a'
因此,在 .h 裡面寫函數實現的弊端就暴露出來了。但是,經常會有這樣的需求,將一個函數設置為 內聯inline) 函數,並且放在 .h 文件裡面,那麼,怎樣才能防止出現上述 重復定義的報錯呢?
2.3 static 關鍵詞
應對上面的情況,static關鍵詞很好地解決了這個問題。
用static修飾函數,則表明該函數只能在本文件中使用,因此,當不同的文件中有相同的函數名被static修飾時,不會產生重復定義的報錯。例如:
- //a.c文件
- static void test()
- {
- return;
- }
- void test_a()
- {
- test();
- }
- //b.c文件
- static void test()
- {
- return;
- }
- void test_b()
- {
- test();
- }
編譯工程時不會報錯,但是test()函數只能被 a.c 和 b.c 中的函數調用,不能被 c.c 等其他文件中的函數調用。
那麼,用static修飾 .h 文件中定義的函數,會有什麼效果呢?
- //a.h文件
- static void test()
- {
- return;
- }
- //b.c文件
- #include "a.h"
- void test_b()
- {
- test();
- }
- //c.c文件
- #include "a.h"
- void test_c()
- {
- test();
- }
這樣的話,在預編譯後,b.c 和 c.c 文件中,由於 #include "a.h" ,故在這兩個文件開頭都會定義 static void test() 函數,因此,test_b() 和 test_c() 均調用的是自己文件中的 static void test() 函數 , 因此不會產生重復定義的報錯。
因此,結論,在 .h 文件中定義函數的話,建議一定要加上 static 關鍵詞修飾,這樣,在被多個文件包含時,才不會產生重復定義的錯誤。
3. 防止頭文件重復包含
經常寫程序的人都知道,我們在寫 .h 文件的時候,一般都會加上
- #ifndef XXX
- #define XXX
- ……
- #endif
這樣做的目的是為了防止頭文件的重復包含,具體是什麼意思呢?
它不是為了防止多個文件包含某一個頭文件,而是為了防止一個頭文件被同一個文件包含多次。具體說明如下:
- //a.h文件
- static void test_a()
- {
- return;
- }
- //b.c文件
- #include "a.h"
- void test_b()
- {
- test_a();
- }
- //c.c
- #include "a.h"
- void test_c()
- {
- test_a();
- }
這樣是沒有問題的,但下面這種情況就會有問題。
- //a.h文件
- static void test_a()
- {
- return;
- }
- //b.h文件
- #include "a.h"
- //c.h文件
- #include "a.h"
- //main.c文件
- #include "b.h"
- #include "c.h"
- void main()
- {
- test_a();
- }
這樣就“不小心”產生問題了,因為 b.h 和 c.h 都包含了 a.h,那麼,在預編譯main.c 文件的時候,會展開為如下形式:
- //main.c 預編譯之後的臨時文件
- static void test_a()
- {
- return;
- }
- static void test_a()
- {
- return;
- }
- void main()
- {
- test_a();
- }
在同一個 .c 裡面,出現了兩次 test_a() 的定義,因此,會出現重復定義的報錯。
但是,如果在 a.h 裡面加上了 #ifndef……#define……#endif 的話,就不會出現這個問題了。
例如,上面的 a.h 改為:
- //a.h 文件
- #ifndef A_H
- #define A_H
- static void test_a()
- {
- return;
- }
- #endif
預編譯展開main.c則會出現:
- //main.c 預編譯後的臨時文件
- #ifndef A_H
- #define A_H
- static void test_a()
- {
- return;
- }
- #endif
- #ifndef A_H
- #define A_H
- static void test_a()
- {
- return;
- }
- #endif
- void main()
- {
- test_a();
- }
在編譯main.c時,當遇到第二個 #ifndef A_H ,由於前面已經定義過 A_H,故此段代碼被跳過不編譯,因此,不會產生重復定義的報錯。這就是 #ifndef……#define……#endif 的精髓所在。
本文出自 “對影成三人” 博客,請務必保留此出處http://ticktick.blog.51cto.com/823160/596179