C++ 内存管理
1. C++內存管理
-
C/C++程序內存的分配
C/C++編譯的程序占用的內存分區
- 棧區(stack):由編譯器自動分配釋放 ,存放函數的參數名,局部變量的名等。其操作方式類似于數據結構中的棧。
- 堆區(heap):由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收。注意它與數據結構中的堆是兩回事,分配方式倒是類似于鏈表。
- 全局/靜態存儲區 :全局變量和局部靜態變量的存儲是放在一塊的(在以前的C語言中,全局變量又分為初始化的和未初始化的,在C++里面沒有這個區分)。程序結束后由系統釋放。
- 常量存儲區 :常量字符串就是放在這里的,正常情況不允許修改,程序結束后由系統釋放 。
- 程序代碼區 :放函數體的二進制代碼。
在C++中,內存分為:棧、堆、自由存儲區、全局/靜態存儲區、常量存儲區。
- 棧,在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束是這些存儲單元自動被釋放。棧內存分配運算內置于處理器的指令集中,效率高,分配的內存容量有限。
- 堆,就是那些由malloc等分配的內存塊,用free來釋放內存。
- 自由存儲區,那些由new分配的內存塊,由應用程序去控制,一般一個new就要對應一個delete。如果程序員沒有釋放掉,那么在程序結束后,操作系統會自動回收。
- 全局/靜態存儲區,全局變量和靜態變量被分配到同一塊內存中,在以前的C語言中,全局變量又分為初始化的和未初始化的,在C++里面沒有這個區分了,他們共同占用同一塊內存區。
- 常量存儲區,這是一塊比較特殊的存儲區,他們里面存放的是常量,不允許修改。
?
堆和自由存儲區的區別與聯系
從技術上來說,堆(heap)是C語言和操作系統的術語。堆是操作系統所維護的一塊特殊內存,它提供了動態分配的功能,當運行程序調用malloc()時就會從中分配,稍后調用free可把內存交還。而自由存儲是C++中通過new和delete動態分配和釋放對象的抽象概念,通過new來申請的內存區域可稱為自由存儲區。基本上,所有的C++編譯器默認使用堆來實現自由存儲,也即是缺省的全局運算符new和delete也許會按照malloc和free的方式來被實現,這時藉由new運算符分配的對象,說它在堆上也對,說它在自由存儲區上也正確。但程序員也可以通過重載操作符,改用其他內存來實現自由存儲,例如全局變量做的對象池,這時自由存儲區就區別于堆了。我們所需要記住的就是:
- 堆是C語言和操作系統的術語、是操作系統維護的一塊內存,而自由存儲是C++中通過new與delete動態分配和釋放對象的抽象概念。堆與自由存儲區并不等價。
- new所申請的內存區域在C++中稱為自由存儲區。藉由堆實現的自由存儲,可以說new所申請的內存區域在堆上。
1.1.1.3 堆和棧的區別
- 管理方式:對于棧來講,是由編譯器自動管理,無需我們手工控制;對于堆來說,釋放工作由程序員控制,容易產生memory leak。
- 空間大小:一般來講在32位系統下,堆內存可以達到4G的空間,從這個角度來看堆內存幾乎是沒有什么限制的。但是對于棧來講,一般都是有一定的空間大小的,默認的棧空間大小是1M(VS2017 項目-屬性-鏈接器-系統可以修改)。
- 碎片問題:對于堆來講,頻繁的malloc/free勢必會造成內存空間的不連續,從而造成大量的碎片,使程序效率降低。棧是先進后出的隊列,以至于永遠都不可能有一個內存塊從棧中間彈出。
- 分配方式:堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如局部變量的分配。動態分配由alloca函數進行分配,但是棧的動態分配和堆是不同的,他的動態分配是由編譯器進行釋放,無需我們手工實現。
- 分配效率:棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是C/C++函數庫提供的,它的機制是很復雜的,例如為了分配一塊內存,庫函數會按照一定的算法在堆內存中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由于內存碎片太多),就有可能調用系統功能去增加程序數據段的內存空間,這樣就有機會分到足夠大小的內存,然后進行返回。顯然,堆的效率比棧要低得多。
?
?
2. new/delete和malloc/free的區別與聯系:
- new/delete是C++關鍵字,需要編譯器支持。malloc/free是庫函數,需要頭文件支持。
- 使用new操作符申請內存分配時無須指定內存塊的大小,編譯器會根據類型信息自行計算。而malloc則需要顯式地指出所需內存的尺寸。
- new操作符內存分配成功時,返回的是對象類型的指針,類型嚴格與對象匹配,無須進行類型轉換,故new是符合類型安全性的操作符。而malloc內存分配成功則是返回void * ,需要通過強制類型轉換將void*指針轉換成我們需要的類型。
- new內存分配失敗時,會拋出bac_alloc異常。malloc分配內存失敗時返回NULL。
- new會先調用operator new函數,申請足夠的內存(通常底層使用malloc實現)。然后調用類型的構造函數,初始化成員變量,最后返回自定義類型指針。delete先調用析構函數,然后調用operator delete函數釋放內存(通常底層使用free實現)。
- ?malloc/free是庫函數,只能動態的申請和釋放內存,無法強制要求其做自定義類型對象構造和析構工作。
- C++允許重載new/delete操作符,特別的,布局new的就不需要為對象分配內存,而是指定了一個地址作為內存起始區域,new在這段內存上為對象調用構造函數完成初始化工作,并返回此地址。而malloc不允許重載。
- new操作符從自由存儲區(free store)上為對象動態分配內存空間,而malloc函數從堆上動態分配內存。自由存儲區是C++基于new操作符的一個抽象概念,凡是通過new操作符進行內存申請,該內存即為自由存儲區。而堆是操作系統中的術語,是操作系統所維護的一塊特殊內存,用于程序的內存動態分配,C語言使用malloc從堆上分配內存,使用free釋放已分配的對應內存。自由存儲區不等于堆,如上所述,布局new就可以不位于堆中。
注意:
堆是一個實際的區域,而自由存儲區是一個更上層的概念。通常new確實是在堆上申請內存,但是程序員可以自己重載new操作符,使用其他內存來實現自由存儲(這并不常見)。另外,c++ primer plus這本書上有提到布局new,可以為對象在棧上分配內存。總的來說,自由存儲區是new申請的區間的概念。
?
?
既然new/delete的功能覆蓋了malloc/free,為什么C++還要保留malloc/free?因為C++程序經常要調用C函數,而C程序只能用malloc/free管理動態內存。
new/delete、malloc/free底層實現原理:new/delete的底層實現是調用malloc/free函數實現的,而malloc/free的底層實現也不是直接操作內存而是調用系統API實現的。
new/delete的兩種分配方式原理圖如下:
?注意,針對上圖最末尾所述的“new[]/delete[]時會多開辟4字節用于存儲對象個數”,作如下說明:
對于內置類型:
- new []不會在首地址前4個字節定義數組長度。
- delete 和 delete[]是一樣的執行效果,都會刪除整個數組,要刪除的長度從new時即可知道。
對于自定義類型:
- new []會在首地址前4個字節定義數組長度。
- 當delete[]時,會根據前4個字節所定義的長度來執行析構函數刪除整個數組。
- 如果只是delete數組首地址,只會刪除第一個對象的值。?
1. 參考資料:
1. C++內存管理
總結
- 上一篇: static、const用法
- 下一篇: 成都欢乐谷淡季是什么时候