1. 程式人生 > >C和C++記憶體模型 C和C++記憶體模型

C和C++記憶體模型 C和C++記憶體模型

轉載自:https://www.cnblogs.com/Stultz-Lee/p/6751522.html

C分為四個區:堆,棧,靜態全域性變數區,常量區

C++記憶體分為5個區域(堆疊全常代 ):

  1. 堆 heap :
    由new分配的記憶體塊,其釋放編譯器不去管,由我們程式自己控制(一個new對應一個delete)。如果程式設計師沒有釋放掉,在程式結束時OS會自動回收。涉及的問題:“緩衝區溢位”、“記憶體洩露”

  2. 棧 stack :
    是那些編譯器在需要時分配,在不需要時自動清除的儲存區。存放區域性變數、函式引數。
    存放在棧中的資料只在當前函式及下一層函式中有效,一旦函式返回了,這些資料也就自動釋放了。

  3. 全域性/靜態儲存區 (.bss段和.data段) :
    全域性和靜態變數被分配到同一塊記憶體中。在C語言中,未初始化的放在.bss段中,初始化的放在.data段中;在C++裡則不區分了。

  4. 常量儲存區 (.rodata段) :
    存放常量,不允許修改(通過非正當手段也可以修改)

  5. 程式碼區 (.text段) :
    存放程式碼(如函式),不允許修改(類似常量儲存區),但可以執行(不同於常量儲存區)

根據c/c++物件生命週期不同,c/c++的記憶體模型有三種不同的記憶體區域,即

  1. 自由儲存區,動態區、靜態區。
  2. 自由儲存區:區域性非靜態變數的儲存區域,即平常所說的棧
  3. 動態區: 用operator new ,malloc分配的記憶體,即平常所說的堆
  4. 靜態區:全域性變數 靜態變數 字串常量存在位置

而程式碼雖然佔記憶體,但不屬於c/c++記憶體模型的一部分

在linux系統中,程式在記憶體中的分佈如下所示:

低地址
.text---> .data --->.bss

--->heap(堆) --> unused <-- stack(棧)

-->env
高地址

其中 :

  • .text 部分是編譯後程序的主體,也就是程式的機器指令。

  • .data 和 .bss 儲存了程式的全域性變數,.data儲存有初始化的全域性變數,.bss儲存只有宣告沒有初始化的全域性變數。

  • heap(堆)中儲存程式中動態分配的記憶體,比如C的malloc申請的記憶體,或者C++中new申請的記憶體。堆向高地址方向增長。

  • stack(棧)用來進行函式呼叫,儲存函式引數,臨時變數,返回地址等。

BSS 是“Block Started by Symbol”的縮寫,意為“以符號開始的塊”。BSS是Unix連結器產生的未初始化資料段。其他的段分別是包含程式程式碼的 “text”段和包含已初始化資料的“data”段。BSS段的變數只有名稱和大小卻沒有值。此名後來被許多檔案格式使用,包括PE。“以符號開始的塊” 指的是編譯器處理未初始化資料的地方。BSS節不包含任何資料,只是簡單的維護開始和結束的地址,以便記憶體區能在執行時被有效地清零。BSS節在應用程式 的二進位制映象檔案中並不存在。

在採用段式記憶體管理的架構中(比如intel的80x86系統),bss段(Block Started by Symbol segment)通常是指用來存放程式中未初始化的全域性變數的一塊記憶體區域,一般在初始化時bss 段部分將會清零。bss段屬於靜態記憶體分配,即程式一開始就將其清零了。

  比如,在C語言之類的程式編譯完成之後,已初始化的全域性變數儲存在.data 段中,未初始化的全域性變數儲存在.bss 段中。

  text和data段都在可執行檔案中(在嵌入式系統裡一般是固化在映象檔案中),由系 統從可執行檔案中載入;而bss段不在可執行檔案中,由系統初始化。

各個段的關係

一個正在執行著的C編譯程式佔用的記憶體分為程式碼區、初始化資料區、未初始化資料區、堆區 和棧區5個部分。

(1)程式碼區(text segment)。程式碼區指令根據程式設計流程依次執行,對於順序指令,則只會執行一次(每個程序),如果反覆,則需要使用跳轉指令,如果進行遞迴,則需 要藉助棧來實現。

程式碼區的指令中包括操作碼和要操作的物件(或物件地址引用)。如果是立即數(即具體的數值,如5),將直接包含在程式碼中;如果是區域性資料,將在棧區 分配空間,然後引用該資料地址;如果是BSS區和資料區,在程式碼中同樣將引用該資料地址。

(2)全域性初始化資料區/靜態資料區(Data Segment)。只初始化一次。

(3)未初始化資料區(BSS)。在執行時改變其值。

(4)棧區(stack)。由編譯器自動分配釋放,存放函式的引數值、區域性變數的值等。其操作方式類似於資料結構中的棧。每當一個函式被呼叫,該函 數返回地址和一些關於呼叫的資訊,比如某些暫存器的內容,被儲存到棧區。然後這個被呼叫的函式再為它的自動變數和臨時變數在棧區上分配空間,這就是C實現 函式遞迴呼叫的方法。每執行一次遞迴函式呼叫,一個新的棧框架就會被使用,這樣這個新例項棧裡的變數就不會和該函式的另一個例項棧裡面的變數混淆。

(5)堆區(heap)。用於動態記憶體分配。堆在記憶體中位於bss區和棧區之間。一般由程式設計師分配和釋放,若程式設計師不釋放,程式結束時有可能由OS 回收。

之所以分成這麼多個區域,主要基於以下考慮:

一個程序在執行過程中,程式碼是根據流程依次執行的,只需要訪問一次,當然跳轉和遞迴有可能使程式碼執行多次,而資料一般都需要訪問多次,因此單獨開闢 空間以方便訪問和節約空間。
臨時資料及需要再次使用的程式碼在執行時放入棧區中,生命週期短。
全域性資料和靜態資料有可能在整個程式執行過程中都需要訪問,因此單獨儲存管理。
堆區由使用者自由分配,以便管理。

下面通過一段簡單的程式碼來檢視C程式執行時的記憶體分配情況。相關資料在執行時的位置如註釋所述。

//main.cpp
int a = 0;        //a在全域性已初始化資料區
char *p1;        //p1在BSS區(未初始化全域性變數)
main()
{
int b;        //b在棧區
char s[] = "abc";  //s為陣列變數,儲存在棧區,
//"abc"為字串常量,儲存在已初始化資料區
char *p1,*p2;    //p1、p2在棧區
char *p3 = "123456";  //123456\0在已初始化資料區,p3在棧區
static int c =0;    //C為全域性(靜態)資料,存在於已初始化資料區
//另外,靜態資料會自動初始化
p1 = (char *)malloc(10);//分配得來的10個位元組的區域在堆區
p2 = (char *)malloc(20);//分配得來的20個位元組的區域在堆區
free(p1);
free(p2);
}