一個程式在執行main函式之前都幹了些什麼?
《一 》怎麼執行程式(如何把程式載入到記憶體上
首先記憶體需要的是資料和指令(機器語言)但是程式是高階語言,
1:先通過編譯連結生成.exe檔案(.exe檔案在磁碟中儲存,且.exe檔案中是機器語言)
2:.exe檔案通過mmap函式對映到虛擬記憶體上
3:再通過分段分頁機制把需要的指令和資料載入到記憶體
4:把main函式的入口地址寫入到下一行指令暫存器中
《二》編譯連結的過程
預處理:
將所有的”#define”刪除,並且展開所有的巨集定義.
處理所有的條件編譯指令,比如:”#if”
處理”#include”預編譯指令,將包含的檔案插入到該預編譯指令的位置(拷貝一份),該過程是遞迴的可能會重複包含.
刪除所有的註釋
新增行號和檔名標識
保留所有的#pragma編譯器指令
編譯:生成.s檔案(彙編程式碼檔案)
詞法分析 語法分析 語義分析 程式碼優化
彙編:生成.o檔案,它是不可執行的.
將彙編程式碼轉換成機器可以執行的 指令.
連結:檔案格式 linux(elf)windows(pf)
合併段和符號表:(連結錯誤一般都發生在符號表中,它只關心全域性符號)相同的段進行合併
符號分析:在符號引用的地方找到符號定義的地方,處理外部引用的符號(進行替換) (把虛擬(*UND*)的資料和地址變為真實的資料和地址)
分配地址記憶體空間
符號的重定位(在指令段中發生)
《三》虛擬地址空間大小
大小:4G(32位作業系統) 使用者空間3G 核心空間1G
堆和棧的區別:
堆: 1:手動開闢手動釋放 2:開闢的空間不連續 3:從低地址向高地址延伸 4:剩餘的大小就是堆的大小
棧: 1:系統開闢系統釋放 2:開闢的空間是連續的 3:從高地址向低地址延伸 4:大小不到1M (用遞迴的方法驗證,如下圖)
#include<stdio.h> int i=1; void fun() { char arr[1024]; //1k printf("%d ",i); i++; fun(); } void main() { fun(); }
ELF 檔案一般包含 一下幾個程式碼段 :在linux中Readelf -h main.o 檢視(elfheader)
file header:欄位裡存放了描述整個檔案的基本屬性資訊的內容,如程式入口地址,其他各段資訊(偏移量和範圍)
rodata欄位 :是存放只讀資料
common : 存放註釋的
data段:存放已初始化且初始化不為0的全域性變數的一塊記憶體區域。資料段屬於靜態記憶體分配。(初始化後的非const的全域性變數變數或者區域性static變數。)
bss段:存放未初始化的全域性變數的一塊記憶體區域且初始化為0的資料, BSS段屬於靜態記憶體分配(未初始化後的非const全域性變數和區域性static變數)
.text段:通常是指用來存放程式執行程式碼的一塊記憶體區域。這部分割槽域的大小在程式執行前就已經確定,並且記憶體區域通常屬於只讀, 某些架構也允許程式碼段為可寫,即允許修改程式。在程式碼段中,也有可能包含一些只讀的常數變數,例如字串常量等。(主要是編譯後的原始碼指令,是隻讀欄位。)
擴充套件 : 全域性的未初始化變數存在於.bss段中,具體體現為一個佔位符;全域性的已初始化變數存於.data段中;而函式內的自動變數都在棧上分配空間。.bss是不佔用.exe檔案空間的,其內容由作業系統初始化(清零);而.data卻需要佔用,其內容由程式初始化,因此造成了上述情況。
.bss段和.data段的區別:
bss段(未手動初始化的資料)並不給該段的資料分配空間,只是記錄資料所需空間的大小。
data(已手動初始化的資料)段則為資料分配空間,資料儲存在目標檔案中。 資料段包含經過初始化的全域性變數以及它們的值。BSS段的大小從可執行檔案中得到 ,然後連結器得到這個大小的記憶體塊,緊跟在資料段後面。當這個記憶體區進入程式的地址空間後全部清零。包含資料段和BSS段的整個區段此時通常稱為資料區。
區域性變數都是指令嗎? 區域性變數在什麼時候開闢空間?
檔案中不存在.bss段 從elfheader中的sectiion headers中提取資訊
在用的時候開闢空間 指令都存在在text段.用的時候開闢空間
Bss段到底節省了什麼空間 ?
BSS段節省了檔案空間
全域性變數分為強符號和弱符號?
強符號:已初始化的符號
弱符號:未初始化的符號
兩個強符號編譯報錯(重定義) 一強一弱取強符號的地址(弱符號暫時放在com塊中,連線後在.bss段) 兩個弱符號(和編譯器有關 1,報錯 2,就近原則)
外部引用的變數都放在哪裡?
外部引用的變數在編譯是放在*UND*中(因為編譯是檔案單獨編譯 此檔案中並不知道外部變數的地址所以暫時放在*UND*中)連線時會找到它的地址
外部引用的函式?