c語言的編譯鏈結過程要把我們編寫的乙個c程式(源**)轉換成可以在硬體上執行的程式(可執行**),需要進行編譯和鏈結。編譯就是把文字形式源**翻譯為機器語言形式的目標檔案的過程。鏈結是把目標檔案、作業系統的啟動**和用到的庫檔案進行組織形成最終生成可載入、可執行**的過程。過程**如下:
預處理器:將.c 檔案轉化成 .i檔案,使用的gcc命令是:gcc –e,對應於預處理命令cpp;
編譯器:將.c/.h檔案轉換成.s檔案,使用的gcc命令是:gcc –s,對應於編譯命令 cc –s;
彙編器:將.s 檔案轉化成 .o檔案,使用的gcc 命令是:gcc –c,對應於彙編命令是 as;
鏈結器:將.o檔案轉化成可執行程式,使用的gcc 命令是: gcc,對應於鏈結命令是 ld;
載入器:將可執行程式載入到記憶體並進行執行,loader和ld-linux.so。
編譯過程又可以分成兩個階段:編譯和彙編。
編譯是指編譯器讀取源程式(字元流),對之進行詞法和語法的分析,將高階語言指令轉換為功能等效的
彙編**。
原始檔的編譯過程包含兩個主要階段:
第乙個階段是預處理階段,在正式的編譯階段之前進行。預處理階段將根據已放置在檔案中的預處理指令來修改原始檔的內容。
主要是以下幾方面的處理:
巨集定義指令,如 #define a b 對於這種偽指令,預編譯所要做的是將程式中的所有a用b替換,但作為字串常量的 a則不被替換。還有 #undef,則將取消對某個巨集的定義,使以後該串的出現不再被替換。
條件編譯指令,如#ifdef,#ifndef,#else,#elif,#endif等。 這些偽指令的引入使得程式設計師可以通過定義不同的巨集來決定編譯程式對哪些**進行處理。預編譯程式將根據有關的檔案,將那些不必要的**過濾掉
標頭檔案包含指令,如#include "filename"或者#include 等。 該指令將標頭檔案中的定義統統都加入到它所產生的輸出檔案中,以供編譯程式對之進行處理。
特殊符號,預編譯程式可以識別一些特殊的符號。 例如在源程式中出現的line標識將被解釋為當前行號(十進位制數),file則被解釋為當前被編譯的c源程式的名稱。預編譯程式對於在源程式中出現的這些串將用合適的值進行替換。
標頭檔案的目的主要是為了使某些定義可以供多個不同的c源程式使用,這涉及到頭檔案的定位即搜尋路徑問題。標頭檔案搜尋規則如下:
所有header file的搜尋會從-i開始
然後找環境變數 c_include_path,cplus_include_path,objc_include_path指定的路徑
再找預設目錄(/usr/include、/usr/local/include、/usr/lib/gcc-lib/i386-linux/2.95.2/include......)
第二個階段編譯、優化階段,編譯程式所要作得工作就是通過詞法分析和語法分析,在確認所有的指令都符合語法規則之後,將其翻譯成等價的中間**表示或彙編**。
彙編實際上指彙編器(as)把組合語言**翻譯成目標機器指令的過程
。目標檔案中所存放的也就是與源程式等效的目標的機器語言**。目標檔案由段組成。通常乙個目標檔案中至少有兩個段:
可重定位(relocatable)檔案:由編譯器和彙編器生成,可以與其他可重定位目標檔案合併建立乙個可執行或共享的目標檔案;
共享(shared)目標檔案:一類特殊的可重定位目標檔案,可以在鏈結(靜態共享庫)時加入目標檔案或載入時或執行時(動態共享庫)被動態的載入到記憶體並執行;
可執行(executable)檔案:由鏈結器生成,可以直接通重載入器載入到記憶體中充當程序執行的檔案。
靜態庫(static library)就是將相關的目標模組打包形成的單獨的檔案。使用ar命令。
靜態庫的優點在於:
動態庫(dynamic library)是一種特殊的目標模組,它可以在執行時被載入到任意的記憶體位址,或者是與任意的程式進行鏈結。
動態庫的優點在於:
鏈結器主要是將有關的目標檔案彼此相連線生成可載入、可執行的目標檔案。鏈結器的核心工作就是符號表解析和重定位。
gcc先從-l尋找;
再找環境變數library_path指定的搜尋路徑;
再找內定目錄 /lib /usr/lib /usr/local/lib 這是當初compile gcc時寫在程式內的。
編譯目標**時指定的動態庫搜尋路徑-l;
環境變數ld_library_path指定的動態庫搜尋路徑;
配置檔案/etc/ld.so.conf中指定的動態庫搜尋路徑;
預設的動態庫搜尋路徑/lib /usr/lib/ /usr/local/lib
鏈結器將函式的**從其所在地(目標檔案或靜態鏈結庫中)拷貝到最終的可執行程式中。這樣該程式在被執行時這些**將被裝入到該程序的虛擬位址空間中。靜態鏈結庫實際上是乙個目標檔案的集合,其中的每個檔案含有庫中的乙個或者一組相關函式的**。
為建立可執行檔案,鏈結器必須要完成的主要任務:
符號解析:把目標檔案中符號的定義和引用聯絡起來;
重定位:把符號定義和記憶體位址對應起來,然後修改所有對符號的引用。
關於符號表和符號解析以及重定位的分析後續學習。
在此種方式下,函式的定義在動態鏈結庫或共享物件的目標檔案中。在編譯的鏈結階段,動態鏈結庫只提供符號表和其他少量資訊用於保證所有符號引用都有定義,保證編譯順利通過。動態鏈結器(ld-linux.so)鏈結程式在執行過程中根據記錄的共享物件的符號定義來動態載入共享庫,然後完成重定位。在此可執行檔案被執行時,動態鏈結庫的全部內容將被對映到執行時相應程序的虛位址空間。動態鏈結程式將根據可執行程式中記錄的資訊找到相應的函式**。
載入器把可執行檔案從外存載入到記憶體並進行執行。 linux中程序執行時的記憶體映像如下:
載入過程如下:
載入器首先建立如上圖所示的記憶體映像,然後根據段頭部表,把目標檔案拷貝到記憶體的資料和**段中。然後,載入器跳轉到程式入口點(即符號_start 的位址),執行啟動**(startup code),啟動**的呼叫順序如所示:
unix系統提供了一系列工具幫助理解和處理目標檔案。gnubinutils 包也提供了很多幫助。這些工具包括:
頂 0
踩
程式的編譯鏈結過程
一段源 到可執行性程式需要經歷預處理 編譯彙編和鏈結等步驟,接下來詳細介紹這些過程 假設檔案main.czhong有如下 include int main 1 巨集定義指令 2 條件編譯 3 標頭檔案包含指令 4 特殊符號處理 不能在標頭檔案中定義全域性變數,因為在標頭檔案中定義全域性變數將會使所有...
編譯鏈結過程
在談編譯鏈結過程之前我們需要了解一下虛擬位址空間以及程式在編譯鏈結過程時經過了什麼步驟。虛擬位址空間之前在程序空間的部落格中詳細介紹過了,詳見 上圖就是32位系統中4g虛擬位址空間的分布情況 text 段 指令段,存放的是指令 在程式中,我們把區域性變數定義 區域性變數的 定義是指令而不是資料 還有...
C程式的編譯鏈結過程
程式由原始檔編譯得到可執行檔案看起來好像是很簡單的過程,windows的ide環境下,點一下bulid就可以生成可執行檔案,在linux環境下,gcc編譯器也提供了很多選項可以很方便的從原始檔生成可執行檔案。事實上程式的編譯和鏈結是乙個非常複雜的過程,ide幫我們隱藏了大量的細節。下面我們以最經典的...