天天看點

作業系統中堆和棧詳解一、程式的記憶體配置設定二、程式執行個體三、堆和棧的理論知識

版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/zhaobryant/article/details/42010029

一、程式的記憶體配置設定

對于一個由C/C++編譯的程式,其所占用的記憶體可以劃分為以下幾個部分:

  1. 棧區(stack)—— 由作業系統自動配置設定和釋放,主要用于存放函數參數值,局部變量等。其操作方式類似于資料結構中的棧。 
  2. 堆區(heap)—— 一般由程式員動态配置設定和釋放,若程式員不主動釋放,則程式結束後由作業系統回收。注意,它與資料結構中的堆是不同的,配置設定方式類似于連結清單。 
  3. BSS段——主要用于存放未初始化的靜态變量和全局變量,可讀寫,它在程式結束後由作業系統進行釋放。
  4. 資料段(data)——主要用于存放已初始化的靜态變量和全局變量,可讀寫,它在程式結束後由作業系統釋放。
  5. 代碼段(text)——主要用于儲存程式代碼,包括CPU執行的機器指令,同時全局常量也是儲存在代碼段的,如字元串字面值。

二、程式執行個體

/main.cpp
int a = 0;                       // 全局初始化區域
char *p1;                        // 全局未初始化區域
int main(){
    int b;                       // 棧
    char s[] = "adoryn";         // 棧
    char *p2;                    // 棧
    char *p3 = "zhaobryant";     // 字元串字面量存放在常量區,p3存放在棧上
    static int c = 0;            // 全局(靜态)初始化區域
    p1 = (char *)malloc(10);
    p2 = (char *)malloc(20);     // 配置設定獲得的10和20位元組的記憶體區放在堆區
    strcpy(p1, "zhaobryant");    // 字元串字面量存放在常量區,編譯器可能會将它與p3所指向的"zhaobryant"優化為同一個位址
    return 0;
}           

三、堆和棧的理論知識

1. 申請方式對比

棧stack:

由系統自動配置設定。例如,聲明在函數中一個局部變量,即int b,系統自動在棧中為變量b開辟空間。

堆heap:

需要程式員自己申請,并指明大小。

在C中使用malloc函數,如p1 = (char *)malloc(10)

在C++中用new運算符,如p2 = new char[10]

但是p1、p2本身是在棧中的。

2. 申請後系統響應

隻要棧的剩餘空間大于所申請空間,系統将為程式提供記憶體,否則将傳回異常提示棧溢出。

首先應該知道作業系統有一個記錄空閑記憶體塊節點的連結清單,當系統收到程式的申請時,會周遊該連結清單,尋找第一個空間大于所申請空間的記憶體塊節點,然後将該節點從空閑節點連結清單中删除,并将該節點的空間配置設定給程式。另外,對于大多數系統,會在這塊記憶體空間中的首位址處記錄本次配置設定的大小,這樣,代碼中的delete語句才能正确的釋放記憶體空間。另外,由于找到的空閑記憶體塊節點的大小不一定正好等于申請的大小,系統會自動的将多餘的那部分重新放入空閑連結清單中。

3. 申請大小的限制

在Windows下,棧是向低位址擴充的資料結構,是一塊連續的記憶體的區域。也就是說,棧頂的位址和棧的最大容量是系統預先規定好的。在Windows下,棧的大小是2M(也有的說是1M,總之是一個編譯時就确定的常數),如果申請的空間超過棧的剩餘空間時,将提示記憶體溢出。是以,能從棧獲得的空間較小。

堆是向高位址擴充的資料結構,是不連續的記憶體區域。這是由于系統是用連結清單來存儲空閑記憶體位址的,自然是不連續的,而連結清單的周遊方向是由低位址向高位址。堆的大小受限于計算機系統中有效的虛拟記憶體。由此可見,堆獲得的空間比較靈活,也比較大。

4. 申請效率對比

棧由系統自動配置設定,速度較快,但程式員是無法控制的。

堆是由new/malloc進行記憶體配置設定,一般速度比較慢,且容易産生記憶體碎片,不過用起來最友善,速度快,也最靈活。

5. 存儲内容對比

在函數調用時,第一個進棧的是主函數中後的下一條指令(函數調用語句的下一條可執行語句)的位址,然後是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,然後是函數中的局部變量。注意靜态變量是不入棧的。當本次函數調用結束後,局部變量先出棧,然後是參數,最後棧頂指針指向最開始存的地 址,也就是主函數中的下一條指令,程式由該點繼續運作。

一般是在堆的頭部用一個位元組存放堆的大小,堆中的具體内容由程式員安排。

6. 存取效率對比

對比兩段代碼:

char s1[] = "aaaaaaaaaaaaaaa";

char *s2 = "bbbbbbbbbbbbbbbbb"; 

如上,aaaaaaaaaaa是在運作時刻指派的;而bbbbbbbbbbb是在編譯時就确定的。

但是,在以後的存取中,在棧上的數組比指針所指向的字元串快。

例如:

#include ...
int main(){
    char a = 1;
    char c[] = "1234567890";
    char *p = "1234567890";
    a = c[1];
    a = p[1];
    return 0;
}           

對應的彙編代碼:

10: a = c[1];
00401067 8A 4D F1 mov c1, byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4], c1
11: a = p[1];
0040106D 8B 55 EC mov edx, dword ptr [ebp-14h]
0040106D 8A 42 01 mov a1, byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4], a1            

第一種在讀取時直接就把字元串中的元素讀到寄存器cl中,而第二種則要先把指針值讀到edx中,再根據edx讀取字元,顯然慢了。

7. 小結

堆和棧的差別可以用如下的比喻來看出:

  • 使用棧就像我們去飯館裡吃飯,隻管點菜(發出申請)、付錢、和吃(使用),吃飽了就走,不必理會切菜、洗菜等準備工作和洗碗、刷鍋等掃尾工作,其好處是快捷簡單,但是自由度小。
  • 使用堆就象是自己動手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由度大。