C語言 原始檔和標頭檔案理解

2021-08-28 23:34:18 字數 4100 閱讀 4569

簡單的說其實要理解c檔案與標頭檔案(即.h)有什麼不同之處,首先需要弄明白編譯器的工作過程,一般說來編譯器會做以下幾個過程:

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) 

標頭檔案內容如下: 

int test;

現在以這個例子來講解編譯器的工作: 

1.預處理階段:編譯器以c檔案作為一 個單元,首先讀這個c檔案,發現第一句與第二句是包含乙個標頭檔案,就會在所有搜尋路徑中尋找這兩個檔案,找到之後,就會將相應標頭檔案中再去處理巨集,變數, 函式宣告,巢狀的標頭檔案包含等,檢測依賴關係,進行巨集替換,看是否有重複定義與宣告的情況發生,最後將那些檔案中所有的東東全部掃瞄進這個當前的c檔案 中,形成乙個中間「c檔案」

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)、存在高次方程,列出方程也解不出。而後者已經是數學的範疇了,所以說,最難之處還在於掌握清晰的概念;

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

void foo();

#include "a.h"   //我的問題出來了:這句話是要,還是不要?

void foo()

#include "a.h"

int main(int argc, char *argv)

針對上面的**,請回答三個問題:

a.c 中的 #include "a.h" 這句話是不是多餘的? 

為什麼經常見 xx.c 裡面 include 對應的 xx.h? 

如果 a.c 中不寫,那麼編譯器是不是會自動把 .h 檔案裡面的東西跟同名的 .c 檔案繫結在一起? 

(請針對上面3道題仔細考慮10分鐘,莫要著急看下面的解釋。:) 考慮的越多,下面理解的就越深。)

c語言標頭檔案和原始檔 C語言標頭檔案防衛式宣告

c語言一般提供三種預處理功能 巨集處理 檔案包含 條件編譯。標頭檔案防衛式申明中會用到條件編譯中 ifndef define endif的用法。所以,首先價紹下條件編譯。一般情況下,在生成可執行檔案的過程中,源程式檔案中的所有 行都進行編譯,但是在一些跨作業系統的情況下,要求 既能在windows下...

檔案 標頭檔案 原始檔(C )

標頭檔案和原始檔中的函式宣告與定義 函式原型 file fsopen const char filename,const char mode,int shflag file wfsopen const wchar t filename,const wchar t mode,int shflag 注 ...

STM32重要原始檔和標頭檔案說明

stm32f4xx ppp.h 外設標頭檔案。這裡的ppp只是乙個 在實際上是具體的外設名字,如adc,dma等。在實際使用時根據所需的外設選擇性移植。在stm32f4xx stdperiph driver inc中stm32f4xx ppp.c 外設原始檔。這裡的ppp只是乙個 在實際上是具體的外...