深入理解include預編譯原理


你了解 #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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM