C++的動態(tài)內(nèi)存管理你真的了解嗎
前言
想必大家對c語言的動態(tài)內(nèi)存分配并不陌生,忘了的小伙伴也可以看看我的這篇文章C語言動態(tài)內(nèi)存分配
c語言的動態(tài)內(nèi)存分配由于有些地方用起來比較麻煩同時檢查錯誤的機(jī)制不適合c++,因此c++引入new/delete操作符進(jìn)行內(nèi)存管理,下面我們來深入探討c++為什么要引入new/delete
用法上
對內(nèi)置類型
new/delete同樣是在堆區(qū)申請釋放空間
new和delete申請釋放單個元素的空間,new[]和delete[]申請釋放一塊連續(xù)的空間,new與delete匹配,new[]與delete[]匹配。new后面直接跟類型,不需要強(qiáng)制類型轉(zhuǎn)換。簡單來說new/delete用于單個對象,new[]/delete[]用于多個對象。
對內(nèi)置類型沒有什么區(qū)別,new操作符和malloc函數(shù)一樣,不會對空間初始化,唯一的不同在后面的底層原理會介紹
#include <iostream> using namespace std; int main() { int* p1 = (int*)malloc(sizeof(int)); int* p2 = (int*)malloc(sizeof(int) * 10); //new后面跟動態(tài)申請的類型 int* p3 = new int;//動態(tài)申請一個int型空間 int* pp3 = new int(1);//動態(tài)申請一個int型空間并初始化為1 //動態(tài)申請10個int型空間并初始化 int* p4 = new int[10]{ 1,2,3 }; free(p1); free(p2); delete p3; delete pp3; delete[] p4; }
對自定義類型
對自定義類型那就有很大的區(qū)別了,new一個自定義對象,在申請空間后還會調(diào)用構(gòu)造函數(shù)初始化,delete對象會先調(diào)用析構(gòu)函數(shù)清理對象中的資源,然后釋放空間
#include <iostream> using namespace std; class Test { public: Test(int data = 10) : _data(data) { cout << "Test():" << endl; } ~Test() { cout << "~Test():" << endl; } private: int _data; }; int main() { // 申請單個Test類型的空間 Test* p1 = (Test*)malloc(sizeof(Test)); free(p1); // 申請10個Test類型的空間 Test* p2 = (Test*)malloc(sizeof(Test) * 10); free(p2); // 申請單個Test類型的對象 Test* p3 = new Test; //申請空間并調(diào)用構(gòu)造函數(shù)初始化 delete p3; // 申請10個Test類型的對象 Test* p4 = new Test[10];//申請空間并調(diào)用構(gòu)造函數(shù)初始化 delete[] p4; return 0; }
簡單解釋一下:對于delete操作符先調(diào)用析構(gòu)函數(shù)清理對象的資源,再釋放空間,這里為了演示我只寫了棧的構(gòu)造函數(shù)和析構(gòu)函數(shù)
#include <iostream> using namespace std; class Stack { public: Stack(int capacity = 4) :_capacity(capacity) { int* _p = new int[capacity]; _top = 0; _capacity = capacity; cout << "Stack()" << endl; } ~Stack() { delete[] _p; _top = _capacity = 0; cout << "~Stack()" << endl; } private: int* _p; int _top; int _capacity; }; int main() { Stack* s1=new Stack; delete s1; return 0; }
delete先調(diào)用構(gòu)造函數(shù)清理對象維護(hù)的堆區(qū)B的資源,然后再釋放堆區(qū)A的空間
new/delete底層原理
通過匯編代碼我們發(fā)現(xiàn),在底層:new會調(diào)用operator new函數(shù)和Stack構(gòu)造函數(shù),而delete也會調(diào)用析構(gòu)函數(shù)和operator delete函數(shù),那么operator new和operator delete是什么函數(shù)呢?
operator new和operator delete是系統(tǒng)提供的兩個全局函數(shù),通過operator new申請空間,operator delete釋放空間,實際上operator new也是調(diào)用malloc申請空間的,operator delete也是調(diào)用free釋放空間的,那么為什么不直接用malloc和free呢?
c語言中malloc申請空間失敗會返回NULL空指針,那我們檢查錯誤的方式就是通過errno錯誤碼,而面向?qū)ο蟮恼Z言,處理錯誤的方式一般是拋異常,C++中也要求拋異常——try catch,
這里我簡單提一下拋異常,當(dāng)我們new失敗后,如果不捕獲異常就會拋異常
int main() { //由于申請空間過大new失敗 char* p = new char[1024u * 1024u * 1024u * 2 - 1]; printf("execute\n"); return 0; }
那我們捕獲異常,new失敗后編譯器就會提示申請失敗原因
#include <iostream> using namespace std; int main() { try //檢測異常 { char* p = new char[1024u * 1024u * 1024u * 2 - 1]; //new失敗,直接跳到catch,不執(zhí)行下面語句 printf("execute\n"); } catch (const exception& e) //捕獲并處理異常 { cout << e.what() << endl; } return 0; }
所以這就是為什么不直接使用malloc的原因,因為malloc申請空間失敗不會拋異常
而operator delete也是在free函數(shù)的基礎(chǔ)上增加了一些檢查機(jī)制
operator new/operator delete用法與malloc/free函數(shù)類似,在定位new中會介紹
C++也有operator new[]和operator delete[],僅僅是為了和new[]和delete[]配對
重載類的專屬operator new和 operator delete
operator new和operator delete是可以自己定義的,一般用于在STL中的內(nèi)存池中申請空間,沒學(xué)過內(nèi)存池的小伙伴可以簡單了解一下。
有個住在山上的少年,他每次洗澡,做飯,洗衣服需要用水的時候都需要跑到山下的河邊接水,然后再回到山上,每天重復(fù)多次。那么可不可以在家附近建一個小水池,一次性將水池裝滿水,每次用水的時候就直接從水池打水就行了,不需要每次都跑到山下。這也就是內(nèi)存池出現(xiàn)的原理,這里山下的小河可以看作操作系統(tǒng)的堆區(qū),而每次打水可以看作是從堆區(qū)申請空間,水池可以看作內(nèi)存池,這樣我們直接從堆區(qū)申請一大塊空間放在內(nèi)存池,每次需要申請時,直接到內(nèi)存池申請空間就行了,不需要每次都從堆區(qū)申請空間。
這里以雙鏈表為例,如果沒有內(nèi)存池,每次創(chuàng)建新的節(jié)點時都需要從堆區(qū)申請空間,頻繁的找操作系統(tǒng)申請會大大降低效率
我們直接在類里面定義operator new和operator delete,與系統(tǒng)提供的兩個全局的函數(shù)構(gòu)成重載,到時候new和delete就會調(diào)用我們定義的operator new和delete而不會調(diào)用那兩個全局的函數(shù)
struct ListNode { ListNode* _next; ListNode* _prev; int _data; ListNode(int val = 0) :_next(nullptr) ,_prev(nullptr) ,_data(val) {} void* operator new(size_t n) { void* p = nullptr; p = allocator<ListNode>().allocate(1); //allocator是STL中的內(nèi)存池 cout << "memory pool allocate" << endl; return p; } void operator delete(void* p) { allocator<ListNode>().deallocate((ListNode*)p, 1); cout << "memory pool deallocate" << endl; } }; class List { public: List() { _head = new ListNode; _head->_next = _head; _head->_prev = _head; } void ListPush(int val) { ListNode* newnode = new ListNode; newnode->_data = val; ListNode* tail = _head->_prev; tail->_next = newnode; newnode->_prev = tail; newnode->_next = _head; _head->_prev = newnode; } ~List() { ListNode* cur = _head->_next; while (cur != _head) { ListNode* next = cur->_next; delete cur; cur = next; } delete _head; _head = nullptr; } private: ListNode* _head; }; int main() { List l; l.ListPush(1); l.ListPush(2); l.ListPush(3); l.ListPush(4); return 0; }
定位new
定位new表達(dá)式在實際中一般是配合內(nèi)存池使用。因為內(nèi)存池分配出的內(nèi)存沒有初始化
#include <iostream> using namespace std; class Test { public: Test(int data = 10) : _data(data) { cout << "Test():" << endl; } ~Test() { cout << "~Test():" << endl; } private: int _data; }; //構(gòu)造函數(shù)是不支持直接手動調(diào)用的,所以引入定位new,對已經(jīng)存在的對象調(diào)用構(gòu)造函數(shù)初始化,常用于內(nèi)存池中的對象 int main() { //調(diào)用operator new申請空間,operator new和malloc函數(shù)用法類似 Test* p = (Test*)operator new(sizeof(Test)); //調(diào)用構(gòu)造函數(shù)初始化 new(p)Test(1); //定位new,new后面接要初始化對象的地址+類型,(1)表示初始化為1 //先調(diào)用析構(gòu)函數(shù),析構(gòu)函數(shù)可以直接調(diào)用 p->~Test(); //在調(diào)用operator delete釋放空間 operator delete (p); return 0; }
new/delete與malloc/free區(qū)別總結(jié)
共同點:都從堆上申請空間,并需要手動釋放空間,因為new/delete可以說是對malloc/free的升級,申請/釋放空間還是會調(diào)用malloc/free
不同點:
1、new/delete是操作符,而malloc/free是函數(shù)
2、首先是用法上的不同,malloc返回類型是void*,需要強(qiáng)轉(zhuǎn),而new不需要強(qiáng)轉(zhuǎn)了,new后面直接跟類型,new也不需要計算空間大小,申請N個加[N],new/delete配對,mew[]/delete[]配對
3、malloc失敗返回NULL指針,new失敗如果不catch捕獲就會拋異常
4、對內(nèi)置類型,new和malloc一樣也不會初始化,但對自定義類型,new會先申請空間再調(diào)用構(gòu)造函數(shù)初始化,delete會先調(diào)用析構(gòu)函數(shù)清理對象中的資源再釋放空間
內(nèi)存泄漏
動態(tài)申請的內(nèi)存空間不使用了,又沒有主動釋放,就存在內(nèi)存泄漏,當(dāng)然不是所有的內(nèi)存泄漏都有危害。
1.出現(xiàn)內(nèi)存泄漏的進(jìn)程正常結(jié)束,會將內(nèi)存還給操作系統(tǒng),不會有什么危害
2.但出現(xiàn)內(nèi)存泄漏的進(jìn)程非正常結(jié)束,比如僵尸進(jìn)程,或是長期運行的程序,比如服務(wù)器程序,出現(xiàn)內(nèi)存泄漏,那么危害就很大了,系統(tǒng)會越來越慢,甚至是卡死宕機(jī)
所以我們在new申請空間時,一定要記得delete釋放空間
了解到這里我們就明白了c++為什么要引入new/delete了
1.對自定義類型,對象動態(tài)申請空間時,new/delete會自動調(diào)用構(gòu)造函數(shù)/析構(gòu)函數(shù)
2.new失敗后拋異常,符合面向?qū)ο笳Z言對出錯的處理機(jī)制
3.我們可以定義類專屬的operator new和operator delete,這樣可以從內(nèi)存池申請空間,避免頻繁從堆區(qū)申請空間
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
詳解C語言求兩個數(shù)的最大公約數(shù)及最小公倍數(shù)的方法
這篇文章主要介紹了C語言求兩個數(shù)的最大公約數(shù)及最小公倍數(shù)的方法,輾轉(zhuǎn)相除法和輾轉(zhuǎn)相減法在解決這種問題時最常用到,需要的朋友可以參考下2016-03-03c++連續(xù)輸入未知個數(shù)的數(shù)字操作
這篇文章主要介紹了c++連續(xù)輸入未知個數(shù)的數(shù)字操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12Cocos2d-x學(xué)習(xí)筆記之CCLayerColor層的使用實例
這篇文章主要介紹了Cocos2d-x學(xué)習(xí)筆記之CCLayerColor層的使用實例,CCLayerColor是一個顏色布景層類,本文依然使用Hello World作為例子講解,需要的朋友可以參考下2014-09-09