程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 深入理解include預編譯原理

深入理解include預編譯原理

編輯:關於C語言

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

1.  #include 命令的作用

1.1  什麼情況不使用 include

  1. //a.c文件 
  2.  
  3. void test_a() 
  4.     return;  
  5.  
  6.  
  7. //b.c文件 
  8.  
  9. void test_a();  // 函數聲明
  10.  
  11. void test_b() 
  12.     test_a();    // 由於上面已經聲明了,所以可以使用 

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

1.2  什麼情況使用 include

如果工程裡面的函數特別多,那麼按照上面的做法,則必須在每一個 .c 文件的開頭列出所有本文件調用過的函數的聲明,這樣很不高效,而且一旦某個函數的形式發生變化,又得一個一個改 .c 開頭的函數聲明。
因此,#include 預編譯命令誕生。

  1. //a.c文件 
  2.  
  3. void test_a() 
  4.      return;  
  5.  
  6. //a.h文件 
  7.  
  8. void test_a(); 
  9.  
  10. //b.c文件 
  11.  
  12. #include "a.h"    // 包含含有 test_a() 函數聲明的頭文件 
  13.  
  14. void test_b() 
  15.     test_a();         

1.3  #include 起到什麼效果

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

  1. //b.c 預編譯後的臨時文件 
  2.  
  3. void test_a(); 
  4.  
  5. void test_b() 
  6.     test_a();         

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

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

2. static 關鍵詞的使用

2.1  什麼叫函數重復定義

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

  1. //a.c文件 
  2.  
  3. void test() 
  4.     return; 
  5.  
  6. //b.c文件 
  7.  
  8. void test() 
  9.     return; 

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

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

2.2  在.h裡面寫函數實現

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

  1. //a.h文件 
  2.  
  3. void test_a() 
  4.    return;     
  5.  
  6. //b.c文件 
  7.  
  8. #include "a.h" 
  9.  
  10. void test_b() 
  11.     test_a(); 

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

  1. //b.c 預編譯後的臨時文件 
  2.  
  3. void test_a() 
  4.    return;     
  5.  
  6. void test_b() 
  7.     test_a(); 

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

  1. //c.c文件 
  2.  
  3. #include "a.h" 
  4.  
  5. void test_c() 
  6.     test_a(); 

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

  1. // c.c 預編譯後的臨時文件 
  2.  
  3. void test_a() 
  4.     return;     
  5.  
  6. void test_c() 
  7.     test_a(); 

那麼,在鏈接器進行鏈接link)的時候,會報錯:

 multiple definition of `test_a'

因此,在 .h 裡面寫函數實現的弊端就暴露出來了。但是,經常會有這樣的需求,將一個函數設置為 內聯inline) 函數,並且放在 .h 文件裡面,那麼,怎樣才能防止出現上述 重復定義的報錯呢?

2.3  static 關鍵詞

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

用static修飾函數,則表明該函數只能在本文件中使用,因此,當不同的文件中有相同的函數名被static修飾時,不會產生重復定義的報錯。例如:

  1. //a.c文件 
  2.  
  3. static void test() 
  4.     return; 
  5.  
  6. void test_a() 
  7.     test(); 
  8.  
  9. //b.c文件 
  10.  
  11. static void test() 
  12.     return; 
  13.  
  14. void test_b() 
  15.     test(); 

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

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

  1. //a.h文件 
  2.  
  3. static void test() 
  4.     return; 
  5.  
  6. //b.c文件 
  7.  
  8. #include "a.h" 
  9.  
  10. void test_b() 
  11.     test(); 
  12.  
  13. //c.c文件 
  14.  
  15. #include "a.h" 
  16.  
  17. void test_c() 
  18.     test(); 

這樣的話,在預編譯後,b.c 和 c.c 文件中,由於 #include "a.h" ,故在這兩個文件開頭都會定義 static void test() 函數,因此,test_b() 和 test_c() 均調用的是自己文件中的 static void test() 函數 , 因此不會產生重復定義的報錯。

因此,結論,在 .h 文件中定義函數的話,建議一定要加上 static 關鍵詞修飾,這樣,在被多個文件包含時,才不會產生重復定義的錯誤。

3.  防止頭文件重復包含

經常寫程序的人都知道,我們在寫 .h 文件的時候,一般都會加上

  1. #ifndef    XXX 
  2. #define   XXX  
  3. …… 
  4. #endif 

這樣做的目的是為了防止頭文件的重復包含,具體是什麼意思呢?

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

  1. //a.h文件 
  2.  
  3. static void test_a() 
  4.     return; 
  5.  
  6. //b.c文件 
  7.  
  8. #include "a.h" 
  9.  
  10. void test_b() 
  11.     test_a(); 
  12.  
  13. //c.c 
  14.  
  15. #include "a.h" 
  16.  
  17. void test_c() 
  18.     test_a(); 

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

  1. //a.h文件 
  2.  
  3. static void test_a() 
  4.     return; 
  5.  
  6. //b.h文件 
  7.  
  8. #include "a.h" 
  9.  
  10. //c.h文件 
  11.  
  12. #include "a.h" 
  13.  
  14. //main.c文件 
  15.  
  16. #include "b.h" 
  17. #include "c.h" 
  18.  
  19. void main() 
  20.     test_a(); 

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

  1. //main.c 預編譯之後的臨時文件 
  2.  
  3. static void test_a() 
  4.     return; 
  5.  
  6. static void test_a() 
  7.     return; 
  8.  
  9. void main() 
  10.     test_a(); 

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

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

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

  1. //a.h 文件 
  2.  
  3. #ifndef  A_H 
  4. #define A_H 
  5.  
  6. static void test_a() 
  7.     return; 
  8.  
  9. #endif 

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

  1. //main.c 預編譯後的臨時文件 
  2.  
  3. #ifndef A_H 
  4. #define A_H 
  5.  
  6. static void test_a() 
  7.     return; 
  8.  
  9. #endif 
  10.  
  11. #ifndef A_H 
  12. #define A_H 
  13.  
  14. static void test_a() 
  15.     return; 
  16.  
  17. #endif 
  18.  
  19. void main() 
  20.     test_a(); 

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

本文出自 “對影成三人” 博客,請務必保留此出處http://ticktick.blog.51cto.com/823160/596179

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved