隨着代碼越寫越長,一個源文件的體制越來越臃腫。於是提倡將代碼寫到不同的多個源文件中去。將代碼寫到多個源文件中去就會遇到各個源文件中函數與變量的調用規則問題。
通常人們習慣性的把宏定義、結構體、聯合體、枚舉、外部變量和外部函數聲明等寫入到頭 文件.h中去,而把函數的聲明、變量定義等寫入到.c文件中去。當某一.c源文件需要調用某一函數的時候,只要將包含這個函數聲明的頭文件包含到本文件中 來就可以了。如a.h中聲明了a.c中定義的fun()函數,而b.c需要fun()函數,只需要#include ”a.h”就可以了。B.c怎么找到fun()的呢,難道a.c和a.h間通過同名建立了一種聯系,b.c會直接到a.c中去尋找fun()?
很多時候,人們習慣性的把一個源文件中的宏定義、結構體、聯合體、枚舉、外部變量和外部函數聲明等寫入到一個同名的頭文件中去。這給人的錯覺就是同名使得兩者被關聯起來,在b.c包含a.h時,會自動去a.c中去尋找fun()函數。其實不然。
再給一個例子,如a.h中聲明了b.c中定義的fun()函數,而c.c需要fun()函數,只需要#include”a.h”就可以了。這個例子就說明,不是同名的頭文件也可以用來聲明源文件中的函數等。那怎么通過頭文件尋找到函數定義呢?
原理要從編譯器工作過程說起。編譯器工作過程分為4步驟:
1預處理階段 :以c源文件為單位,處理宏替換、條件編譯和文件包含。預處理本質上就是代碼文本的替換。拿頭文件包含來說,就是把包含的頭文件中的所有代碼copy到源文件中去。於是就形成了一個“中間c文件”。
2語法分析階段 :c語言語法分析,你懂得。
3編譯階段:以“中間c文件”為單位,首先編譯成純匯編語句,再將之匯編成跟CPU相關的二進制碼,生成各個目標文件 (.obj文件)。編譯階段不會去尋找用到的頭文件中的函數的定義,只是按照一定規格轉換代碼格式為二進制。
4連接階段:將上一步成生的各個目標文件,根據一些參數,連接生成最終的可執行文件。主要的工作就是重定位各個目標文件的函數,變量等。
我們在b.c或c.c中用#include “a.h”實際上是引入相關聲明,使得編譯可以通過,程序並不關心實現是在哪里,是怎么實現的。源文件編譯后成生了目標文件(.o或.obj文件),目標 文件中,這些函數和變量就視作一個個符號。在link的時候,需要在makefile里面說明需要連接哪個.o或.obj文件(在這里是b.c生成的.o 或.obj文件),此時,連接器會去這個.o或.obj文件中找在c.c中實現的函數,再把他們build到makefile中指定的那個可以執行文件 中。通常,編譯器會在每個.o或.obj文件中都去找一下所需要的符號,而不是只在某個文件中找或者說找到一個就不找了。因此,如果在幾個不同源文件中都 包含了同一個頭文件,該頭文件中的某些變量默認地就會被編譯超過一次,鏈接的時候就會提示“redefined”。這也就解釋了重定義一般不會在編譯階段出錯,而會在鏈接階段出錯。
總結出來一句話:源文件調用#inlude頭文件中的函數不是通過直接查找同名源文件找到的,而是通過逐個查找已經編譯好的.o或者.obj找到的。但是還是建議使用同名的源文件和頭文件,這樣便於形成模塊化,使代碼清晰易懂,便於打理。
編譯器的編譯過程告訴我們另外一個寫頭文件需要注意的地方,頭文件的預編譯在插入有用 代碼的同時也會插入大量不需要的東西,一個頭文件被多個源文件包含還會出現重定義的問題。這些都是需要注意的。插入了不需要的東西在很大程度上是不可避免 的,但是精簡了代碼,看起來簡潔了不少。我們可以把那些經常在一起使用的宏定義、聲明等放在一個頭文件中,減少不必要的公用。關於重定義,可以通過#ifndef #define #endif來解決。下面以一個例子來說明。
假設有三個文件
node.h //定義節點
list.h //對鏈表的操作函數
test.c //測試函數
包含關系如下:
list.h中
#include"node.h"
test.c中
#include"list.h"
#include"node.h"
#include ... 省略其它必要的頭文件
使用命令編譯
$gcc -o testtest.c
編譯時,會出現錯誤:XXX重定義
為什么呢?
1)test.c中包含了node.h,因為node.h是定義結構的文件,而且已經被list.h包含了,所以這里node.h會預編譯兩次,出現重定義!
所以,可以去掉test.c中的頭文件node.h即可
2)修改node.h,避免重定義,這種方法也是推薦的方法
- #ifndef _NODE
- #define _NODE
- typedef struct node{
- int x;
- struct node*next;
- }NODE;
- #endif
使用一個標記變量_NODE來表示NODE結構已經被定義了,將定義過程包含在#ifndef~#endif中,這樣,不管包含多少次node.h文件,都不會出現重定義。
當然,這不僅僅限於結構的定義。
轉載地址:http://blog.csdn.net/sunmingming512/article/details/12756997