深入理解C++中的new和delete并實現(xiàn)對象池
深入理解new和delete
new和delete稱作運算符

我們轉反匯編看看

這2個運算符本質也是相應的運算符的重載的調用
malloc和new的區(qū)別?
1.malloc按字節(jié)開辟內存的;new開辟內存時需要指定類型 new int[10]
所以malloc開辟內存返回的都是void*
而new相當于運算符的重載函數(shù) operator new ->返回值自動轉成指定的類指針 int*
2.malloc只負責開辟空間,new不僅僅有malloc的功能,可以進行數(shù)據(jù)的初始化
new int(20);//初始化20 new int[20]();//開辟數(shù)組是不支持初始化值的,但是支持寫個空括號,表示給每個元素初始化為0 ,相當于每個元素調用int()成為0
3.malloc開辟內存失敗返回nullptr指針;new拋出的是bad_alloc類型的異常
(也就是說,new運算符開辟內存失敗,要把它的代碼擴在try catch里面,是不能通過返回值和空指針比較的。)
try//可能發(fā)生錯誤的代碼放在try里面
{
int *p = new int;
delete []p;
int *q = new int[10];
delete q;
}
catch (const bad_alloc &err)//捕獲相應類型的異常
{
cerr << err.what() << endl;//打印錯誤
}

free和delete的區(qū)別?
delete p: 調用析構函數(shù),然后再free( p),相當于包含了free
如果delete的是普通的指針,那么delete (int*)p和free( p)是沒有區(qū)別的
因為對于整型指針來說,沒有析構函數(shù),只剩下內存的釋放
new -> 對operator new重載函數(shù)的調用 delete -> 對operator delete重載函數(shù)的調用
把new和delete的重載函數(shù)定義在全局的地方,這樣我們整個項目工程中只有涉及到new和delete的地方都會調用到我們全局重寫的new,delete的重載函數(shù)。
//先調用operator new開辟內存空間、然后調用對象的構造函數(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; 先調用p指向對象的析構函數(shù)、再調用operator delete釋放內存空間
void operator delete(void *ptr)
{
cout << "operator delete addr:" << ptr << endl;
free(ptr);
}


new和delete從內存管理的角度上來說和malloc和free沒有什么區(qū)別
除非就是內存開辟失敗,返回不一樣
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++中,如何設計一個程序檢測內存泄漏問題?
內存泄漏就是new操作沒有對應的delete,我們可以在全局重寫上面這些函數(shù),在new操作里面用映射表記錄都有哪些內存被開辟過,delete的時候把相應的內存資源刪除掉,new和delete都有對應關系
如果整個系統(tǒng)運行完了,我們發(fā)現(xiàn),映射表記錄的一些內存還沒有被釋放,就存在內存泄漏了! 我們用new和delete接管整個應用的所有內存管理 ,對內存的開辟和釋放都記錄
也可以通過編譯器既定的宏和API接口,把函數(shù)調用堆棧打印出來,到底在哪個源代碼的哪一頁的哪一行做了new操作沒有delete
new和delete能混用嗎?
C++為什么區(qū)分單個元素和數(shù)組的內存分配和釋放呢?
下面這樣操作是否可以???

其實現(xiàn)在對于整型來說,沒有所謂的構造函數(shù)和析構函數(shù)可言,所以這樣的代碼就只剩下malloc和free的功能,所以底層調用的就是malloc和free

所以,它們現(xiàn)在混用是沒有問題的?。?!
那什么時候我們才需要考慮這些問題呢?
class Test
{
public:
Test(int data = 10) { cout << "Test()" << endl; }
~Test() { cout << "~Test()" << endl; }
private:
int ma;
};


在這里面,我們能不能混用呢?


出現(xiàn)錯誤了。
此時new和delete不能進行混用了!



在這里,new和delete可以混用嗎?


運行出錯了。
我們最好是這樣配對使用:
new delete new[] delete[]
對于普通的編譯器內置類型
new/delete[]
new[]/delete
這樣混用是可以的!
因為只涉及內存的開辟和釋放,底層調用的就是malloc和free
但是,如果是對象,就不能混用了。

一個Test對象是4個字節(jié)。
每一個Test對象有1個整型的成員變量。

new的時候,分配了5個Test對象,但是不只是開辟了20個字節(jié)哦!
delete[]p2的時候先調用Test對象的析構函數(shù),析構函數(shù)有this指針,this指針區(qū)分析構的對象,this指針把正確的對象的地址傳到析構函數(shù)。現(xiàn)在加了[]表示有好幾個對象,有一個數(shù)組,里面的每個對象都要析構,但是它是怎么知道是有5個對象呢???
所以,實際上,new Test[5]是開辟了如圖式的內存:
多開辟了4個字節(jié),存儲對象的個數(shù)。
用戶在寫new Test[5]時,這個5是要被記錄下來的。
而且,new操作完了之后,給以后返回的p2指針指向的地址是0x104這個地址!即數(shù)組首元素的地址。并不是真真正正底層開辟的0x100這個地址,因為那個是不需要讓用戶知道的,用戶只需要知道這個指針指向的是第一個元素對象的地址。

當我們去delete[]p2的時候,它一看這個[]就知道釋放的是一個對象數(shù)組,那么就要從p2(0x104)上移4個字節(jié),去取對象的個數(shù),知道是5個對象了(一個對象是4字節(jié)),然后把ox104下的內存平均分成5份,每一份內存的起始地址就是對象的起始地址,然后傳給對象的析構函數(shù),就可以進行對象的析構了。然后進行內存的釋放,operator delete(p2-4),從0x100開始釋放?。?!


這個代碼錯誤在:實際上開辟的內存空間大小是20+4=24字節(jié),開辟內存是從0028開辟的,因為它有析構函數(shù),所以在底層給數(shù)組開辟內存時多開辟了4個字節(jié)來存儲開辟的對象的個數(shù),但是用戶返回的是02c,比028剛好多了4個字節(jié),也就是給用戶返回的是真真正正對象的起始地址。
delete p2;它就認為p2只是指向1個對象,因為沒有使用delete[],所以它就只是把Test[0]這個對象析構了而已,然后直接free(p2),從第一個對象的地址(02c)開始free,而底層內存是從028開始開辟的。
我們換成delete[]p2,來運行看看

從指針-4開始free釋放內存的操作

這個代碼的出錯在:只是new出來1個對象,在0x104開辟的,p1也是指向了0x104,但是在delete[]的時候,認為是指向的是對象數(shù)組,因為還有析構函數(shù),于是它就從0x104上移4個字節(jié)去取開辟對象的個數(shù),

這就出現(xiàn)了問題了。
關鍵是它free的時候,執(zhí)行的是free(0x104-4)
但是new的時候并不是從0x100開始開辟內存的。
自定義的類類型,有析構函數(shù),為了調用正確的析構函數(shù),那么開辟對象數(shù)組的時候,會多開辟4個字節(jié),記錄對象的個數(shù)
對象池代碼應用
對象池的實現(xiàn)是靜態(tài)鏈表,在堆上開辟的。



#include <iostream>
using namespace std;
template<typename T>
class Queue
{
public:
Queue()//構造函數(shù) 0構造(默認構造)
{
_front = _rear = new QueueItem();
}
~Queue()//析構函數(shù)
{
QueueItem *cur = _front;//指向頭結點
while (cur != nullptr)
{
_front = _front->_next;
delete cur;
cur = _front;
}
}
void push(const T &val)//入隊操作
{
QueueItem *item = new QueueItem(val);//malloc
_rear->_next = item;
_rear = item;
}
void pop()//出隊操作 隊頭出 頭刪法
{
if (empty())
return;
QueueItem *first = _front->_next;
_front->_next = first->_next;
if (_front->_next == nullptr)//隊列原本只有1個有效元素節(jié)點
{
_rear = _front;
}
delete first;//free
}
T front()const//獲取首元素的值
{
return _front->_next->_data;
}
bool empty()const { return _front == _rear; }//判空 鏈式隊列
private:
//產(chǎn)生一個QueueItem的對象池(10000個QueueItem節(jié)點)
struct QueueItem//節(jié)點類型,鏈式隊列,帶頭節(jié)點的單鏈表
{
QueueItem(T data = T()) :_data(data), _next(nullptr) {}//構造函數(shù)
//給QueueItem提供自定義內存管理
void* operator new(size_t size)
{
if (_itemPool == nullptr)//如果對象池滿了,對象池的指針就指向空了,然后現(xiàn)在進入,再開辟一個對象池
{
_itemPool = (QueueItem*)new char[POOL_ITEM_SIZE*sizeof(QueueItem)];//開辟池
QueueItem *p = _itemPool;
for (; p < _itemPool + POOL_ITEM_SIZE - 1; ++p)//連在一個鏈表上
{
p->_next = p + 1;//因為節(jié)點內存是連續(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;//指向下一個節(jié)點的指針域
static QueueItem *_itemPool;//指向對象池的起始地址,因為所有的 QueueItem都放在一個對象池里面
static const int POOL_ITEM_SIZE = 100000;//開辟的對象池的節(jié)點的個數(shù),靜態(tài)常量可以直接在類體初始化
};
QueueItem *_front;//指向頭節(jié)點
QueueItem *_rear;//指向隊尾 即鏈表的最后一個元素
};
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;
}
可以把指針改為智能指針,出作用域,對象池自動釋放
到此這篇關于深入理解C++中的new和delete并實現(xiàn)對象池的文章就介紹到這了,更多相關C++對象池內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++ map 根據(jù)value找key的實現(xiàn)
今天小編就為大家分享一篇C++ map 根據(jù)value找key的實現(xiàn),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-12-12
VisualStudio 使用Visual Leak Detector檢查內存泄漏
這篇文章主要介紹了VisualStudio 使用Visual Leak Detector檢查內存泄漏的相關資料,需要的朋友可以參考下2015-07-07

