深入理解include預編譯原理

2021-09-03 10:20:03 字數 4041 閱讀 4956

1.  #include 命令的作用

1.1  什麼情況不使用 include

檔案 

void test_a()   

檔案 void test_a();  // 函式宣告 

void test_b() 

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

1.2  什麼情況使用 include

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

因此,#include 預編譯命令誕生。

檔案 

void test_a()   

檔案 void test_a(); 

檔案 #include "a.h"    // 包含含有 test_a() 函式宣告的標頭檔案 

void test_b() 

1.3  #include 起到什麼效果

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

預編譯後的臨時檔案 

void test_a(); 

void test_b() 

由此可見,得到的效果和手動加 test_a() 函式宣告時的效果相同。

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

2.1  什麼叫函式重複定義

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

檔案 

void test()   

檔案 void test() 

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

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

2.2  在.h裡面寫函式實現

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

檔案 

void test_a()   

檔案 #include "a.h" 

void test_b() 

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

預編譯後的臨時檔案 

void test_a()   

void test_b() 

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

檔案 

#include "a.h" 

void test_c() 

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

// c.c 預編譯後的臨時檔案 

void test_a()   

void test_c() 

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

multiple definition of `test_a'

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

檔案 

static void test()   

void test_a()   

檔案 static void test()   

void test_b() 

編譯工程時不會報錯,但是test()函式只能被 a.c 和 b.c 中的函式呼叫,不能被 c.c 等其他檔案中的函式呼叫。

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

檔案 

static void test()   

檔案 #include "a.h" 

void test_b()   

檔案 #include "a.h" 

void test_c() 

3.  防止標頭檔案重複包含

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

#ifndef    *** 

#define   ***  

…… #endif

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

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

檔案 

static void test_a()   

檔案 #include "a.h" 

void test_b()   

#include "a.h" 

void test_c() 

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

檔案 

static void test_a()   

檔案 #include "a.h" 

檔案 #include "a.h" 

檔案 #include "b.h" 

#include "c.h" 

void main() 

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

預編譯之後的臨時檔案 

static void test_a()   

static void test_a()   

void main() 

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

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

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

檔案 

#ifndef  a_h 

#define a_h 

static void test_a()   

#endif

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

預編譯後的臨時檔案 

#ifndef a_h 

#define a_h 

static void test_a()   

#endif 

#ifndef a_h 

#define a_h 

static void test_a()   

#endif 

void main() 

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

深入理解include預編譯原理

1.include 命令的作用 1.1 什麼情況不使用 include 檔案 void test a 檔案 void test a 函式宣告 void test b 其實,這樣的工程,可以不用使用 include 預編譯命令。1.2 什麼情況使用 include 如果工程裡面的函式特別多,那麼按照上...

深入理解include預編譯原理

1.include 命令的作用 1.1 什麼情況不使用 include 檔案 void test a 檔案 void test a 函式宣告 void test b 其實,這樣的工程,可以不用使用 include 預編譯命令。1.2 什麼情況使用 include 如果工程裡面的函式特別多,那麼按照上...

深入理解Android EventBus原理

1.定義乙個evnet public static class messageevent2.準備觀察者 宣告和注釋你的訂閱方法,可選地指定執行緒模式 subscribe threadmode threadmode.main 比如這個就指定主線程 關於型別的解釋介紹請查閱 註冊和反註冊在你的使用中,例...