一個典型的C程序存儲分區包含以下幾類:
- Text段
- 已初始化數據段
- 未初始化數據段
- 棧
- 堆
1. Text段
Text段通常也稱為代碼段,由可執行指令構成,是程序在目標文件或內存中的一部分,Text段通常放在棧或堆的下面,以防止堆棧溢出篡改其數據。
通常情況下,Text段是可共享的,對於需要頻繁調用的程序,其在內存中只需要一份拷貝即可,如文本編輯器、C編譯器、Shell等,因此text段通常設為只讀以防止程序的突發性的修改。
2. 已初始化數據段
已初始化數據段,通常簡單稱作數據段,數據段占據程序虛擬地址空間的一部分,內部包括全局變量、靜態變量(程序負責初始化這些變量)。需注意的是,數據段不是只讀的,在運行時變量值是可以變動的。
數據段還可以更細的分為初始化只讀區以及初始化可讀寫區。
舉例:全局字符串 char s[] = “hello world”,全局變量 int debug=1,靜態變量 static int i = 10 存儲在初始化可讀寫區;另一種情況下,const char* string = “hello world”,字符串“hello world”存儲在初始化只讀區,string指針則存在初始化可讀寫區。
3. 未初始化數據段
未初始化數據段,通常稱作“bss”段,名字來源於古老的匯編操作符命名 “block started by symbol”,段內的數據在程序開始執行之前被內核初始化為0值,通常開始於已初始化數據段的末尾處。段內包含初始化為0的全局變量/靜態變量以及源碼中未顯示進行初始化的變量。
舉例:變量 static int i; 全局變量 int j; 包含在BBS段中。
4. 棧
棧與堆是相互毗鄰的,並且生長方向相反;當棧指針觸及到堆指針位置,意味着棧空間已經被耗盡(如今地址空間越來越大,及虛擬內存技術發展,棧與堆可能放置在內存的任何地方,但生長方向依然還是相向的)。
棧區域包含一個LIFO結構的程序棧,其通常放置在內存的高地址處,在x86架構中,棧朝地址0方向生長,在其它架構也可能朝着相反的方向生長。棧指針寄存器跟蹤棧頂位置,每當有數值被壓入棧中,棧頂指針會被調整,在一個函數的調用過程中,壓入的一系列數值被稱作“棧幀”,棧幀至少包含一個返回地址。
棧存儲着自動變量以及每次函數調用時保存的信息,每當函數被調用時,返回地址以及調用者的上下文環境例如一些機器寄存器都存儲在棧中,新的被調用函數此時會在棧上重新分配自動/臨時變量,這就是遞歸函數的工作原理。每當函數遞歸調用自己時,都會使用新的棧幀,因此當前函數實體內的棧幀內的變量不會影響另外一個函數實體內的變量。
5. 堆
堆通常用作動態內存分配,堆空間起始於BSS段的末尾,並向高地址處生長,堆空間通常由malloc, realloc 及 free管理,這些接口可能再使用brk/sbrk系統調用來調整大小,在一個進程中,堆空間被進程內所有的共享庫及動態加載模塊所共享。
示例-查看可執行文件的存儲分配
注:size命令以字節為單位統計可執行程序的text, data, 及bss段(更多詳情參考man page of size(1))
1. 檢查下述C程序
編譯后查看text/data/bss分布情況
2. 增加一個全局變量
編譯后觀察變化,bss區域增加了4字節
3. 再添加一個靜態變量
編譯后再看變化,bss區域增加了4字節
4. 初始化3中的靜態變量看看
此時變量存儲到了data段,data增加了4字節,bss減少了4字節
5.繼續初始化2中的全局變量看看
此時靜態變量也存儲到了data段,data增加了4字節,bss減少了4字節
6. 添加一個全局const變量看看
此時“haha”字符串存儲到了text段,並增加了5個字符,多出來的一個字符是\0,ptr指針存儲到了data段,增加了4個字節。
注:在步驟4/5中,初始化的值若為0,編譯器還是會將變量放入BSS區。感興趣的同學可以動手編譯看看。
文章翻譯/修改自 https://www.geeksforgeeks.org/memory-layout-of-c-program/