深入理解C++中的new和delete并實(shí)現(xiàn)對(duì)象池
深入理解new和delete
new和delete稱作運(yùn)算符
我們轉(zhuǎn)反匯編看看
這2個(gè)運(yùn)算符本質(zhì)也是相應(yīng)的運(yùn)算符的重載的調(diào)用
malloc和new的區(qū)別?
1.malloc按字節(jié)開辟內(nèi)存的;new開辟內(nèi)存時(shí)需要指定類型 new int[10]
所以malloc開辟內(nèi)存返回的都是void*
而new相當(dāng)于運(yùn)算符的重載函數(shù) operator new ->返回值自動(dòng)轉(zhuǎn)成指定的類指針 int*
2.malloc只負(fù)責(zé)開辟空間,new不僅僅有malloc的功能,可以進(jìn)行數(shù)據(jù)的初始化
new int(20);//初始化20 new int[20]();//開辟數(shù)組是不支持初始化值的,但是支持寫個(gè)空括號(hào),表示給每個(gè)元素初始化為0 ,相當(dāng)于每個(gè)元素調(diào)用int()成為0
3.malloc開辟內(nèi)存失敗返回nullptr指針;new拋出的是bad_alloc類型的異常
(也就是說,new運(yùn)算符開辟內(nèi)存失敗,要把它的代碼擴(kuò)在try catch里面,是不能通過返回值和空指針比較的。)
try//可能發(fā)生錯(cuò)誤的代碼放在try里面 { int *p = new int; delete []p; int *q = new int[10]; delete q; } catch (const bad_alloc &err)//捕獲相應(yīng)類型的異常 { cerr << err.what() << endl;//打印錯(cuò)誤 }
free和delete的區(qū)別?
delete p: 調(diào)用析構(gòu)函數(shù),然后再free( p),相當(dāng)于包含了free
如果delete的是普通的指針,那么delete (int*)p和free( p)是沒有區(qū)別的
因?yàn)閷?duì)于整型指針來說,沒有析構(gòu)函數(shù),只剩下內(nèi)存的釋放
new -> 對(duì)operator new重載函數(shù)的調(diào)用 delete -> 對(duì)operator delete重載函數(shù)的調(diào)用
把new和delete的重載函數(shù)定義在全局的地方,這樣我們整個(gè)項(xiàng)目工程中只有涉及到new和delete的地方都會(huì)調(diào)用到我們?nèi)种貙懙膎ew,delete的重載函數(shù)。
//先調(diào)用operator new開辟內(nèi)存空間、然后調(diào)用對(duì)象的構(gòu)造函數(shù)(初始化) void* operator new(size_t size) { void *p = malloc(size); if (p == nullptr) throw bad_alloc(); cout << "operator new addr:" << p << endl; return p; } //delete p; 先調(diào)用p指向?qū)ο蟮奈鰳?gòu)函數(shù)、再調(diào)用operator delete釋放內(nèi)存空間 void operator delete(void *ptr) { cout << "operator delete addr:" << ptr << endl; free(ptr); }
new和delete從內(nèi)存管理的角度上來說和malloc和free沒有什么區(qū)別
除非就是內(nèi)存開辟失敗,返回不一樣
void* operator new[](size_t size) { void *p = malloc(size); if (p == nullptr) throw bad_alloc(); cout << "operator new[] addr:" << p << endl; return p; } void operator delete[](void *ptr) { cout << "operator delete[] addr:" << ptr << endl; free(ptr); }
C++中,如何設(shè)計(jì)一個(gè)程序檢測(cè)內(nèi)存泄漏問題?
內(nèi)存泄漏就是new操作沒有對(duì)應(yīng)的delete,我們可以在全局重寫上面這些函數(shù),在new操作里面用映射表記錄都有哪些內(nèi)存被開辟過,delete的時(shí)候把相應(yīng)的內(nèi)存資源刪除掉,new和delete都有對(duì)應(yīng)關(guān)系
如果整個(gè)系統(tǒng)運(yùn)行完了,我們發(fā)現(xiàn),映射表記錄的一些內(nèi)存還沒有被釋放,就存在內(nèi)存泄漏了! 我們用new和delete接管整個(gè)應(yīng)用的所有內(nèi)存管理 ,對(duì)內(nèi)存的開辟和釋放都記錄
也可以通過編譯器既定的宏和API接口,把函數(shù)調(diào)用堆棧打印出來,到底在哪個(gè)源代碼的哪一頁的哪一行做了new操作沒有delete
new和delete能混用嗎?
C++為什么區(qū)分單個(gè)元素和數(shù)組的內(nèi)存分配和釋放呢?
下面這樣操作是否可以???
其實(shí)現(xiàn)在對(duì)于整型來說,沒有所謂的構(gòu)造函數(shù)和析構(gòu)函數(shù)可言,所以這樣的代碼就只剩下malloc和free的功能,所以底層調(diào)用的就是malloc和free
所以,它們現(xiàn)在混用是沒有問題的!??!
那什么時(shí)候我們才需要考慮這些問題呢?
class Test { public: Test(int data = 10) { cout << "Test()" << endl; } ~Test() { cout << "~Test()" << endl; } private: int ma; };
在這里面,我們能不能混用呢?
出現(xiàn)錯(cuò)誤了。
此時(shí)new和delete不能進(jìn)行混用了!
在這里,new和delete可以混用嗎?
運(yùn)行出錯(cuò)了。
我們最好是這樣配對(duì)使用:
new delete new[] delete[]
對(duì)于普通的編譯器內(nèi)置類型
new/delete[]
new[]/delete
這樣混用是可以的!
因?yàn)橹簧婕皟?nèi)存的開辟和釋放,底層調(diào)用的就是malloc和free
但是,如果是對(duì)象,就不能混用了。
一個(gè)Test對(duì)象是4個(gè)字節(jié)。
每一個(gè)Test對(duì)象有1個(gè)整型的成員變量。
new的時(shí)候,分配了5個(gè)Test對(duì)象,但是不只是開辟了20個(gè)字節(jié)哦!
delete[]p2的時(shí)候先調(diào)用Test對(duì)象的析構(gòu)函數(shù),析構(gòu)函數(shù)有this指針,this指針區(qū)分析構(gòu)的對(duì)象,this指針把正確的對(duì)象的地址傳到析構(gòu)函數(shù)?,F(xiàn)在加了[]表示有好幾個(gè)對(duì)象,有一個(gè)數(shù)組,里面的每個(gè)對(duì)象都要析構(gòu),但是它是怎么知道是有5個(gè)對(duì)象呢???
所以,實(shí)際上,new Test[5]是開辟了如圖式的內(nèi)存:
多開辟了4個(gè)字節(jié),存儲(chǔ)對(duì)象的個(gè)數(shù)。
用戶在寫new Test[5]時(shí),這個(gè)5是要被記錄下來的。
而且,new操作完了之后,給以后返回的p2指針指向的地址是0x104這個(gè)地址!即數(shù)組首元素的地址。并不是真真正正底層開辟的0x100這個(gè)地址,因?yàn)槟莻€(gè)是不需要讓用戶知道的,用戶只需要知道這個(gè)指針指向的是第一個(gè)元素對(duì)象的地址。
當(dāng)我們?nèi)elete[]p2的時(shí)候,它一看這個(gè)[]就知道釋放的是一個(gè)對(duì)象數(shù)組,那么就要從p2(0x104)上移4個(gè)字節(jié),去取對(duì)象的個(gè)數(shù),知道是5個(gè)對(duì)象了(一個(gè)對(duì)象是4字節(jié)),然后把ox104下的內(nèi)存平均分成5份,每一份內(nèi)存的起始地址就是對(duì)象的起始地址,然后傳給對(duì)象的析構(gòu)函數(shù),就可以進(jìn)行對(duì)象的析構(gòu)了。然后進(jìn)行內(nèi)存的釋放,operator delete(p2-4),從0x100開始釋放?。?!
這個(gè)代碼錯(cuò)誤在:實(shí)際上開辟的內(nèi)存空間大小是20+4=24字節(jié),開辟內(nèi)存是從0028開辟的,因?yàn)樗形鰳?gòu)函數(shù),所以在底層給數(shù)組開辟內(nèi)存時(shí)多開辟了4個(gè)字節(jié)來存儲(chǔ)開辟的對(duì)象的個(gè)數(shù),但是用戶返回的是02c,比028剛好多了4個(gè)字節(jié),也就是給用戶返回的是真真正正對(duì)象的起始地址。
delete p2;它就認(rèn)為p2只是指向1個(gè)對(duì)象,因?yàn)闆]有使用delete[],所以它就只是把Test[0]這個(gè)對(duì)象析構(gòu)了而已,然后直接free(p2),從第一個(gè)對(duì)象的地址(02c)開始free,而底層內(nèi)存是從028開始開辟的。
我們換成delete[]p2,來運(yùn)行看看
從指針-4開始free釋放內(nèi)存的操作
這個(gè)代碼的出錯(cuò)在:只是new出來1個(gè)對(duì)象,在0x104開辟的,p1也是指向了0x104,但是在delete[]的時(shí)候,認(rèn)為是指向的是對(duì)象數(shù)組,因?yàn)檫€有析構(gòu)函數(shù),于是它就從0x104上移4個(gè)字節(jié)去取開辟對(duì)象的個(gè)數(shù),
這就出現(xiàn)了問題了。
關(guān)鍵是它free的時(shí)候,執(zhí)行的是free(0x104-4)
但是new的時(shí)候并不是從0x100開始開辟內(nèi)存的。
自定義的類類型,有析構(gòu)函數(shù),為了調(diào)用正確的析構(gòu)函數(shù),那么開辟對(duì)象數(shù)組的時(shí)候,會(huì)多開辟4個(gè)字節(jié),記錄對(duì)象的個(gè)數(shù)
對(duì)象池代碼應(yīng)用
對(duì)象池的實(shí)現(xiàn)是靜態(tài)鏈表,在堆上開辟的。
#include <iostream> using namespace std; template<typename T> class Queue { public: Queue()//構(gòu)造函數(shù) 0構(gòu)造(默認(rèn)構(gòu)造) { _front = _rear = new QueueItem(); } ~Queue()//析構(gòu)函數(shù) { QueueItem *cur = _front;//指向頭結(jié)點(diǎn) while (cur != nullptr) { _front = _front->_next; delete cur; cur = _front; } } void push(const T &val)//入隊(duì)操作 { QueueItem *item = new QueueItem(val);//malloc _rear->_next = item; _rear = item; } void pop()//出隊(duì)操作 隊(duì)頭出 頭刪法 { if (empty()) return; QueueItem *first = _front->_next; _front->_next = first->_next; if (_front->_next == nullptr)//隊(duì)列原本只有1個(gè)有效元素節(jié)點(diǎn) { _rear = _front; } delete first;//free } T front()const//獲取首元素的值 { return _front->_next->_data; } bool empty()const { return _front == _rear; }//判空 鏈?zhǔn)疥?duì)列 private: //產(chǎn)生一個(gè)QueueItem的對(duì)象池(10000個(gè)QueueItem節(jié)點(diǎn)) struct QueueItem//節(jié)點(diǎn)類型,鏈?zhǔn)疥?duì)列,帶頭節(jié)點(diǎn)的單鏈表 { QueueItem(T data = T()) :_data(data), _next(nullptr) {}//構(gòu)造函數(shù) //給QueueItem提供自定義內(nèi)存管理 void* operator new(size_t size) { if (_itemPool == nullptr)//如果對(duì)象池滿了,對(duì)象池的指針就指向空了,然后現(xiàn)在進(jìn)入,再開辟一個(gè)對(duì)象池 { _itemPool = (QueueItem*)new char[POOL_ITEM_SIZE*sizeof(QueueItem)];//開辟池 QueueItem *p = _itemPool; for (; p < _itemPool + POOL_ITEM_SIZE - 1; ++p)//連在一個(gè)鏈表上 { p->_next = p + 1;//因?yàn)楣?jié)點(diǎn)內(nèi)存是連續(xù)開辟的 可以用p+1 } p->_next = nullptr; } QueueItem *p = _itemPool; _itemPool = _itemPool->_next; return p; } void operator delete(void *ptr) { QueueItem *p = (QueueItem*)ptr; p->_next = _itemPool; _itemPool = p;//往頭前放,然后連起來 } T _data;//數(shù)據(jù)域 QueueItem *_next;//指向下一個(gè)節(jié)點(diǎn)的指針域 static QueueItem *_itemPool;//指向?qū)ο蟪氐钠鹗嫉刂?,因?yàn)樗械?QueueItem都放在一個(gè)對(duì)象池里面 static const int POOL_ITEM_SIZE = 100000;//開辟的對(duì)象池的節(jié)點(diǎn)的個(gè)數(shù),靜態(tài)常量可以直接在類體初始化 }; QueueItem *_front;//指向頭節(jié)點(diǎn) QueueItem *_rear;//指向隊(duì)尾 即鏈表的最后一個(gè)元素 }; template<typename T>//在類外定義靜態(tài)成員變量 typename Queue<T>::QueueItem *Queue<T>::QueueItem::_itemPool = nullptr; //typename告訴編譯器后邊的嵌套類作用域下的名字是類型,放心使用吧 int main() { Queue<int> que; for (int i = 0; i < 1000000; ++i) { que.push(i);//QueueItem(i) que.pop();//QueueItem } cout << que.empty() << endl; return 0; }
可以把指針改為智能指針,出作用域,對(duì)象池自動(dòng)釋放
到此這篇關(guān)于深入理解C++中的new和delete并實(shí)現(xiàn)對(duì)象池的文章就介紹到這了,更多相關(guān)C++對(duì)象池內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++ map 根據(jù)value找key的實(shí)現(xiàn)
今天小編就為大家分享一篇C++ map 根據(jù)value找key的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-12-12C++簡(jiǎn)易版Tensor實(shí)現(xiàn)方法詳解
這篇文章主要介紹了C++簡(jiǎn)易版Tensor的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值2022-08-08C++中的四個(gè)默認(rèn)成員函數(shù)與運(yùn)算符重載詳解
這篇文章主要給大家介紹了關(guān)于C++中四個(gè)默認(rèn)成員函數(shù)與運(yùn)算符重載的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來跟著小編一起學(xué)習(xí)學(xué)習(xí)吧。2017-08-08Qt編寫地圖實(shí)現(xiàn)實(shí)時(shí)動(dòng)態(tài)軌跡效果
實(shí)時(shí)動(dòng)態(tài)軌跡主要是需要在地圖上動(dòng)態(tài)顯示GPS的運(yùn)動(dòng)軌跡,也是編寫地圖時(shí)一個(gè)重要的功能。本文將利用Qt實(shí)現(xiàn)這一功能,需要的可以參考一下2022-02-02C語言實(shí)現(xiàn)動(dòng)態(tài)版通訊錄的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用C語言實(shí)現(xiàn)一個(gè)簡(jiǎn)單的動(dòng)態(tài)版通訊錄,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C語言有一定幫助,需要的可以參考一下2022-08-08VisualStudio 使用Visual Leak Detector檢查內(nèi)存泄漏
這篇文章主要介紹了VisualStudio 使用Visual Leak Detector檢查內(nèi)存泄漏的相關(guān)資料,需要的朋友可以參考下2015-07-07詳解C語言 三大循環(huán) 四大跳轉(zhuǎn) 和判斷語句
這篇文章主要介紹了詳解C語言 三大循環(huán) 四大跳轉(zhuǎn) 和判斷語句的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07