C語言中,頭文件和源文件的關係(轉)

簡單的說其實要理解C文件與頭文件(即.h)有什麼不一樣之處,首先須要弄明白編譯器的工做過程,通常說來編譯器會作如下幾個過程:html


1.預處理階段 
2.詞法與語法分析階段 
3.編譯階段,首先編譯成純彙編語句,再將之彙編成跟CPU相關的二進制碼,生成各個目標文件 (.obj文件)
4.鏈接階段,將各個目標文件中的各段代碼進行絕對地址定位,生成跟特定平臺相關的可執行文件,固然,最後還能夠用objcopy生成純二進制碼,也就是去掉了文件格式信息。(生成.exe文件)程序員

編譯器在編譯時是以C文件爲單位進行的,也就是說若是你的項目中一個C文件都沒有,那麼你的項目將沒法編譯,鏈接器是以目標文件爲單位,它將一個或多個目標文件進行函數與變量的重定位,生成最終的可執行文件,在PC上的程序開發,通常都有一個main函數,這是各個編譯器的約定,固然,你若是本身寫鏈接器腳本的話,能夠不用main函數做爲程序入口!!!!算法

(main .c文件 目標文件 可執行文件 )編程

有了這些基礎知識,再言歸正傳,爲了生成一個最終的可執行文件,就須要一些目標文件,也就是須要C文件,而這些C文件中又須要一個main函數做爲可執行程序的入口,那麼咱們就從一個C文件入手,假定這個C文件內容以下: 
#include 
#include "mytest.h"數組

int main(int argc,char **argv) 

test = 25; 
printf("test.................%d/n",test); 
}安全

頭文件內容以下: 
int test;函數

如今以這個例子來說解編譯器的工做: 
1.預處理階段:編譯器以C文件做爲一 個單元,首先讀這個C文件,發現第一句與第二句是包含一個頭文件,就會在全部搜索路徑中尋找這兩個文件,找到以後,就會將相應頭文件中再去處理宏,變量, 函數聲明,嵌套的頭文件包含等,檢測依賴關係,進行宏替換,看是否有重複定義與聲明的狀況發生,最後將那些文件中全部的東東所有掃描進這個當前的C文件 中,造成一箇中間「C文件」ui


2.編譯階段,在上一步中至關於將那個頭文件中的test變量掃描進了一箇中 間C文件,那麼test變量就變成了這個文件中的一個全局變量,此時就將全部這個中間C文件的全部變量,函數分配空間,將各個函數編譯成二進制碼,按照特 定目標文件格式生成目標文件,在這種格式的目標文件中進行各個全局變量,函數的符號描述,將這些二進制碼按照必定的標準組織成一個目標文件設計

3.鏈接階段,將上一步成生的各個目標文件,根據一些參數,鏈接生成最終的可 執行文件,主要的工做就是重定位各個目標文件的函數,變量等,至關於將個目標文件中的二進制碼按必定的規範合到一個文件中再回到C文件與頭文件各寫什麼內 容的話題上:理論上來講C文件與頭文件裏的內容,只要是C語言所支持的,不管寫什麼均可以的,好比你在頭文件中寫函數體,只要在任何一個C文件包含此頭文 件就能夠將這個函數編譯成目標文件的一部分(編譯是以C文件爲單位的,若是不在任何C文件中包含此頭文件的話,這段代碼就形同虛設),你能夠在C文件中進 行函數聲明,變量聲明,結構體聲明,這也不成問題!!!那爲什麼必定要分紅頭文件與C文件呢?又爲什麼通常都在頭件中進行函數,變量聲明,宏聲明,結構體聲明 呢?而在C文件中去進行變量定義,函數實現呢??緣由以下: 
1.若是在頭文件中實現一個函數體,那麼若是在多個C文件中引用它,並且又同時編 譯多個C文件,將其生成的目標文件鏈接成一個可執行文件,在每一個引用此頭文件的C文件所生成的目標文件中,都有一份這個函數的代碼,若是這段函數又沒有定 義成局部函數,那麼在鏈接時,就會發現多個相同的函數,就會報錯 
2.若是在頭文件中定義全局變量,而且將此全局變量賦初值,那麼在多個引用此 頭文件的C文件中一樣存在相同變量名的拷貝,關鍵是此變量被賦了初值,因此編譯器就會將此變量放入DATA段,最終在鏈接階段,會在DATA段中存在多個 相同的變量,它沒法將這些變量統一成一個變量,也就是僅爲此變量分配一個空間,而不是多份空間,假定這個變量在頭文件沒有賦初值,編譯器就會將之放入 BSS段,鏈接器會對BSS段的多個同名變量僅分配一個存儲空間 
3.若是在C文件中聲明宏,結構體,函數等,那麼我要在另外一個C文件中引用相 應的宏,結構體,就必須再作一次重複的工做,若是我改了一個C文件中的一個聲明,那麼又忘了改其它C文件中的聲明,這不就出了大問題了,程序的邏輯就變成 了你不可想象的了,若是把這些公共的東東放在一個頭文件中,想用它的C文件就只須要引用一個就OK了!!!這樣豈不方便,要改某個聲明的時候,只須要動一 下頭文件就好了 
4.在頭文件中聲明結構體,函數等,當你須要將你的代碼封裝成一個庫,讓別人來用你的代碼,你又不想公佈源碼,那麼人家如何利 用你的庫呢?也就是如何利用你的庫中的各個函數呢??一種方法是公佈源碼,別人想怎麼用就怎麼用,另外一種是提供頭文件,別人從頭文件中看你的函數原型,這 樣人家才知道如何調用你寫的函數,就如同你調用printf函數同樣,裏面的參數是怎樣的??你是怎麼知道的??還不是看人家的頭文件中的相關聲明 啊!!!固然這些東東都成了C標準,就算不看人家的頭文件,你同樣能夠知道怎麼使用調試

c語言中.c和.h文件的困惑
 

本質上沒有任何區別。   只不過通常: 
.h文件是頭文件,內含函數聲明、宏定義、結構體定義等內容.c文件是程序文件,內含函數實現,變量定義等內容。並且是什麼後綴也沒有關係,只不過編譯器會默認對某些後綴的文件採起某些動做。你能夠強制編譯器把任何後綴的文件都看成c文件來編。

這樣分開寫成兩個文件是一個良好的編程風格。


並且,比方說 我在aaa.h裏定義了一個函數的聲明,而後我在aaa.h的同一個目錄下創建aaa.c , aaa.c裏定義了這個函數的實現,而後是在main函數所在.c文件裏#include這個aaa.h  而後我就可使用這個函數了。 main在運行時就會找到這個定義了這個函數的aaa.c文件。這是由於:main函數爲標準C/C++的程序入口,編譯器會先找到該函數所在的文件。假定編譯程序編譯myproj.c(其中含main())時,發現它include了mylib.h(其中聲明瞭函數void test()),那麼此時編譯器將按照事先設定的路徑(Include路徑列表及代碼文件所在的路徑)查找與之同名的實現文件(擴展名爲.cpp或.c,此例中爲mylib.c),若是找到該文件,並在其中找到該函數(此例中爲void test())的實現代碼,則繼續編譯;若是在指定目錄找不到實現文件,或者在該文件及後續的各include文件中未找到實現代碼,則返回一個編譯錯誤.其實include的過程徹底能夠「當作」是一個文件拼接的過程,將聲明和實現分別寫在頭文件及C文件中,或者將兩者同時寫在頭文件中,理論上沒有本質的區別。以上是所謂動態方式。對於靜態方式,基本全部的C/C++編譯器都支持一種連接方式被稱爲Static Link,即所謂靜態連接。在這種方式下,咱們所要作的,就是寫出包含函數,類等等聲明的頭文件(a.h,b.h,...),以及他們對應的實現文件(a.cpp,b.cpp,...),編譯程序會將其編譯爲靜態的庫文件(a.lib,b.lib,...)。在隨後的代碼重用過程當中,咱們只須要提供相應的頭文件(.h)和相應的庫文件(.lib),就可使用過去的代碼了。相對動態方式而言,靜態方式的好處是實現代碼的隱蔽性,即C++中提倡的「接口對外,實現代碼不可見」。有利於庫文件的轉發.c文件和.h文件的概念與聯繫
      若是說難題最難的部分是基本概念,可能不少人都會持反對意見,但實際上也確實如此。我高中的時候學物理,老師抓的重點就是概念——概念必定要搞清,因而難題也成了容易題。若是你能分析清楚一道物理難題存在着幾個物理過程,每個過程都遵照那一條物理定律(好比動量守恆、牛II定律、能量守恆),那麼就很輕鬆的根據定律列出這個過程的方程,N個過程一定是N個N元方程,難題也就迎刃而解。即使是高中的物理競賽難題,最難之處也不過在於:

(1)、混淆你的概念,讓你沒法分析出幾個物理過程,或某個物理過程遵循的那條物理定律;
(2)、存在高次方程,列出方程也解不出。然後者已是數學的範疇了,因此說,最難之處還在於掌握清晰的概念;

  程序設計也是如此,若是概念很清晰,那基本上沒什麼難題(會難在數學上,好比算法的選擇、時間空間與效率的取捨、穩定與資源的平衡上)。可是,要掌握清晰的概念也沒那麼容易。好比下面這個例子,看看你有沒有很清晰透徹的認識。

//a.h
void foo();

//a.c
#include "a.h"   //個人問題出來了:這句話是要,仍是不要?
void foo()
{
     return;
}

//main.c
#include "a.h"
int main(int argc, char *argv[])
{
    foo(); 
  return 0;
}                
針對上面的代碼,請回答三個問題:

a.c 中的 #include "a.h" 這句話是否是多餘的? 
爲何常常見 xx.c 裏面 include 對應的 xx.h? 
若是 a.c 中不寫,那麼編譯器是否是會自動把 .h 文件裏面的東西跟同名的 .c 文件綁定在一塊兒? 
(請針對上面3道題仔細考慮10分鐘,莫要着急看下面的解釋。:) 考慮的越多,下面理解的就越深。)

  好了,時間到!請忘掉上面的3道題,以及對這三道題引起出的你的想法,而後再聽我慢慢道來。正確的概念是:從C編譯器角度看,.h和.c皆是浮雲,就是更名爲.txt、.doc也沒有大的分別。換句話說,就是.h和.c沒啥必然聯繫。.h中通常放的是同名.c文件中定義的變量、數組、函數的聲明,須要讓.c外部使用的聲明。這個聲明有啥用?只是讓須要用這些聲明的地方方便引用。由於 #include "xx.h" 這個宏其實際意思就是把當前這一行刪掉,把 xx.h 中的內容原封不動的插入在當前行的位置。因爲想寫這些函數聲明的地方很是多(每個調用 xx.c 中函數的地方,都要在使用前聲明一會兒),因此用 #include "xx.h" 這個宏就簡化了許多行代碼——讓預處理器本身替換好了。也就是說,xx.h 其實只是讓須要寫 xx.c 中函數聲明的地方調用(能夠少寫幾行字),至於 include 這個 .h 文件是誰,是 .h 仍是 .c,仍是與這個 .h 同名的 .c,都沒有任何須然關係。
  這樣你可能會說:啊?那我平時只想調用 xx.c 中的某個函數,卻 include了 xx.h 文件,豈不是宏替換後出現了不少無用的聲明?沒錯,確實引入了不少垃圾,可是它卻省了你很多筆墨,而且整個版面也看起來清爽的多。魚與熊掌不可得兼,就是這個道理。反正多些聲明(.h通常只用來放聲明,而放不定義,參見拙著「過馬路,左右看」)也無害處,又不會影響編譯,何樂而不爲呢?
翻回頭再看上面的3個問題,很好解答了吧?

答:不必定。這個例子中顯然是多餘的。可是若是.c中的函數也須要調用同個.c中的其它函數,那麼這個.c每每會include同名的.h,這樣就不須要爲聲明和調用順序而發愁了(C語言要求使用以前必須聲明,而include同名.h通常會放在.c的開頭)。有不少工程甚至把這種寫法約定爲代碼規範,以規範出清晰的代碼來。 
答:1中已經回答過了。 
答:不會。問這個問題的人絕對是概念不清,要不就是想混水摸魚。很是討厭的是中國的不少考試出的都是這種爛題,生怕別人有個清楚的概念了,絕對要把考生搞暈。 
搞清楚語法和概念說易也易,說難也難。竅門有三點:

不要暈着頭工做,要抽空多思考思考,多看看書; 
看書要看好書,問人要問強人。爛書和爛人都會給你一個錯誤的概念,誤導你; 
勤能補拙是良訓,一分辛苦一分才; 
 

(1)經過頭文件來調用庫功能。在不少場合,源代碼不便(或不許)向用戶公佈,只要向用戶提供頭文件和二進制的庫便可。用戶只須要按照頭文件中的接口聲明來調用庫功能,而沒必要關心接口怎麼實現的。編譯器會從庫中提取相應的代碼。
(2)頭文件能增強類型安全檢查。若是某個接口被實現或被使用時,其方式與頭文件中的聲明不一致,編譯器就會指出錯誤,這一簡單的規則能大大減輕程序員調試、改錯的負擔。
頭文件用來存放函數原型。

頭文件如何來關聯源文件?

 這個問題其實是說,已知頭文件「a.h」聲明瞭一系列函數(僅有函數原型,沒有函數實現),「b.cpp」中實現了這些函數,那麼若是我想在「c.cpp」中使用「a.h」中聲明的這些在「b.cpp」中實現的函數,一般都是在「c.cpp」中使用#include 「a.h」,那麼c.cpp是怎樣找到b.cpp中的實現呢? 
其實.cpp和.h文件名稱沒有任何直接關係,不少編譯器均可以接受其餘擴展名。 
譚浩強老師的《C程序設計》一書中提到,編譯器預處理時,要對#include命令進行「文件包含處理」:將headfile.h的所有內容複製到#include 「headfile.h」處。這也正說明了,爲何不少編譯器並不care到底這個文件的後綴名是什麼----由於#include預處理就是完成了一個「複製並插入代碼」的工做。 
程序編譯的時候,並不會去找b.cpp文件中的函數實現,只有在link的時候才進行這個工做。咱們在b.cpp或c.cpp中用#include 「a.h」其實是引入相關聲明,使得編譯能夠經過,程序並不關心實現是在哪裏,是怎麼實現的。源文件編譯後成生了目標文件(.o或.obj文件),目標文件中,這些函數和變量就視做一個個符號。在link的時候,須要在makefile裏面說明須要鏈接哪一個.o或.obj文件(在這裏是b.cpp生成的.o或.obj文件),此時,鏈接器會去這個.o或.obj文件中找在b.cpp中實現的函數,再把他們build到makefile中指定的那個能夠執行文件中。 
在VC中,一幫狀況下不須要本身寫makefile,只須要將須要的文件都包括在project中,VC會自動幫你把makefile寫好。 
一般,編譯器會在每一個.o或.obj文件中都去找一下所須要的符號,而不是隻在某個文件中找或者說找到一個就不找了。所以,若是在幾個不一樣文件中實現了同一個函數,或者定義了同一個全局變量,連接的時候就會提示「redefined」.

本文來自博客園博客,轉載請標明出處:http://www.cnblogs.com/infiniti/archive/2013/03/19/2968689.html

相關文章
相關標籤/搜索