原博客:https://www.cnblogs.com/findumars/p/5929831.html?utm_source=itdadao&utm_medium=referral
原博客很長,本博客的目的是摘取目前對自己很有用的信息。
1 內存管理
1.1 C++內存管理詳解
1.1.1 內存分配方式
1.1.1.1 簡介
在C++中,內存分為:棧、堆、自由存儲區、全局/靜態存儲區、常量存儲區。
棧,在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束是這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率高,分配的內存容量有限。
堆,就是那些由malloc等分配的內存塊,用free來釋放內存。
自由存儲區,那些由new分配的內存塊,由應用程序去控制,一般一個new就要對應一個delete。如果程序員沒有釋放掉,那么在程序結束后,操作系統會自動回收。
全局/靜態存儲區,全局變量和靜態變量被分配到同一塊內存中,在以前的C語言中,全局變量又分為初始化的和未初始化的,在C++里面沒有這個區分了,他們共同占用同一塊內存區。
常量存儲區,這是一塊比較特殊的存儲區,他們里面存放的是常量,不允許修改。
1.1.1.2 堆和自由存儲區的區別與聯系
從技術上來說,堆(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++函數庫提供的,它的機制是很復雜的,例如為了分配一塊內存,庫函數會按照一定的算法在堆內存中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由於內存碎片太多),就有可能調用系統功能去增加程序數據段的內存空間,這樣就有機會分到足夠大小的內存,然后進行返回。顯然,堆的效率比棧要低得多。
1.1.2 嵌入式系統--C++動態內存分配
在嵌入式系統中使用C++的一個常見問題是內存分配,即對new 和 delete 操作符的失控。具有諷刺意味的是,問題的根源卻是C++對內存的管理非常的容易而且安全。具體地說,當一個對象被消除時,它的析構函數能夠安全的釋放所分配的內存。這當然是個好事情,但是這種使用的簡單性使得程序員們過度使用new 和 delete,而不注意在嵌入式C++環境中的因果關系。並且,在嵌入式系統中,由於內存的限制,頻繁的動態分配不定大小的內存會引起很大的問題以及堆破碎的風險。當你必須要使用new 和delete時,你不得不控制C++中的內存分配。你需要用一個全局的new 和delete來代替系統的內存分配符,並且一個類一個類的重載new 和delete。
1.1.2.1 為什么需要new/delete
malloc與free是C++/C語言的標准庫函數,new/delete是C++的運算符。它們都可用於申請動態內存和釋放內存。對於非內部數據類型的對象而言,光用maloc/free無法滿足動態對象的要求。對象在創建的同時要自動執行構造函數,對象在消亡之前要自動執行析構函數。由於malloc/free是庫函數而不是運算符,不在編譯器控制權限之內,不能夠把執行構造函數和析構函數的任務強加於malloc/free。因此C++語言需要一個能完成動態內存分配和初始化工作的運算符new,以及一個能完成清理與釋放內存工作的運算符delete。最好不要混用。
運算符詳解參考:https://blog.csdn.net/zxx910509/article/details/63679774
1.1.2.2 new/delete使用要點
運算符new使用起來要比函數malloc簡單得多,例如:
int *p1 = (int *)malloc(sizeof(int) * length); int *p2 = new int[length];
這是因為new內置了sizeof、類型轉換和類型安全檢查功能。對於非內部數據類型的對象而言,new在創建動態對象的同時完成了初始化工作。如果對象有多個構造函數,那么new的語句也可以有多種形式。例如:
class Obj { public : Obj(void); // 無參數的構造函數 Obj(int x); // 帶一個參數的構造函數 … } void Test(void) { Obj *a = new Obj; Obj *b = new Obj(1); // 初值為1 … delete a; delete b; }
如果用new創建對象數組,那么只能使用對象的無參數構造函數。例如:
Obj *objects = new Obj[100]; // 創建100個動態對象
不能寫成:
Obj *objects = new Obj[100](1);// 創建100個動態對象的同時賦初值1
在用delete釋放對象數組時,留意不要丟了符號‘[]’。例如:
delete []objects; // 正確的用法 delete objects; // 錯誤的用法
后者有可能引起程序崩潰和內存泄漏。
1.1.2.3 重載全局new和delete操作符
重載參考:https://blog.csdn.net/zxx910509/article/details/64905107
void * operator new(size_t size){ void *p = malloc(size); return (p); } void operator delete(void *p);{ free(p); }
也可以對單個類的new 和 delete 操作符重載。
class TestClass { public: void * operator new(size_t size); void operator delete(void *p); // .. other members here ... }; void *TestClass::operator new(size_t size) { void *p = malloc(size); // Replace this with alternative allocator return (p); } void TestClass::operator delete(void *p) { free(p); }
1.1.2.4 重載new[ ]和delete[ ]
C++將對象數組的內存分配作為一個單獨的操作,而不同於單個對象的內存分配。
class TestClass{ public: void *operator new[](size_t size); void operator delete[](void *p); } void *TestClass::operator new[](size_t size){ void *p=malloc(size); return p; } void TestClass:operator delete[](void *p){ free(p); } int main(){ TestClass *p=new TestClass[10]; delete[] p; }
1.1.3 關於內存的一些編程習慣總結
- 用malloc或new申請內存之后,應該立即檢查指針值是否為NULL。防止使用指針值為NULL的內存。
- 不要忘記為數組和動態內存賦初值。防止將未被初始化的內存作為右值使用。
- 避免數組或指針的下標越界,特別要當心發生“多1”或者“少1”操作。
- 動態內存的申請與釋放必須配對,防止內存泄漏。
- 用free或delete釋放了內存之后,立即將指針設置為NULL,防止產生“野指針”。
1.1.4 內存耗盡
如果在申請動態內存時找不到足夠大的內存塊,malloc和new將返回NULL指針,宣告內存申請失敗。
1.1.4.1 虛擬內存
虛擬內存的目的是為了讓物理內存擴充成更大的邏輯內存,從而讓程序獲得更多的可用內存。
為了更好的管理內存,操作系統將內存抽象成地址空間。每個程序擁有自己的地址空間,這個地址空間被分割成多個塊,每一塊稱為一頁。這些頁被映射到物理內存,但不需要映射到連續的物理內存,也不需要所有頁都必須在物理內存中。當程序引用到不在物理內存中的頁時,由硬件執行必要的映射,將缺失的部分裝入物理內存並重新執行失敗的指令。
1.2 C++健壯指針和資源管理
先完善C++只是再回頭來學
2 內存泄漏