你了解 #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 XXX #define XXX …… #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/
參考:http://blog.csdn.net/softmanfly/article/details/41699511