C++-內存管理

參考博客:https://blog.csdn.net/jing0611/article/details/4030237

在最開始的學習中,老師一直在說一個關於C++的話題。C++是一個極度追求性能的語言。
通過學習,這不是開玩笑,在我所學習的語言中,對內存的使用有如此的執着的“較真”也就是C++了。

因爲性能的緣故,一個內存不合理的分配,都可能在日後成爲一個隱患,成爲一個令人頭疼的BUG。所以最近想好好總結一下內存管理。我們在學習中也經常聽到棧溢出,內存泄漏等這些感覺很嚴重的問題。首先我們要搞清楚什麼是棧和什麼是堆。

棧和堆

首先要理解一個關鍵的問題。我們此時說的是內存分配中的棧和堆,不是在討論數據結構中的棧和二叉堆。我在之前學習操作系統的時候劃過一個圖,是關於Linux下操作系統程序地址空間的。中間還有棧區和堆區的分佈

內存管理

從圖中可以看出基本分佈,內存中的棧區是處於高地址以地址的增長方向爲上,棧地址是向下增長的。從上往下,棧區是分配局部變量空間。堆區是從下往上,堆區的地址是向上增長的用於分配程序員申請的內存空間。

內存分配方式

內存分配方式有三種

從靜態存儲區域分配

內存在程序編譯的時候就已經分配好了,這塊內存在程序的整個運行期間都存在

例如全局變量,static靜態成員變量

在棧上創建

執行函數時,函數內部變量的存儲單位可以在棧上創建,函數執行結束時,這些存儲單元自動釋放。棧內存分配運算置於處理器的指令集中,效率很高,但是分配的內存容量有限

在堆上分配

也稱爲動態內存分配。程序在運行的時候用malloc或new申請任意多少內存,程序員自己負責在何時用free或delete來釋放這塊內存。動態內存的生命週期由程序員決定,使用非常靈活,但如果在堆上分配了空間,就有責任回收它,否則運行的程序會出現泄漏,頻繁地分配和釋放不同大小的堆空間將會產生堆內碎塊。也就是我們常說的內存碎片。

程序內存空間

一個程序將操作系統分配給其運行的內存分爲4個區域

代碼區(code area)
全局數據區(data area)
堆區(heap area)
棧區(stack area)

棧區:由編譯器自動分配釋放,存放爲函數運行的局部變量,函數參數,返回數據,返回地址等。操作方式與數據結構中的類似。

堆區:一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收。分配方式類似於鏈表

全局數據區:也叫做靜態區,存放全局變量,靜態數據。程序結束後由系統釋放

文字常量區:可以理解爲常量區,常量字符串存放這裏。程序結束後由系統釋放

程序代碼區:存放函數體的二進制代碼。但是代碼段中也分爲代碼段和數據段。

關於文字常量區

文字常量區,在大多數解釋中,都僅僅說明常量字符串存放這裏。但是如果深究字眼,那麼其他常量比如整型是否存放這裏呢?我查閱了一些資料,是這麼解釋的:常量之所以稱爲“文字常量”,其中“文字”是指我們只能以它的值的形式指代它,“常量”是指它的值是不可變的。同時注意一點:文字常量是不可尋址的(即我們的程序中不可能出現獲取所謂常量20的存儲地址&20這樣的表達式),雖然常量也是存儲在內存的某個地方,但是我們沒有辦法訪問常量的地址的。

還有就是我們都知道的常量是有類型的。所以總的來說,只要是常量都存放在文字常量區!!

程序例子
int a = 0; //全局初始化區
char *p1; //全局未初始化區
int main() {
int b; //棧
char s[] = /"abc/"; //棧
char *p2; //棧
char *p3 = /"123456/"; //123456//0在常量區,p3在棧上。
static int c =0;//全局(靜態)初始化區
p1 = new char[10];
p2 = new char[20];
//分配得來得和字節的區域就在堆區。
strcpy(p1, /"123456/"); //123456//0放在常量區,編譯器可能會將它與p3所指向的/"123456/"優化成一個地方。
}

堆與棧的區別

申請方式

stack:由系統自動分配。比如在函數運行中聲明一個局部變量int b = 10;,系統自動在棧中爲b開闢空間。

heap:需要程序員自己申請,並指明大小,在C中是有malloc函數,在C++中多使用new運算符

p1 = (char*)malloc(10);
p2 = new char[10];
此時的p1和p2是在棧中的

從C++角度上說,使用new分配堆空間可以調用類的構造函數,而malloc()函數僅僅是一個函數調用,它不會調用構造函數,它所接受的參數是一個unsigned long類型。同樣,delete在釋放堆空間之前會調用析構函數,而free函數則不會。

申請後系統的響應

stack:只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,否則將報異常提示棧溢出

heap:在操作系統中有一個記錄空閒內存地址的表,這是一種鏈式結構。它記錄了有哪些還未使用的內存空間。當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,然後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序。

對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才能正確的釋放本內存空間。由於找到的堆結點的大小不一定正好等於申請的大小,系統會將自動的多餘的那部分重新放入空閒鏈表中。

申請大小的限制

stack:在WINDOWS中,棧是向下增長的,是一塊連續的區域。也就是說棧從棧頂的地址和棧的最大容量是系統預先規定好的。棧的大小是2M。當超過這個內存之後,會報Overflow異常。所以棧能申請的空間較小。

heap:堆是向上增長的,向高地址遞增的,因爲它是一個用鏈表來存儲的空閒地址空間。自然是個不連續的內存區域。堆的大小受限於計算機系統中有效的虛擬內存。我們知道當我們玩網絡遊戲時,當虛擬內存不夠時我們會更改虛擬內存的大小。一般初始值爲512M,可以更大,所以堆能分配的區域是相當大的。

申請效率的比較

stack:由系統自動分配,速度較快,但是程序員不可控

heap:由new或者malloc申請的一塊新空間(內存),一般速度比較慢,而且容易產生內存碎片,不過用起來方便。

在WINDOWS下,最好的方式是用VirtualAlloc分配內存,他不是在堆,也不是棧,而是直接在進程的地址空間中保留一快內存,雖然用起來最不方便。但是速度快,也最靈活。

VirtualAlloc是一個Windows API函數,該函數的功能是在調用進程的虛擬地址空間,預定或者提交一部分頁。

堆和棧的存儲內容

stack:在函數調用中,第一個進棧的是主函數中後的第一個指令(函數調用語句的下一條可執行語句)的地址,然後是函數的各個參數,在大多數的C編譯器中,參數是由右向左入棧的,然後是函數中的局部變量。但是,靜態變量不入棧

當本次函數調用結束後,局部變量先出棧,然後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。

heap:一般是在堆的頭部用一個字節存放堆的大小。堆中的內容由程序員自己安排。

存取效率的比較

char s1[] = 'a';
char *s2 = 'b';

a是在運行時刻才賦值的;而b是在編譯時就確定的;但是,在以後的存取中,在棧上的數組比指針所指向的字符串(堆)快。

無論是堆還是棧,都要防止越界現象的發生(除非你是故意使其越界),因爲越界的結果要麼是程序崩潰,要麼是摧毀程序的堆、棧結構,產生以想不到的結果。

個人總結

堆和棧的分配區別在一下幾個方面

1、管理方式不同;

2、空間大小不同;

3、能否產生碎片不同;

4、生長方向不同;

5、分配方式不同;

6、分配效率不同;

管理方式: 棧是在函數運行時,由系統自動分配;而堆是通過程序員自己調用malloc函數或者new運算符去申請一個需要的大小空間。

空間大小: 棧的空間大小並不大,一般最多爲2M,超過之後會報Overflow錯誤。堆的空間非常大,最大可到達4G,可操作的空間非常大。

能否產生碎片: 棧的操作與數據結構中的棧用法是類似的。‘後進先出’的原則,以至於不可能有一個空的內存塊從棧被彈出。因爲在它彈出之前,在它上面的後進棧的數據已經被彈出。它是嚴格按照棧的規則來執行。但是堆是通過new/malloc隨機申請的空間,頻繁的調用它們,則會產生大量的內存碎片。這是不可避免地。

生長方向: 棧的生長方向是由高地址向低地址增長,是自上而下的。堆的生長方向是由低地址向高地址增長,是自下而上的。

分配方式: 堆都是動態分配的,沒有靜態分配。但是棧有兩種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如局部變量的分配。動態分配由malloc函數實現,但是棧的動態分配和堆是不同的,它的動態分配是由編譯器進行和釋放,無需程序員進行操作。

分配效率: 棧是機器系統提供的數據結構,計算機底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行。這就決定了棧有着很高的效率。堆需要通過C/C++的庫函數進行一個複雜的算法,在對內存中搜尋一個足夠大小的空間,如果沒有足夠的空間(內存碎片空間太多),就有可能調用系統功能去增加程序數據段的內存空間,這樣就有機會分到足夠大小的內存,然後進行返回。顯然,堆的效率比棧要低的多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章