1. 程式人生 > >C/C++中的內存分區

C/C++中的內存分區

strong splay 讀寫 鏈接 中斷處理程序 內存大小 bsp isp 特權

五大內存分區

在C++中,內存分成5個區,它們分別是:棧、堆、自由存儲區、全局/靜態存儲區和常量存儲區。

棧:由編譯器自動分配和釋放,存放函數的參數值、局部變量的值等。操作方式類似於數據結構中的棧。

堆:堆由程序員手動分配和釋放,且完全不同於數據結構中的堆,分配方式類似鏈表。由new/delete 申請和釋放。若程序員忘記釋放則由系統於程序結束時回收。

自由存儲區:是由malloc等分配的內存塊,和堆十分相似,不過是用free來釋放。

全局/靜態存儲區:存放全局變量和靜態變量。在C中,全局變量又分為初始化(DATA段)的和未初始化(BSS段)的,初始化的全局變量和靜態變量在一塊區域,未初始化的全局變量和靜態變量在相鄰的另一塊區域;C++中沒有這一區分。

常量存儲區:這是一塊特殊存儲區,裏面存放常量,不允許修改。

BSS段、DATA段、CODE段

BSS段(bss segment):通常是用來存放程序中未初始化的全局變量的一塊內存區域。BSS是英文Block Startd by Symbol的簡稱。BSS段屬於靜態內存分配。

DATA段(data segment):通常是用來存放程序中已初始化的全局變量的一塊內存區域。數據段屬於靜態內存分配。

CODE段(code segment):通常是用來存放程序執行代碼的一塊內存區域,這部分區域的大小在程序運行前就已經確定,並且內存區域通常屬於只讀,某些架構也允許代碼段為可寫,即允許修改程序。在代碼段中,也有可能包含一些只讀的函數變量,例如字符串常量等。

Linux環境程序典型的內存布局如下圖所示:

技術分享圖片

代碼段(Text Segment):用戶存放CPU執行的機器指令,為防止指令被其它程序修改,代碼段一般只讀不可更改。比如,源碼中的字符串常量存儲於代碼段,不可修改。

初始化數據段(Data Segment):又稱為DATA段,用於存儲初始化的全局變量和Static變量,段大小在編譯時確定,所以內存的分配屬於靜態內存分配。

未初始化數據段(BSS Segment,Block Started by Symbol):又稱為BSS段,通常用來存放程序中未初始化的全局變量和Static,雖未顯示初始化,但在程序載入內存執行時,由內核清0,所以未顯示初始化則默認為0。BSS段的大小也是在編譯時確定,運行時內存的分配屬於靜態內存分配。

堆(Heap):用於保存程序運行時動態申請的內存空間,由開發人員手動申請,手動釋放,若不手動釋放,程序結束後由系統回收,生命周期是整個程序運行期間,比如使用malloc()或new申請的內存空間。堆的地址空間“向上增加”,即當堆上保存的數據越多,堆的地址就越高。堆的內存分配屬於動態分配,一般運行時才知道分配的內存大小,並且堆可分配存活於函數之外的內存,在未顯示調用free()或delete釋放時,其生命周期為進程的生命周期。

映射段(Memory Mapping Segment):該區域內核將文件內容直接映射到內存。任何應用程序都可以請求該區域。Linux中通過mmap()系統調用,Windows中通過creatFileMapping()/MapViewOfFile()創建。文件I/O時內存映射方便並且高效,所以,它常用來加載動態庫,還可以創建一種匿名映射,並不對應於文件,而用於程序數據。在Linux中,如果使用malloc()申請一塊過大的內存,C庫函數便會創建這種內存映射段,而不是使用堆內存。“過大”的內存指超過M_MMAP_THRESHOLD字節,默認128KB,可以通過mallopt()函數調整。映射段也屬於動態分配。

棧(Stack):用於保存函數的局部變量(但不包括static聲明的靜態變量,靜態變量存放在數據段或BSS段)、參數、返回值、函數返回地址以及調用者環境信息(比如寄存器值)等信息,由系統進行內存的管理,在函數完成執行後,系統自行釋放棧區內存,不需要用戶管理。整個程序的棧區的大小可以由用戶自行設定,Windows默認的棧區大小為1M,可通過Visual Studio更改編譯參數手動更改棧的大小。64bits的Linux默認棧大小為10MB,可通過命令ulimit -s臨時修改。棧是一種“後進先出”(Last In First Out,LIFO)的數據結構,這意味著最後入棧的數據,將會是第一個出棧的數據。對於那些暫時存貯無需長期保存的信息來說,LIFO這種數據結構非常理想。在調用函數後,系統通常會清除棧上保存的信息。棧另外一個重要的特征是,它的地址空間“向下減少”,即當棧上保存的數據越多,棧的地址就越低。

內核空間(Kernel Space),:用於存儲操作系統和驅動程序,用戶空間用於存儲用戶的應用程序,二者不能簡單地使用指針傳遞數據。當一個進程執行系統調用而陷入內核空間執行內核代碼時,我們稱進程處於內核運行態(或簡稱為內核態)。此時處理器處於特權級最高的(0級)內核代碼中執行。當進程處於內核態時,執行的內核代碼會使用當前進程的內核棧。每個進程都有自己的內核棧。當進程在執行用戶自己的代碼時,則稱其處於用戶運行態(用戶態),即此時處理器在執行最低特權級(3級)用戶代碼中。當正在執行用戶程序而突然被中斷程序中斷時,此時用戶程序也可以象征性地稱為處於進程的內核態。因為中斷處理程序將使用當前進程的內核棧。這與處於內核態的進程的狀態有些類似。

內存段的特點和區別如下。
|段名|存儲內容 |分配方式|生長方向|讀寫特點|運行態|
|—|---|—|---|—|
|代碼段|程序指令、字符串常量、虛函數表|靜態分配|由低到高|只讀|用戶態|
|數據段|初始化的全局變量和靜態變量|靜態分配|由低到高|可讀可寫|用戶態|
|BSS段|未初始化的全局變量和靜態變量|靜態分配|由低到高|可讀可寫|用戶態|
|堆|動態申請的數據|動態分配|由低到高|可讀可寫|用戶態|
|映射段|動態鏈接庫、共享文件、匿名映射對象|動態分配|由低到高|可讀可寫|用戶態|
|棧|局部變量、函數參數與返回值、函數返回地址、調用者環境信息|靜態分配|由高到低|可讀可寫|用戶態|
|內核空間|儲操作系統、驅動程序|動態+靜態|由低到高+由高到低|不能直接訪問|內核態|

關於內核空間,其中包含內核棧和內核的數據段,所以內存地址生長方向既有由低到高(內核數據段),也有由高到低(內核棧)。關於讀寫的特點,由內核進行讀寫,用戶程序不可直接訪問。

以下面的C++代碼為例,看一下常見變量所屬的內存段。

#include <string.h>

int a = 0;                         // a在數據段,0為文字常量,在代碼段
char *p1;                          // BSS段,系統默認初始化為NULL
void main()
{
    int b;                         //
    char *p2 = "123456";          //123456在代碼段,p2在棧上
    static int c =0;              //c在數據段
    const int d=0;                 //
    static const int d;            //數據段
    p1 = (char*)malloc(10);        //分配得的10字節在堆
    strcpy(p1,"123456");         //123456放在字符串常量區,編譯器可能會將它與p2所指向的"123456"優化成一個地方
}


參考文獻

https://blog.csdn.net/K346K346/article/details/45592329

C/C++中的內存分區