C語言中.h和.c文件解析

  簡單的說其實要理解C文件與頭文件(即.h)有什么不同之處瞄勾,首先需要弄明白編譯器的工作過程兴蒸,一般說來編譯器會做以下幾個過程:

  1.預處理階段

  2.詞法與語法分析階段

  3.編譯階段,首先編譯成純匯編語句,再將之匯編成跟CPU相關的二進制碼滔驶,生成各個目標文件 (.obj文件)

  4.連接階段贺归,將各個目標文件中的各段代碼進行絕對地址定位淆两,生成跟特定平臺相關的可執(zhí)行文件,當然拂酣,最后還可以用objcopy生成純二進制碼秋冰,也就是去掉了文件格式信息。(生成.exe文件)

  編譯器在編譯時是以C文件為單位進行的婶熬,也就是說如果你的項目中一個C文件都沒有剑勾,那么你的項目將無法編譯,連接器是以目標文件為單位赵颅,它將一個或多個目標文件進行函數與變量的重定位虽另,生成最終的可執(zhí)行文件,在PC上的程序開發(fā)饺谬,一般都有一個main函數捂刺,這是各個編譯器的約定,當然募寨,你如果自己寫連接器腳本的話族展,可以不用main函數作為程序入口!!!!

  (main .c文件 目標文件 可執(zhí)行文件)

  有了這些基礎知識,再言歸正傳拔鹰,為了生成一個最終的可執(zhí)行文件仪缸,就需要一些目標文件,也就是需要C文件列肢,而這些C文件中又需要一個main函數作為可執(zhí)行程序的入口恰画,那么我們就從一個C文件入手,假定這個C文件內容如下:

  #include

  #include "mytest.h"

  int main(int argc,char **argv)

  {

  test = 25;

  printf("test.................%d\n",test);

  }

  mytest.h頭文件內容如下:

  int test;

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

  1.預處理階段:編譯器以C文件作為一個單元瓷马,首先讀這個C文件拴还,發(fā)現第一句與第二句是包含一個頭文件,就會在所有搜索路徑中尋找這兩個文件欧聘,找到之后片林,就會將相應頭文件中再去處理宏,變量,函數聲明拇厢,嵌套的頭文件包含等爱谁,檢測依賴關系,進行宏替換孝偎,看是否有重復定義與聲明的情況發(fā)生访敌,最后將那些文件中所有的東東全部掃描進這個當前的C文件中,形成一個中間"C文件"

  2.編譯階段衣盾,在上一步中相當于將那個頭文件中的test變量掃描進了一個中間C文件寺旺,那么test變量就變成了這個文件中的一個全局變量,此時就將所有這個中間C文件的所有變量势决,函數分配空間阻塑,將各個函數編譯成二進制碼,按照特定目標文件格式生成目標文件果复,在這種格式的目標文件中進行各個全局變量陈莽,函數的符號描述,將這些二進制碼按照一定的標準組織成一個目標文件

  3.連接階段虽抄,將上一步成生的各個目標文件走搁,根據一些參數,連接生成最終的可執(zhí)行文件迈窟,主要的工作就是重定位各個目標文件的函數私植,變量等,相當于將個目標文件中的二進制碼按一定的規(guī)范合到一個文件中再回到C文件與頭文件各寫什么內容的話題上:理論上來說C文件與頭文件里的內容车酣,只要是C語言所支持的曲稼,無論寫什么都可以的,比如你在頭文件中寫函數體湖员,只要在任何一個C文件包含此頭文件就可以將這個函數編譯成目標文件的一部分(編譯是以C文件為單位的贫悄,如果不在任何C文件中包含此頭文件的話,這段代碼就形同虛設)破衔,你可以在C文件中進行函數聲明清女,變量聲明钱烟,結構體聲明晰筛,這也不成問題!!!那為何一定要分成頭文件與C文件呢?又為何一般都在頭件中進行函數,變量聲明拴袭,宏聲明读第,結構體聲明呢?而在C文件中去進行變量定義,函數實現呢??原因如下:

  1.如果在頭文件中實現一個函數體拥刻,那么如果在多個C文件中引用它怜瞒,而且又同時編譯多個C文件,將其生成的目標文件連接成一個可執(zhí)行文件,在每個引用此頭文件的C文件所生成的目標文件中吴汪,都有一份這個函數的代碼惠窄,如果這段函數又沒有定義成局部函數,那么在連接時漾橙,就會發(fā)現多個相同的函數杆融,就會報錯

  2.如果在頭文件中定義全局變量,并且將此全局變量賦初值霜运,那么在多個引用此頭文件的C文件中同樣存在相同變量名的拷貝脾歇,關鍵是此變量被賦了初值,所以編譯器就會將此變量放入DATA段淘捡,最終在連接階段藕各,會在DATA段中存在多個相同的變量,它無法將這些變量統一成一個變量焦除,也就是僅為此變量分配一個空間激况,而不是多份空間,假定這個變量在頭文件沒有賦初值膘魄,編譯器就會將之放入

BSS段誉碴,連接器會對BSS段的多個同名變量僅分配一個存儲空間

  3.如果在C文件中聲明宏,結構體瓣距,函數等黔帕,那么我要在另一個C文件中引用相應的宏,結構體蹈丸,就必須再做一次重復的工作成黄,如果我改了一個C文件中的一個聲明,那么又忘了改其它C文件中的聲明逻杖,這不就出了大問題了奋岁,程序的邏輯就變成了你不可想象的了,如果把這些公共的東東放在一個頭文件中荸百,想用它的C文件就只需要引用一個就OK了!!!這樣豈不方便闻伶,要改某個聲明的時候,只需要動一下頭文件就行了

  4.在頭文件中聲明結構體够话,函數等蓝翰,當你需要將你的代碼封裝成一個庫,讓別人來用你的代碼女嘲,你又不想公布源碼畜份,那么人家如何利用你的庫呢?也就是如何利用你的庫中的各個函數呢??一種方法是公布源碼,別人想怎么用就怎么用欣尼,另一種是提供頭文件爆雹,別人從頭文件中看你的函數原型,這樣人家才知道如何調用你寫的函數,就如同你調用printf函數一樣钙态,里面的參數是怎樣的??你是怎么知道的??還不是看人家的頭文件中的相關聲明啊!!!當然這些東東都成了C標準慧起,就算不看人家的頭文件,你一樣可以知道怎么使用

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

  本質上沒有任何區(qū)別册倒。 只不過一般:.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())時疙赠,發(fā)現它include了mylib.h(其中聲明了函數void

test()),那么此時編譯器將按照事先設定的路徑(Include路徑列表及代碼文件所在的路徑)查找與之同名的實現文件(擴展名為.cpp或.c朦拖,此例中為mylib.c)圃阳,如果找到該文件,并在其中找到該函數(此例中為void

test())的實現代碼璧帝,則繼續(xù)編譯;如果在指定目錄找不到實現文件捍岳,或者在該文件及后續(xù)的各include文件中未找到實現代碼,則返回一個編譯錯誤.其實include的過程完全可以"看成"是一個文件拼接的過程睬隶,將聲明和實現分別寫在頭文件及C文件中锣夹,或者將二者同時寫在頭文件中,理論上沒有本質的區(qū)別理疙。

  以上是所謂動態(tài)方式晕城。

  對于靜態(tài)方式泞坦,基本所有的C/C++編譯器都支持一種鏈接方式被稱為Static Link窖贤,即所謂靜態(tài)鏈接。

  在這種方式下,我們所要做的赃梧,就是寫出包含函數滤蝠,類等等聲明的頭文件(a.h,b.h,...),以及他們對應的實現文件(a.cpp,b.cpp,...)授嘀,編譯程序會將其編譯為靜態(tài)的庫文件(a.lib,b.lib,...)物咳。在隨后的代碼重用過程中,我們只需要提供相應的頭文件(.h)和相應的庫文件(.lib)蹄皱,就可以使用過去的代碼了览闰。

  相對動態(tài)方式而言,靜態(tài)方式的好處是實現代碼的隱蔽性巷折,即C++中提倡的"接口對外压鉴,實現代碼不可見"。有利于庫文件的轉發(fā).

  如果說難題最難的部分是基本概念锻拘,可能很多人都會持反對意見油吭,但實際上也確實如此。我高中的時候學物理署拟,老師抓的重點就是概念--概念一定要搞清婉宰,于是難題也成了容易題。如果你能分析清楚一道物理難題存在著幾個物理過程推穷,每一個過程都遵守那一條物理定律(比如動量守恒心包、牛II定律、能量守恒)馒铃,那么就很輕松的根據定律列出這個過程的方程谴咸,N個過程必定是N個N元方程,難題也就迎刃而解骗露。即便是高中的物理競賽難題岭佳,最難之處也不過在于:

  (1)、混淆你的概念萧锉,讓你無法分析出幾個物理過程珊随,或某個物理過程遵循的那條物理定律;

  (2)、存在高次方程柿隙,列出方程也解不出叶洞。而后者已經是數學的范疇了,所以說禀崖,最難之處還在于掌握清晰的概念;

  程序設計也是如此衩辟,如果概念很清晰,那基本上沒什么難題(會難在數學上波附,比如算法的選擇艺晴、時間空間與效率的取舍昼钻、穩(wěn)定與資源的平衡上)。但是封寞,要掌握清晰的概念也沒那么容易然评。比如下面這個例子,看看你有沒有很清晰透徹的認識狈究。

//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道題还蹲,以及對這三道題引發(fā)出的你的想法爹耗,然后再聽我慢慢道來。正確的概念是:從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魄梯,這樣就不需要為聲明和調用順序而發(fā)愁了(C語言要求使用之前必須聲明桨螺,而include同名.h一般會放在.c的開頭)。有很多工程甚至把這種寫法約定為代碼規(guī)范酿秸,以規(guī)范出清晰的代碼來灭翔。

  答:1中已經回答過了。

  答:不會辣苏。問這個問題的人絕對是概念不清肝箱,要不就是想混水摸魚。非常討厭的是中國的很多考試出的都是這種爛題稀蟋,生怕別人有個清楚的概念了煌张,絕對要把考生搞暈。

  搞清楚語法和概念說易也易退客,說難也難唱矛。竅門有三點: 不要暈著頭工作,要抽空多思考思考井辜,多看看書;

  看書要看好書绎谦,問人要問強人。爛書和爛人都會給你一個錯誤的概念粥脚,誤導你;

  勤能補拙是良訓窃肠,一分辛苦一分才;

  (1)通過頭文件來調用庫功能。在很多場合刷允,源代碼不便(或不準)向用戶公布冤留,只要向用戶提供頭文件和二進制的庫即可碧囊。用戶只需要按照頭文件中的接口聲明來調用庫功能,而不必關心接口怎么實現的纤怒。編譯器會從庫中提取相應的代碼糯而。

  (2)頭文件能加強類型安全檢查。如果某個接口被實現或被使用時泊窘,其方式與頭文件中的聲明不一致熄驼,編譯器就會指出錯誤,這一簡單的規(guī)則能大大減輕程序員調試烘豹、改錯的負擔瓜贾。

  頭文件用來存放函數原型。

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

  這個問題實際上是說携悯,已知頭文件"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中指定的那個可以執(zhí)行文件中苹享。

  (非常重要)

  在VC中,一幫情況下不需要自己寫makefile浴麻,只需要將需要的文件都包括在project中得问,VC會自動幫你把makefile寫好囤攀。

  通常,編譯器會在每個.o或.obj文件中都去找一下所需要的符號宫纬,而不是只在某個文件中找或者說找到一個就不找了焚挠。因此,如果在幾個不同文件中實現了同一個函數漓骚,或者定義了同一個全局變量蝌衔,鏈接的時候就會提示"redefined"

  最后分享一些C語言的學習資料可以給大家參考

  C語言編程基礎

  http://www.makeru.com.cn/live/1758_311.html?s=45051

  提升C編程能力

  http://www.makeru.com.cn/live/1392_1166.html?s=45051

  夯實C語言,從小白到大牛的進階之路!

  http://www.makeru.com.cn/live/5413_1980.html?s=45051

  指針

  http://www.makeru.com.cn/live/1392_238.html?s=45051

  C語言實現面向對象編程

  http://www.makeru.com.cn/live/1392_1051.html?s=45051

  結構體普及與應用

  http://www.makeru.com.cn/live/5413_1909.html?s=45051

  C語言玩轉鏈表

  http://www.makeru.com.cn/live/1392_338.html?s=45051

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末认境,一起剝皮案震驚了整個濱河市胚委,隨后出現的幾起案子挟鸠,更是在濱河造成了極大的恐慌叉信,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件艘希,死亡現場離奇詭異硼身,居然都是意外死亡,警方通過查閱死者的電腦和手機覆享,發(fā)現死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門佳遂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人撒顿,你說我怎么就攤上這事丑罪。” “怎么了凤壁?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵吩屹,是天一觀的道長。 經常有香客問我拧抖,道長煤搜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任唧席,我火速辦了婚禮擦盾,結果婚禮上,老公的妹妹穿的比我還像新娘淌哟。我一直安慰自己迹卢,他們只是感情好,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布徒仓。 她就那樣靜靜地躺著腐碱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蓬衡。 梳的紋絲不亂的頭發(fā)上喻杈,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天彤枢,我揣著相機與錄音,去河邊找鬼筒饰。 笑死缴啡,一個胖子當著我的面吹牛,可吹牛的內容都是我干的瓷们。 我是一名探鬼主播业栅,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼谬晕!你這毒婦竟也來了碘裕?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤攒钳,失蹤者是張志新(化名)和其女友劉穎帮孔,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體不撑,經...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡文兢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了焕檬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姆坚。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖实愚,靈堂內的尸體忽然破棺而出兼呵,到底是詐尸還是另有隱情,我是刑警寧澤腊敲,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布击喂,位于F島的核電站,受9級特大地震影響兔仰,放射性物質發(fā)生泄漏茫负。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一乎赴、第九天 我趴在偏房一處隱蔽的房頂上張望忍法。 院中可真熱鬧,春花似錦榕吼、人聲如沸饿序。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽原探。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間咽弦,已是汗流浹背徒蟆。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留型型,地道東北人段审。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像闹蒜,于是被迫代替她去往敵國和親寺枉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354