標籤:記憶體管理 c
C程式的記憶體管理
熟悉Java語言的肯定知道,Java中記憶體管理是由虛擬機器協助我們完成的,在C/C++中可不是這樣,程式員需要自己去分配和回收記憶體空間。本文記錄了C程式在記憶體中儲存結構、C變數和函數常見的儲存類型、分配和回收記憶體等方面的內容。以下C程式所使用的編譯器版本是GCC 4.4.7。
從一個C程式說起
檔案的結構
對於以下這段Hello.c程式再熟悉不過了
#include<stdio.h>int main(void){ printf("Hello World\n"); return 0;}
下面使用gcc編譯它,然後運行可執行檔,再查看可執行檔的儲存結構
可以看出,可執行檔Hello在儲存時(沒有調入記憶體時)分為代碼區(text),資料區(data)和未初始化資料區(bss)3個部分。另外3個欄位中,dec表示十進位總和,hex表示十六進位總和,filename表示檔案名稱。各段的具體說明如下:
(1)程式碼片段(text segment):存放CPU執行的機器指令。通常代碼區是可以共用的(即另外的執行程式可以調用它)。代碼區通常是唯讀,以防止程式意外的修改它的指令。常量資料在編譯時間在代碼區分配記憶體。代碼區的指令包括作業碼和操作對象(或對象的地址引用)。如果是立即數,就直接包含在代碼中;如果是局部資料,將在運行時的棧空間中分配,然後在引用該資料的地址;如果是bss區和資料區,在代碼中同樣是引用該資料的地址。
(2)全域初始化資料區/待用資料區(initialized data segment/data segment),或者簡稱資料區段:該地區包含了在程式中明確被初始化的全域變數,已經初始化的靜態變數(包括全域靜態變數和局部靜態變數)。需要注意的是,被const聲明的變數和字串常量在程式碼片段中分配記憶體。這和組合語言中的資料區段的概念是類似的。
(3)未初始化資料區bss(Block Started By Symbol):儲存的是未初始化的全域變數和未初始化的靜態變數。bss地區的資料在程式執行前會被核心初始化為0或者null 指標(NULL),這和棧中的變數是不同的,棧中的變數(局部變數)如果沒有初始化就使用,系統會隨機分配一個值給它,這是不安全的。
上述這些都是可執行檔的儲存結構分析,其實運行時的記憶體結構和這個十分類似,只不過多了堆記憶體和棧記憶體地區,在後面會分析到。下面通過幾個例子驗證之。
還是以Hello.c程式為例
我們在Hello.c中增加了一句代碼,定義一個常量i,通過分析比較,可以發現程式碼片段text區大小增加了4個位元組(一個int類型佔4個位元組),其他地區不變,可知常量是分配在程式碼片段的。
在上述的基礎上,在添加一句,定義一個全域變數a,並給它賦值為2,觀察各地區變化
通過比較發現,只有資料區段的大小增加了4個位元組,也證明了明確被初始化的全域變數是被分配在資料區的。靜態變數也是一樣,可自行證之。
在上述的基礎上,我們在定義一個全域變數b,但是這一個不要賦值,觀察各地區變化
可以發現,這一次只有bss地區增加4個位元組,也證明了未初始化的全域變數是分配在bss地區的。未初始化的靜態變數同理,可自行證之。
進程的結構
一個程式執行的時候就表現為一個或者多個進程,其實進程核心的資料結構和上述檔案的儲存結構很相似,主要是多了堆記憶體和棧記憶體地區。主要的布局如所示
各部分說明如下:
(1)代碼區(text segment):載入的是上述可執行檔的程式碼片段,其載入到記憶體中的位置由載入器完成。
(2)全域初始化資料區/待用資料區(Data Segment):載入的是上述可執行檔的資料區段,位置位於可執行程式碼片段後面,可以是不相連的。在程式運行之初就為資料區段申請了空間,程式退出的時候釋放空間,其生命週期是整個程式的運行時期。
(3)未初始化資料區(BSS):載入的是上述可執行檔的BSS段,位置在資料區段之後,可以不相連。其生命週期和資料區段一樣。
(4)棧區(Stack):由編譯器自動分配釋放,存放函數的參數值、返回值、局部變數等。在程式運行過程中動態分配和釋放,棧區位於BSS後,是向上有限擴充的。
(5)堆區(Heap):用於動態記憶體分配。位於棧區的後面,是向下有限擴充的。一般由程式員進行分配和釋放,若不釋放,在程式結束的時候,由OS負責回收。
(未完待續)
C程式記憶體管理