在談編譯鏈結過程之前我們需要了解一下虛擬位址空間以及程式在編譯鏈結過程時經過了什麼步驟。虛擬位址空間之前在程序空間的部落格中詳細介紹過了,詳見:
上圖就是32位系統中4g虛擬位址空間的分布情況
.text 段:指令段,存放的是指令**,在程式中,我們把區域性變數定義(區域性變數的 定義是指令而不是資料)還有一些操作指令都存放在.text 中。它的屬性是可執行可讀不可 寫。 .
data 段:資料段,裡面存放的是初始化不為 0 的靜態區域性變數和全域性變數。屬性是可讀可 寫不可執行。注意:在 data 段上面有乙個 rodata 段(rodata 段的位置在.o 檔案時去觀察它 是在.bss 段的下方),它的屬性是只可讀。所以當我們這樣定義字串時:char *p = 「hello world」; *p = 『a』;會報錯的原因就是這裡的」hello world」在 rodata 段存放,它只可讀, 不可修改。 .
bss 段:資料段,裡面存放的是未初始化或者初始化為 0 的全域性變數和靜態區域性變數。屬性 是可讀可寫不可執行,在 bss 段中的資料預設都會被修改為 0。
棧:棧中存放的是區域性變數,特性是先進後出,並且它是由系統自動開闢以及釋放。並且記憶體是連續的。
命令列引數:main 函式的引數列表。argc 引數個數 ar** 引數內容 envp 環境變數
1、預編譯:預編譯又稱為預處理,是做些**文字的替換工作。是整個編譯過程的最先做的工作。
預編譯一些基本要做的事情:
(1)刪除所有的注釋
(2)展開所有的巨集命令(#define)
(3)處理所有的預編譯指令(#if,#endif)
(4)展開所有的標頭檔案,將標頭檔案中的所以東西展開(#include)
在經過預編譯之後產生main.i檔案。
2、編譯:編譯程式所要作得工作就是通過詞法分析和語法分析,在確認所有的指令都符合語法規則之後,將其翻譯成等價的中間**表示或彙編**。
編譯時要做的一些事情:
(1)生成和彙總符號
(2)生成彙編指令
產生main.s檔案。
3、彙編:彙編實際上指把組合語言**翻譯成目標機器指令的過程。對於被翻譯系統處理的每乙個c語言源程式,都將最終經過這一處理而得到相應的目標檔案。
彙編時期要做的事情就是轉換為二進位制可重定位檔案,生成main.o檔案。
我們在linux系統下利用file命令來檢視main.o的檔案編碼格式,可以看出main.o的檔案型別是可重定位檔案(lsb relocatable)。
file main.o:file命令用來識別檔案型別,也可用來辨別一些檔案的編碼格式。
readelf 命令:一般般用於檢視elf格式的檔案資訊,常見的檔案如在linux上的可執行檔案,動態庫(*.so)或者靜態庫(*.a) 等包含elf格式的檔案。readelf -h main.o 顯示elf檔案開始的檔案頭資訊。
我們利用readelf -h命令查main.o檔案頭的資訊,可以看出檔案裡面存在program header,起始位置為0,大小為52bytes;還存在section headers表,起始位置為208,裡面一共有9張表的表頭資訊,每個表頭資訊大小為40bytes,也就是section headers表總大小為40*9bytes。
readelf -s main.o:sections 顯示節頭資訊(如果有資料的話)。從圖中我們可以看到的是section header table表中的資訊,其中虛擬位址空間的位址均為0,二進位制可重定位檔案,並不能讀入到我們最終執行該程式的虛擬位址空間,所以沒有辦法確定該段在虛擬位址空間執行時最終真正的位址。所以我們之前說的低位址不可以訪問並不衝突。
這些段中的具體存放的是什麼東西,在剛開始的虛擬位址空間中已經介紹了。不過需要注意的是,因為.bss段存放的都是初始化為0或者沒有初始化的資料(預設值也為0),也就是值都為0的資料,那麼我完全不用儲存他們的值,也知道存在.bss段的資料值都是0。既然不存,就需要.systable段了,也就是我們的符號表段來記錄符號。
以下即為我們的程式
objdump -t main.o:顯示檔案的符號表入口。#includeusing namespace std;
int data1 = 10;
int data2 = 0;
int data3;
static int data4 = 20;
static int data5 = 0;
static int data6;
short a = 10;
short b = 20;
void fun();
int main()
利用objdump命令來檢視檔案的符號表, 在符號表中,有每個資料的資訊,可以看到data1,data4,data7都在.data段,data2,段,.data2,data5,data6,data8,data9都在.bss段,data3有*com*標記。由此我們可以看出,所有的資料會生成符號,儲存在符號表中,這樣的話就算.bss段沒有在檔案中佔據真正的記憶體,我們也可以通過符號表得知.bss段中資料的存在,而且他們的值都為0。我們可以看到data3比較特殊,它只給了乙個*com*標記,這就是強符號與弱符號的原因了。
強符號和弱符號:
在c語言中有強符號和弱符號的概念。
弱符號:沒有初始化的全域性非靜態變數。
強符號:有初始化的全域性非靜態變數。
乙個檔案中的弱符號有可能在鏈結時候會被其他檔案的同名強符號替代,所以在編譯過程中並不給他分配實際的段,只有在鏈結完成後發現他沒有被替代才會真正給他分配。
編譯鏈結過程(一)
什麼是編譯?什麼是鏈結?為什麼需要編譯和鏈結?在很久以前,計算機發展的初期,還在用機器語言編寫程式,量比較少時是不需要編譯和鏈結的。因為當時的程式設計師直接編寫機器碼讓計算機執行。每種cpu的指令是不相同的,所以每乙個程式要換一台不同cpu的機器上執行時,需要重新寫程式,而且機器語言 涉及很多計算機...
編譯鏈結過程(二)
前一篇博文提到編譯的幾個步驟,這一篇來了解下具體每一步都幹了些什麼,好叫心裡有數。詳細的過程,我想只有通過分析乙個具體的編譯器 才好。下面介紹的幾個步驟完成了原始碼檔案經過編譯鏈結後成為可執行檔案 預編譯後的檔案,不再包含注釋,標頭檔案也插入進來,條件編譯也得到相應的處理。那麼,剩下的就是實實在在的...
程式編譯,鏈結過程
c語言的編譯鏈結過程要把我們編寫的乙個c程式 源 轉換成可以在硬體上執行的程式 可執行 需要進行編譯和鏈結。編譯就是把文字形式源 翻譯為機器語言形式的目標檔案的過程。鏈結是把目標檔案 作業系統的啟動 和用到的庫檔案進行組織形成最終生成可載入 可執行 的過程。過程 如下 預處理器 將.c 檔案轉化成 ...