您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關C++中new和delete怎么用的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
new和delete稱作運算符
我們轉反匯編看看
這2個運算符本質也是相應的運算符的重載的調用
malloc和new的區別?
1.malloc按字節開辟內存的;new開辟內存時需要指定類型 new int[10]
所以malloc開辟內存返回的都是void*
而new相當于運算符的重載函數 operator new ->返回值自動轉成指定的類指針 int*
2.malloc只負責開辟空間,new不僅僅有malloc的功能,可以進行數據的初始化
new int(20);//初始化20 new int[20]();//開辟數組是不支持初始化值的,但是支持寫個空括號,表示給每個元素初始化為0 ,相當于每個元素調用int()成為0
3.malloc開辟內存失敗返回nullptr指針;new拋出的是bad_alloc類型的異常
(也就是說,new運算符開辟內存失敗,要把它的代碼擴在try catch里面,是不能通過返回值和空指針比較的。)
try//可能發生錯誤的代碼放在try里面 { int *p = new int; delete []p; int *q = new int[10]; delete q; } catch (const bad_alloc &err)//捕獲相應類型的異常 { cerr << err.what() << endl;//打印錯誤 }
delete p: 調用析構函數,然后再free( p),相當于包含了free
如果delete的是普通的指針,那么delete (int*)p和free( p)是沒有區別的
因為對于整型指針來說,沒有析構函數,只剩下內存的釋放
new -> 對operator new重載函數的調用 delete -> 對operator delete重載函數的調用
把new和delete的重載函數定義在全局的地方,這樣我們整個項目工程中只有涉及到new和delete的地方都會調用到我們全局重寫的new,delete的重載函數。
//先調用operator new開辟內存空間、然后調用對象的構造函數(初始化) 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指向對象的析構函數、再調用operator delete釋放內存空間 void operator delete(void *ptr) { cout << "operator delete addr:" << ptr << endl; free(ptr); }
new和delete從內存管理的角度上來說和malloc和free沒有什么區別
除非就是內存開辟失敗,返回不一樣
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,我們可以在全局重寫上面這些函數,在new操作里面用映射表記錄都有哪些內存被開辟過,delete的時候把相應的內存資源刪除掉,new和delete都有對應關系
如果整個系統運行完了,我們發現,映射表記錄的一些內存還沒有被釋放,就存在內存泄漏了! 我們用new和delete接管整個應用的所有內存管理 ,對內存的開辟和釋放都記錄
也可以通過編譯器既定的宏和API接口,把函數調用堆棧打印出來,到底在哪個源代碼的哪一頁的哪一行做了new操作沒有delete
C++為什么區分單個元素和數組的內存分配和釋放呢?
下面這樣操作是否可以???
其實現在對于整型來說,沒有所謂的構造函數和析構函數可言,所以這樣的代碼就只剩下malloc和free的功能,所以底層調用的就是malloc和free
所以,它們現在混用是沒有問題的!!!
那什么時候我們才需要考慮這些問題呢?
class Test { public: Test(int data = 10) { cout << "Test()" << endl; } ~Test() { cout << "~Test()" << endl; } private: int ma; };
在這里面,我們能不能混用呢?
出現錯誤了。
此時new和delete不能進行混用了!
在這里,new和delete可以混用嗎?
運行出錯了。
我們最好是這樣配對使用:
new delete new[] delete[]
對于普通的編譯器內置類型
new/delete[]
new[]/delete
這樣混用是可以的!
因為只涉及內存的開辟和釋放,底層調用的就是malloc和free
但是,如果是對象,就不能混用了。
一個Test對象是4個字節。
每一個Test對象有1個整型的成員變量。
new的時候,分配了5個Test對象,但是不只是開辟了20個字節哦!
delete[]p2的時候先調用Test對象的析構函數,析構函數有this指針,this指針區分析構的對象,this指針把正確的對象的地址傳到析構函數。現在加了[]表示有好幾個對象,有一個數組,里面的每個對象都要析構,但是它是怎么知道是有5個對象呢???
所以,實際上,new Test[5]是開辟了如圖式的內存:
多開辟了4個字節,存儲對象的個數。
用戶在寫new Test[5]時,這個5是要被記錄下來的。
而且,new操作完了之后,給以后返回的p2指針指向的地址是0x104這個地址!即數組首元素的地址。并不是真真正正底層開辟的0x100這個地址,因為那個是不需要讓用戶知道的,用戶只需要知道這個指針指向的是第一個元素對象的地址。
當我們去delete[]p2的時候,它一看這個[]就知道釋放的是一個對象數組,那么就要從p2(0x104)上移4個字節,去取對象的個數,知道是5個對象了(一個對象是4字節),然后把ox104下的內存平均分成5份,每一份內存的起始地址就是對象的起始地址,然后傳給對象的析構函數,就可以進行對象的析構了。然后進行內存的釋放,operator delete(p2-4),從0x100開始釋放!!!
這個代碼錯誤在:實際上開辟的內存空間大小是20+4=24字節,開辟內存是從0028開辟的,因為它有析構函數,所以在底層給數組開辟內存時多開辟了4個字節來存儲開辟的對象的個數,但是用戶返回的是02c,比028剛好多了4個字節,也就是給用戶返回的是真真正正對象的起始地址。
delete p2;它就認為p2只是指向1個對象,因為沒有使用delete[],所以它就只是把Test[0]這個對象析構了而已,然后直接free(p2),從第一個對象的地址(02c)開始free,而底層內存是從028開始開辟的。
我們換成delete[]p2,來運行看看
從指針-4開始free釋放內存的操作
這個代碼的出錯在:只是new出來1個對象,在0x104開辟的,p1也是指向了0x104,但是在delete[]的時候,認為是指向的是對象數組,因為還有析構函數,于是它就從0x104上移4個字節去取開辟對象的個數,
這就出現了問題了。
關鍵是它free的時候,執行的是free(0x104-4)
但是new的時候并不是從0x100開始開辟內存的。
自定義的類類型,有析構函數,為了調用正確的析構函數,那么開辟對象數組的時候,會多開辟4個字節,記錄對象的個數
對象池的實現是靜態鏈表,在堆上開辟的。
#include <iostream> using namespace std; template<typename T> class Queue { public: Queue()//構造函數 0構造(默認構造) { _front = _rear = new QueueItem(); } ~Queue()//析構函數 { 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個有效元素節點 { _rear = _front; } delete first;//free } T front()const//獲取首元素的值 { return _front->_next->_data; } bool empty()const { return _front == _rear; }//判空 鏈式隊列 private: //產生一個QueueItem的對象池(10000個QueueItem節點) struct QueueItem//節點類型,鏈式隊列,帶頭節點的單鏈表 { QueueItem(T data = T()) :_data(data), _next(nullptr) {}//構造函數 //給QueueItem提供自定義內存管理 void* operator new(size_t size) { if (_itemPool == nullptr)//如果對象池滿了,對象池的指針就指向空了,然后現在進入,再開辟一個對象池 { _itemPool = (QueueItem*)new char[POOL_ITEM_SIZE*sizeof(QueueItem)];//開辟池 QueueItem *p = _itemPool; for (; p < _itemPool + POOL_ITEM_SIZE - 1; ++p)//連在一個鏈表上 { p->_next = p + 1;//因為節點內存是連續開辟的 可以用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;//數據域 QueueItem *_next;//指向下一個節點的指針域 static QueueItem *_itemPool;//指向對象池的起始地址,因為所有的 QueueItem都放在一個對象池里面 static const int POOL_ITEM_SIZE = 100000;//開辟的對象池的節點的個數,靜態常量可以直接在類體初始化 }; QueueItem *_front;//指向頭節點 QueueItem *_rear;//指向隊尾 即鏈表的最后一個元素 }; template<typename T>//在類外定義靜態成員變量 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怎么用”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。