編譯連結原理
虛擬地址空間
32位計算機,每個程式都有4G的虛擬地址空間。首先虛擬地址空間分為兩大塊,一個是使用者空間,一個是核心空間。使用者空間佔3G的大小,並且它是每個程序所獨有的,它的開頭128M存放的是我們無法訪問的地方。
- .text段:它也叫指令段,顧名思義,它存放的是指令程式碼,在程式中,我們把區域性變數定義(區域性變數的定義是指令而不是資料)還有一些操作指令都存放在.text中。它的屬性是可執行可讀不可寫。
- .data段:資料段,裡面存放的是初始化不為0的靜態區域性變數和全域性變數。屬性是可讀可寫不可執行。注意:在data段上面有一個rodata段(rodata段的位置在.o檔案時去觀察它是在.bss段的下方),它的屬性是隻可讀。所以當我們這樣定義字串時:char *p = “hello world”; *p = ‘a’;會報錯的原因就是這裡的”hello world”在rodata段存放,它只可讀,不可修改。
- .bss段:資料段,裡面存放的是未初始化或者初始化為0的全域性變數和靜態區域性變數。屬性是可讀可寫不可執行,在bss段中的資料預設都會被修改為0。
- 堆:堆記憶體是我們在c語言中用malloc申請或者在c++中用new來申請的一端可能不連續的記憶體,堆是由低地址向高地址申請的。
- 棧:棧中存放我們的區域性變數,特性是先進後出,並且它是由系統自動開闢以及釋放。並且記憶體是連續的。
- 命令列引數:main函式的引數列表。argc 引數個數 argv 引數內容 envp 環境變數
- 環境變數:即envp的值
- 最下邊是核心空間
編譯(生成二進位制可重定位檔案)
1、預編譯(生成 .i 檔案)
操作命令:gcc -E main.c -o main.i 具體內容: (1)巨集定義指令 (2)條件編譯 (3)標頭檔案包含指令 (4)特殊符號處理 常見問題: 不能在標頭檔案中定義全域性變數,因為在標頭檔案中定義全域性變數將會使所有包含該標頭檔案的檔案存在該段程式碼,也就是說這些檔案將定義一個相同的全域性變數,這樣將不可避免的造成衝突
2、編譯(生成 .s 檔案,即彙編檔案) 操作命令:gcc -S main.i -o main.s 具體內容:編譯環節是指對原始碼進行語法分析,並優化產生對應的彙編程式碼的過程 (1) 生成符號 (2) 生成彙編指令
3、彙編(生成 .o 檔案,即二進位制可重定位檔案) 操作命令:gcc -c main.s -o main.o 具體內容:將組合語言翻譯成機器語言(二進位制)程式碼
注意:
- .bss段在二進位制可重定位檔案中不佔記憶體,達到了節省空間的目的(問題:資料如何標識?)。
因為.bss段中的資料都是沒有初始化或初始化為0的資料,所以每個數的值都為0,沒必要每個都儲存,只需要標識其存在即可。每個資料都會生符號,.bss段的大小隻是說明有多少個數據(弱符號不計入其中),並不實際儲存,而實際的資料產生的符號在符號表中。這樣就解決了資料的標識問題。
- 強符號和弱符號
強符號:初始化了的非靜態資料
弱符號:沒有初始化的非靜態資料
弱符號不往資料段儲存(指的是不參與.bss段對資料個數的計算),只會產生一個/*COM*標誌
連結(生成可執行程式)
操作命令:gcc -o main main.s
具體內容:將有關的目標檔案彼此連結,也即將在一個檔案中引用的符號同該符號在另外一個檔案中的定義連結起來,是的所有的這些檔案成為一個能夠讓作業系統裝入執行的統一整體,可分為動態連結和靜態連結連結有以下幾個部分要做: 1、 合併段表:將多個二進位制可重定位檔案中的段資訊整合放到一個檔案中 2、 調整段偏移:段表經過合併之後大小發生了變化,所以地址需要適當的偏移 3、 合併符號表:將多個二進位制可重定位檔案中的符號整合到一個檔案中 4、 完成符號的重定位:聯結器把每個符號定義與一個虛擬地址聯絡起來,然後修改所有對這些符號的引用,使得它們指向這個儲存位置,從而重定位這些節。
執行:
按照不同段的屬性相同的不同的段劃分到同一頁裝入虛擬地址空間
附:
32位虛擬地址空間是多大?為啥? 4G,32位指的是地址匯流排的條數 32條,2^32 = 4G