關於原始檔,標頭檔案,靜態連結庫檔案,動態連結庫檔案的的理解
先從原始檔和標頭檔案的關係說起,由於是還是初學階段,只接觸了C++語言和windows平臺下的程式設計,所以只講這兩方面的東東,
標頭檔案的作用:對函式,變數,和類的宣告,其實在標頭檔案也可對一些特殊函式和變數定義,比如可以在標頭檔案中對行內函數和const型別變數定義,由於對類的宣告也就是對類的定義,即在對類完成聲明後也就等於對類做了定義,所以標頭檔案中對類的宣告也就是對類的定義,但是對類的非行內函數成員的定義則不能放在頭件中,
原始檔的作用:原始檔可以用於實現在標頭檔案中宣告的函式原型實現,也可以在原始檔中包含一些標頭檔案。
#include命令的實現:
這個命令有兩種格試,實現的方法不同,用尖括號實現的格試預編譯系統在搜尋頭件時直接到windows
由雙引號引出的包含命令感覺會分為三步吧,第一步會在你的程式的當前目錄下尋找標頭檔案,第一步結束第二步就會到你的整合開發環境所在的目錄下去查詢標頭檔案,比如你所安裝的VS2012或VC++的包含檔案目錄,第二步結束就會到系統包含的標頭檔案中去查詢,也可以理解成到c盤下的system資料夾中的一個include資料夾中去查詢,
這是我自已現在理解的查詢方式,三步查詢對應了三做不同的目錄:1,自已的原始檔所在的目錄,2,VS或VC++安裝檔案對應的目錄,3,c:\windows\system\include,而這三種目錄對應了三種不同的標頭檔案,第一種目錄一般是程式作者自已定義的標頭檔案,比如說程式作者為了使自已的程式結構比較清淅,更於修改,把一些適合放在一個頭檔案中宣告的函式放到一個頭檔案宣告,而這個標頭檔案就是作者自已定義的頭件,第二種目錄一般對應的是
作了以上解釋下面再解釋程式的預編譯工具就比較容易了。
我把一個程式從原始碼開始到被載入到記憶體中執行為止這段時間分成了幾步,預編譯,編譯,連結,載入,執行。同樣在這個過程中程式所處的狀態也就分成了好幾種,我自已給他們起了名字如下:
純原始碼(狀態1)#預編譯#
下面對程式碼的狀態和我為什麼如此命名這幾種狀態做一下解譯:
純原始碼:就是我們用VS編寫出的程式碼,這個時候這些程式碼是以文件的形式存放的,我感覺實質上和word或者文字文件沒有什麼區別,由於未經過計算機做任何的處理僅是由程式作者寫出的,所以用純原始碼,用了一個“純”字兒,
裝備後原始碼:這是經過預編譯後生成的程式碼,感覺這種程式碼在實質上還是由ASCII碼組成,這裡說一下程式的預編譯處理對我們的程式做了什麼?我們可以把預編譯處理做的事情概括為:複製,粘帖,替換。複製我們在原始檔中由include命令引用的標頭檔案,把複製來的檔案粘帖到include命令出現的位置,把原始檔中由#define
有符號意義的二進位制程式碼:
這裡舉個例子,通過這個例子可以很好的理解標頭檔案,原始檔,以及windows的編譯器的動作。
這裡我們在標頭檔案a.h中作出了函式a()宣告,但是函式a()的實現也就是定義是在一個原始檔b.c中,而我們在另一個原始檔c.c中呼叫函式a(),下面對上面幾步做一下簡化程式設計:
//標頭檔案a.h
宣告函式a();
//原始檔b.c
#include
實現a();
//原始檔c.c
#include
呼叫a();
一共有一個頭檔案和兩個原始檔;
先說一下編譯的結果吧,由於微軟的編譯器才用的分別編譯,即對每一個原始檔做分別編譯並分別形成有一定獨立性的目際檔案(.o或者.obj),所以這裡就形成了b.o和c.o兩個目標檔案,顯然目標檔案c.o中是沒有函式a的實現的,這裡可以看出我們在原始檔c.c中包含標頭檔案的唯一作用就是做了函式a的宣告,這可以告訴編譯器c.c中的那個符號:a()是一個函式,不是別的什麼東東,這樣編譯器就就知道a()這個函式的形參等最基本的形式,生成二進位制程式碼,但是現在的問題時,我們要執行這段二進位制程式碼就要知道函式a的實現呀,這時怎麼辦呢?而函式a的實現是在檔案b.o中的,怎麼才能讓這兩個檔案頭鏈呢??這已不是編譯器所要處理的任務了,我感覺編譯器的任務最重要的並不是生成二進位制程式碼,而是幫我們檢查我們的程式在模組層面的羅緝上和語法上是不是有問題,也就是可不可以編譯通過衡量的是我們的程式是不是可以翻譯成計算機能認識的“字”,至於計算機是不是能讀懂我們的程式的意思已不是程式設計器所做的事兒了,為什麼我給這個狀態的程式碼起了一個“有符號意義的二進位制程式碼呢??看上面的例子,c.o雖然是目的碼,但是在該檔案中一定存在一種機制,能讓下一步的連結程式找到目標檔案b.o中的函式a實現的那段程式碼,這樣我們可以理解成連結器能在c.o中找到呼叫函式a的呼叫點,並且在b.o中找到函式a的程式碼,這樣看,其實目標檔案還且有“符號”意義,也就是說原始碼中的函式a()的符號在目標檔案中還有一個整體的意義。所以分別編釋後才能形成目的碼,我就叫他為”有符號意義的程式碼”
再說一下連結的步驟:
其實連結步驟完成動作也可以概括為:“程式碼複製”,和預編譯不同的是這裡的程式碼複製只是預編譯做的是原始碼的複製,而這裡做的是對二進位制程式碼的複製,比如這裡將b.o中函式a的實現程式碼複製到b.o呼叫a函的地方(這裡有點靜態連結的意思),上面函式a的複製是連結時程式碼複製的一種,我感覺連結時程式碼複製的第二種情況是程式中用到的.lib函式庫中的一些變數和函式的程式碼複製,比如你可能在檔案b中呼叫了某個.lib中的函式,這時連結器會把你呼叫的函式從.lib中複製到檔案b的呼叫點,(這裡只是說明程式碼複製的概念,複製來的程式碼不一定就一定得插入到b檔案中的呼叫點,也可能“接”到b檔案的未尾,再通過指標來實現程式指令地址的跳轉,至於把複製來的程式碼放到何處實連結器的辦法而定,但是這種連結,自我感覺,一定是通過程式碼複製來實現的)這樣通過程式碼的複製,連結程式就把分立編譯的程式模組組成了一個整體,也就是人們說的可執行檔案.exe,有人說到這裡這個程式就以運行了,為什麼我還叫他“有缺陷的可執行檔案”呢,往下看,
接下來看程式的載入:
如果你把一個.exe檔案只接放到沒有操作系充的“裸機”上去執行,顯然是執行不了的,可是你把這個程式放在一個裝有windows系統的電腦上就能運行了,顯然,程式的執行還是得依靠windows作業系統,這裡就要說到.dll檔案,上面說到的連結這一步時的程式碼複製只講到對程式作者自已寫的檔案和.lib檔案中用到的程式碼複製,並沒有提到.dll檔案中程式碼複製的情況,從.dll檔案的名字可以知道,這部分程式碼是“動態的連結”到執行著的程式的,比如說我們在檔案c中用到了.dll檔案中的函式,但是在連結完成後我們並沒有將這些函式的程式碼封到.exe檔案中去,可是這些函式又確確實實要用,這是怎麼實現的呢,這就是程式載入所要完成的工作,在連結器生成.exe檔案後,連結程式把一個“表”放到了.exe檔案中,這個表就起錄了這個.exe檔案呼叫了哪些.dll中的哪些函式,這樣,載入過程中“載入器”就可以跟據這個表去把.dll檔案中程式用的那部分程式碼和.exe檔案中的程式碼一起載入到記憶體中去,這樣就形成了一個“完美的可執行程式”,這個程式駐留在記憶體中,程式段中的指令真接有CPU取出執行,再和作業系統合作完成程式要完成的各種功能。上面把連結形成的可執行程式碼叫“有缺陷的可執行檔案”到這裡可能就解釋清楚了,因為它沒有.dll檔案中的程式碼,所以不完全,為什麼程式要依賴windows我想除了windows會對資源管理外,這也是最重的原因之一,因為windows提供了程式要完成執行必須的.dll檔案。
上面提到的.h是標頭檔案,.c是原始檔,.lib是靜態連結庫,.dll