編譯與標頭檔

來源:互聯網
上載者:User
C語言中的.c和.h檔案2009-06-29 09:30簡單的說其實要理解C檔案與標頭檔(即.h)有什麼不同之處,首先需要弄明白編譯器的工作過程,一般說來編譯器會做以下幾個過程:1.預先處理階段
2.詞法與文法分析階段
3.編譯階段,首先編譯成純彙編語句,再將之彙編成跟CPU相關的二進位碼,產生各個目標檔案 (.obj檔案)
4.串連階段,將各個目標檔案中的各段代碼進行絕對位址定位,產生跟特定平台相關的可執行檔,當然,最後還可以用objcopy產生純二進位碼,也就是去掉了檔案格式資訊。(產生.exe檔案)編譯器在編譯時間是以C檔案為單位進行的,也就是說如果你的項目中一個C檔案都沒有,那麼你的項目將無法編譯,連接器是以目標檔案為單位,它將一個或多個目標檔案進行函數與變數的重定位,產生最終的可執行檔,在PC上的程式開發,一般都有一個main函數,這是各個編譯器的約定,當然,你如果自己寫連接器指令碼的話,可以不用main函數作為程式入口!!!!(main .c檔案 目標檔案 可執行檔 )有了這些基礎知識,再言歸正傳,為了產生一個最終的可執行檔,就需要一些目標檔案,也就是需要C檔案,而這些C檔案中又需要一個main函數作為可執行程式的入口,那麼我們就從一個C檔案入手,假定這個C檔案內容如下:
#include <stdio.h>
#include "mytest.h"int main(int argc,char **argv)
{
test = 25;
printf("test.................%d/n",test);
}標頭檔內容如下:
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標準,就算不看人家的標頭檔,你一樣可以知道怎麼使用  關於標頭檔和源檔案的分別首先,我們可以將所有東西都放在一個.cpp檔案內.然後編譯器就將這個.cpp編譯成.obj,obj是什麼東西?就是編譯單元了.一個程式,可以由一個編譯單元組成,也可以有多個編譯單元組成. 如果你不想讓你的原始碼變得很難閱讀的話,就請使用多個編譯單元吧.(一個函數不能放到兩個編譯單元裡面,但兩個以上就可以分別放在一個單元,也就是cpp裡面)     那麼就是一個.cpp對應一個.obj,然後將所有的obj連結起來(通過一個叫連結器的程式),組成一個.exe,也就是程式了.    如果一個.cpp要用到另一個.cpp定義的函數怎麼辦? 只需在這個.cpp種寫上他的函式宣告就可以了.其餘工作由連結器幫你完成,你可以隨便調用該函數.    連結器將所有的obj串連起來,但是如果碰巧有相同的函數或外部變數怎麼辦?他如何識別?一般來說是不能允許在同一個程式中,出現兩個一樣的函數名或外部變數名.    但是只得慶幸的是,c++可以通過一種叫做連結屬性的關鍵字來限定,你這個函數是屬於整個程式公用的,還是只是在一個編譯單元obj裡面使用的.    這些關鍵字就是extern 和 static; extern是外部連結的意思,也就是除了這個單元,外部的單元也是能夠訪問這個函數的.static 是內部連結,自屬於自己單元.說了這麼久,還沒有說.h的作用呢?    其實沒有.h也能很好的工作,但是當你發現一個外部連結的函數或外部變數,需要許多份聲明,因為c++這種語言,在使用函數和變數的時候,必須將他聲明,為何要聲明?聲明之後才知道他的規格,才能更好的發現不和規格的部分.你別妄想一個編譯單元,會自動從另一個編譯單元那裡得到什麼資訊,知道你是如何定義這個函數的.    所以說,只要使用到該函數的單元,就必須寫一份聲明在那個.cpp裡面,這樣是不是很麻煩,而且,如果要修改,就必須一個一個修改.這真讓人受不了. .h就是為瞭解決這個問題而誕生,他包含了這些公用的東西.然後所有需要使用該函數的.cpp,只需要用#include包含進去便可.以後需要修改,也只是修改一份內容.請注意不要濫用.h,.h裡面不要寫代碼,.h不是.cpp的倉庫,什麼都塞到裡面.如果在裡面寫代碼,當其他.cpp包含他的時候,就會出現重複定義的情況,比如將函數func(){printf};放到標頭檔a.h,裡面還有一些a.cpp需要的聲明等;然後你發現b.cpp需要用到a.cpp裡面的一個函數,就很高興的將a.h包含進來.注意,#include並不是什麼申請指令,他就是將指定的檔案的內容,原封不動的拷貝進來.這時候實際上a.cpp和b.cpp都有一個func()函數的定義.如果這個函數是內部連結static的話,還好,浪費了一倍空間;如果是extern,外部連結(這個是預設情況),那麼根據在同一個程式內不可出現同名函數的要求,連接器會毫不留情給你一個串連錯誤!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.