C/C++深入講解內(nèi)存管理
C/C++內(nèi)存分布
??首先我們來(lái)看一看以下代碼中變量在內(nèi)存中的存儲(chǔ)位置。
c/c++內(nèi)存分配圖:
1.棧又叫做堆棧,存儲(chǔ)非靜態(tài)局部變量/函數(shù)參數(shù)/返回值等等,棧是向下增長(zhǎng)的。
2.內(nèi)存映射段是高效的I/O映射方式,用于裝載一個(gè)共享的動(dòng)態(tài)內(nèi)存庫(kù)。用戶可使用系統(tǒng)接口創(chuàng)建共享內(nèi)存,做進(jìn)程間通信。
3.堆用于程序運(yùn)行時(shí)動(dòng)態(tài)內(nèi)存分配,堆上可以向上增長(zhǎng)的。
4.數(shù)據(jù)段 - 用于存儲(chǔ)全局?jǐn)?shù)據(jù)和靜態(tài)數(shù)據(jù)。
5.代碼段 - 可執(zhí)行的代碼/只讀常量。
C語(yǔ)言中的動(dòng)態(tài)內(nèi)存管理
malloc/calloc/realloc/free
int main() { int* p1 = (int*)malloc(sizeof(int)); int* p2 = (int*)calloc(4, sizeof(int)); int* p3 = (int*)realloc(p2, sizeof(int) * 10); free(p1); free(p2); free(p3); return 0; }
malloc/calloc/realloc的區(qū)別?
malloc - 堆上動(dòng)態(tài)開(kāi)辟空間
realloc - 堆上動(dòng)態(tài)開(kāi)辟空間 + 初始化為0 (相當(dāng)于malloc + memset)
calloc - 針對(duì)已經(jīng)有的空間進(jìn)行擴(kuò)容 (原地?cái)U(kuò)容或異地?cái)U(kuò)容)
C++的內(nèi)存管理
??C語(yǔ)言的內(nèi)存管理方式在c++中可以繼續(xù)使用,但是有些地方使用起來(lái)就比較麻煩了。因此c++提供了自己的內(nèi)存管理方式:通過(guò)new和delete操作符進(jìn)行動(dòng)態(tài)內(nèi)存管理。 new和delete是運(yùn)算符,不是函數(shù),因此執(zhí)行效率高。
new和delete操作內(nèi)置類(lèi)型::
int main() { // new/delete和malloc/free 針對(duì)內(nèi)置類(lèi)型沒(méi)有任何差別,只是用法不同 //動(dòng)態(tài)申請(qǐng)一個(gè)int類(lèi)型的空間 int* p1 = new int; delete p1; //動(dòng)態(tài)申請(qǐng)一個(gè)int類(lèi)型的空間并初始化為10 int* p2 = new int(10); delete p2; //動(dòng)態(tài)申請(qǐng)10個(gè)int類(lèi)型的空間 int* p3 = new int[10]; delete[] p3; return 0; }
注意:申請(qǐng)和釋放單個(gè)元素的空間,使用new和delete操作符,申請(qǐng)和釋放連續(xù)的空間,使用new[]和delete[]。
new和delete操作自定義類(lèi)型::
注意: 在申請(qǐng)自定義類(lèi)型的空間時(shí),new會(huì)調(diào)用構(gòu)造函數(shù),delete會(huì)調(diào)用析構(gòu)函數(shù),而malloc和free不會(huì)。
??對(duì)于以上總結(jié)一下:
1.c++中如果是申請(qǐng)內(nèi)置類(lèi)型對(duì)象或者數(shù)組,malloc和new沒(méi)有什么區(qū)別。
2.如果是自定義類(lèi)型,那么區(qū)別很大,new和delete是開(kāi)空間 + 初始化,析構(gòu)清理 + 釋放空間,malloc和free僅僅是開(kāi)空間 + 釋放空間。
3.建議在c++中,無(wú)論是自定義類(lèi)型還是內(nèi)置類(lèi)型的申請(qǐng)和釋放,盡量使用new和delete。
operator new與operator delete函數(shù)
new和delete是用戶進(jìn)行動(dòng)態(tài)內(nèi)存申請(qǐng)和釋放的操作符,operator new和 operator delete是系統(tǒng)提供的全局函數(shù),new在底層調(diào)用operator new 全局函數(shù)來(lái)申請(qǐng)空間,delete在底層提供operator delete全局函數(shù)來(lái)釋放空間。
如下是c++官方對(duì)于這兩個(gè)函數(shù)的描述:
operator new和operator delete的實(shí)現(xiàn)代碼:
/* operator new:該函數(shù)實(shí)際通過(guò)malloc來(lái)申請(qǐng)空間,當(dāng)malloc申請(qǐng)空間成功時(shí)直接返回;申請(qǐng)空間失敗, 嘗試執(zhí)行空間不足應(yīng)對(duì)措施,如果改應(yīng)對(duì)措施用戶設(shè)置了,則繼續(xù)申請(qǐng),否則拋異常。 */ void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) { // try to allocate size bytes void* p; while ((p = malloc(size)) == 0) if (_callnewh(size) == 0) { // report no memory // 如果申請(qǐng)內(nèi)存失敗了,這里會(huì)拋出bad_alloc 類(lèi)型異常 static const std::bad_alloc nomem; _RAISE(nomem); } return (p); } /* operator delete: 該函數(shù)最終是通過(guò)free來(lái)釋放空間的 */ void operator delete(void* pUserData) { _CrtMemBlockHeader* pHead; RTCCALLBACK(_RTC_Free_hook, (pUserData, 0)); if (pUserData == NULL) return; _mlock(_HEAP_LOCK); /* block other threads */ __TRY /* get a pointer to memory block header */ pHead = pHdr(pUserData); /* verify block type */ _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse)); _free_dbg(pUserData, pHead->nBlockUse); __FINALLY _munlock(_HEAP_LOCK); /* release other threads */ __END_TRY_FINALLY return; } /* free的實(shí)現(xiàn) */ #define free(p) _free_dbg(p, _NORMAL_BLOCK)
通過(guò)上訴兩個(gè)全局函數(shù)的實(shí)現(xiàn)代碼可以看出,operator new實(shí)際上是通過(guò)malloc來(lái)申請(qǐng)空間的,如果malloc申請(qǐng)空間成功就直接返回,否則執(zhí)行用戶提供的空間不足的應(yīng)對(duì),如果用戶提供該措施就繼續(xù)申請(qǐng),否則就拋異常。operator delete最終是通過(guò)free來(lái)釋放空間的。
operator new的使用案例:
struct ListNode { ListNode(int data = 0) :_next(nullptr) ,_prev(nullptr) ,_data(data) {} ListNode* _next; ListNode* _prev; int _data; }; //operator new的用法跟malloc和free是一樣的,都是在堆上申請(qǐng)空間 //只是申請(qǐng)空間失敗后的處理方式不一樣,malloc失敗返回NULL,operator new失敗以后拋異常 int main() { //C語(yǔ)言 ListNode* p1 = (ListNode*)malloc(sizeof(ListNode)); free(p1); //c++ ListNode* p2 = (ListNode*)operator new(sizeof(ListNode)); operator delete(p2); int* p3 = (int*)malloc(100000000000000000); if (p3 == NULL) { cout << "malloc fail" << endl; } try { int* p4 = (int*)operator new(100000000000000000); } //開(kāi)辟空間失敗,捕獲異常信息 catch (exception& e) { cout << e.what() << endl; } return 0; }
operator delete的使用案例:
class A { public: A(int a = 0) { cout << "A()" << this << endl; } ~A() { cout << "~A()" << this << endl; } private: int _a; }; int main() { //c語(yǔ)言 -> A* p = (A*)malloc(sizeof(A)); //等價(jià)于直接用 A* p = new A; A* p = (A*)operator new(sizeof(A)); new(p)A; // new(p)A(2); 定位new,placement-new,顯示調(diào)用構(gòu)造函數(shù)初始化這塊空間對(duì)象 //等價(jià)于 delete p p->~A(); //析構(gòu)函數(shù)可以顯示調(diào)用 operator delete(p); return 0; }
operator new與operator delete的類(lèi)專屬重載
內(nèi)存池:內(nèi)存池的主要作用是提高效率。通過(guò)一次性申請(qǐng)比較大的空間,來(lái)避免小空間內(nèi)存的頻繁申請(qǐng)和釋放,每次需要為對(duì)象分配內(nèi)存空間時(shí),在已經(jīng)申請(qǐng)好的大的空間內(nèi)分配??臻e區(qū)被按照對(duì)象大小劃分為若干塊,每個(gè)塊之間通過(guò)鏈表連接起來(lái)。
??以下代碼演示了,針對(duì)鏈表的節(jié)點(diǎn)ListNode通過(guò)重載類(lèi)專屬 operator new / operator delete ,實(shí)現(xiàn)鏈表節(jié)點(diǎn)使用內(nèi)存池申請(qǐng)和釋放內(nèi)存,提高效率。
struct ListNode { void* operator new(size_t n) { void* p = nullptr; p = allocator<ListNode>().allocate(1); cout << "memory pool allocate" << endl; return p; } void operator delete(void* p) { allocator<ListNode>().deallocate((ListNode*)p, 1); cout << "memory pool deallocate" << endl; } ListNode* _next; ListNode* _prev; int _data; }; class List { public: List() { _head = new ListNode(); _head->_next = _head; _head->_prev = _head; } ~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 h; return 0; }
new和delete的實(shí)現(xiàn)原理
內(nèi)置類(lèi)型:
若申請(qǐng)的是內(nèi)置類(lèi)型的空間,new和malloc,delete和free基本相似。new/delete 申請(qǐng)和釋放的是單個(gè)元素的空間,new[]和delete[]申請(qǐng)的是連續(xù)空間。new在空間申請(qǐng)失敗時(shí)會(huì)拋異常,malloc申請(qǐng)空間失敗則返回NULL。
自定義類(lèi)型:
new:
- 調(diào)用operator new函數(shù)申請(qǐng)空間。
- 在申請(qǐng)的空間調(diào)用構(gòu)造函數(shù),完成對(duì)象的構(gòu)造。
delete:
- 在空間上調(diào)用析構(gòu)函數(shù),完成對(duì)象中資源清理的工作。
- 調(diào)用operator delete函數(shù)釋放對(duì)象的空間。
new arr[N]:
- 調(diào)用operator new[]函數(shù),在operator new[]中實(shí)際調(diào)用operator new函數(shù)完成N個(gè)對(duì)象空間的申請(qǐng)。
- 在申請(qǐng)的空間上執(zhí)行N次構(gòu)造函數(shù)。
delete []:
- 在釋放的空間上執(zhí)行N次析構(gòu)函數(shù),完成N個(gè)對(duì)象中資源的清理。
- 調(diào)用operator delete[]釋放空間,在operator delete[]中調(diào)用operator delete來(lái)釋放空間。
定位new表達(dá)式(placement-new)
??一般來(lái)說(shuō),使用new申請(qǐng)空間時(shí),是從系統(tǒng)堆上分配空間。申請(qǐng)所得的空間位置是根據(jù)當(dāng)時(shí)的內(nèi)存使用的實(shí)際情況來(lái)決定。但在某些特殊情況下,可能需要在已分配的特定內(nèi)存創(chuàng)建對(duì)象,這就是所說(shuō)的定位new(placement - new)。
??默認(rèn)情況下,如果new不能分配所需要的內(nèi)存空間,那么它會(huì)拋出一個(gè)類(lèi)型為bad_alloc的異常。我們可以改變使用new的方式來(lái)阻止其拋出異常:
//如果申請(qǐng)失敗,new會(huì)返回一個(gè)空指針(NULL) int* p1 = new int; //如果分配空間失敗,new會(huì)拋出std::bad_alloc int* p2 = new (nothrow) int; //如果分配空間失敗,new返回一個(gè)空指針
這種形式的new我們就稱為定位new。
定位new表達(dá)式允許我們向new傳遞額外的參數(shù)。如上的nothrow。將nothrow傳給new,即不能拋出異常。如果這種形式的new不能分配所需內(nèi)存,那么它會(huì)返回一個(gè)空指針。
malloc/free和new/delete的區(qū)別
1.相同點(diǎn):它們都是堆上申請(qǐng)空間并且手動(dòng)釋放。
2.malloc和free是函數(shù),new和delete是操作符。
3.malloc申請(qǐng)的空間不初始化,new申請(qǐng)的空間可以初始化。
4.malloc申請(qǐng)空間時(shí)需要計(jì)算所需空間的大小并且傳遞,new申請(qǐng)空間時(shí)只需在其后加上空間類(lèi)型即可。
5.malloc的返回值為void*,在使用時(shí)必須強(qiáng)轉(zhuǎn),new不需要,因?yàn)閚ew后跟的是空間的類(lèi)型。
6.malloc申請(qǐng)空間失敗時(shí)返回NULL,使用時(shí)需要判空,而new不需要判空,但是new需要捕獲異常。
7.申請(qǐng)自定義類(lèi)型對(duì)象時(shí),malloc/free只會(huì)開(kāi)辟空間,不會(huì)調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)處理空間,但new在申請(qǐng)空間后會(huì)調(diào)用構(gòu)造函數(shù)完成對(duì)象的初始化,delete在釋放空間前后調(diào)用析構(gòu)函數(shù)完成空間中資源的清理。
內(nèi)存泄漏及其危害
內(nèi)存泄漏(Memory Leak) 是指程序中已動(dòng)態(tài)分配的堆內(nèi)存由于某種原因?qū)е鲁绦蛭瘁尫呕蛘邿o(wú)法釋放,造成系統(tǒng)內(nèi)存的浪費(fèi),導(dǎo)致程序運(yùn)行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果。內(nèi)存泄漏并不是指內(nèi)存在物理上的消失,而是應(yīng)用程序分配某段內(nèi)存后,因?yàn)殄e(cuò)誤設(shè)計(jì),失去了對(duì)該段內(nèi)存的控制。
??內(nèi)存泄漏的危害:長(zhǎng)期運(yùn)行的程序出現(xiàn)內(nèi)存泄漏,影響很大,如操作系統(tǒng)、后臺(tái)服務(wù)等等,出現(xiàn)內(nèi)存泄漏會(huì)導(dǎo)致響應(yīng)越來(lái)越慢,最終卡死。
??常見(jiàn)的導(dǎo)致內(nèi)存泄漏的原因:
??c/c++程序中我們比較關(guān)心的兩種內(nèi)存泄漏:
1.堆內(nèi)存泄漏(Heap Leak)
堆內(nèi)存指程序執(zhí)行中需要通過(guò)malloc、realloc、realloc、new等從堆中分配內(nèi)存,用完后需通過(guò)調(diào)用free或者delete釋放。若程序的設(shè)計(jì)錯(cuò)誤導(dǎo)致這一部分內(nèi)存沒(méi)有被釋放掉,那么之后這塊空間將無(wú)法繼續(xù)使用,就會(huì)發(fā)生堆內(nèi)存泄漏。
2.系統(tǒng)資源泄漏
指程序使用系統(tǒng)分配的資源,比方套接字、文件描述符、管道等沒(méi)有使用對(duì)于的函數(shù)釋放掉,導(dǎo)致系統(tǒng)資源的浪費(fèi),嚴(yán)重則可導(dǎo)致系統(tǒng)的效能減少,系統(tǒng)執(zhí)行不穩(wěn)等。
??如何避免內(nèi)存泄漏:
1.工程前期設(shè)計(jì)規(guī)范,養(yǎng)成良好的編碼習(xí)慣。
2.提前預(yù)防。如智能指針等。
3.內(nèi)存泄漏工具的使用。(很多工具不靠譜,且收費(fèi)昂貴)
??內(nèi)存泄漏檢測(cè)工具:(來(lái)自百度百科)
到此這篇關(guān)于C/C++深入講解內(nèi)存管理的文章就介紹到這了,更多相關(guān)C++內(nèi)存管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言超詳細(xì)講解棧的實(shí)現(xiàn)及代碼
棧(stack)又名堆棧,它是一種運(yùn)算受限的線性表。限定僅在表尾進(jìn)行插入和刪除操作的線性表。這一端被稱為棧頂,相對(duì)地,把另一端稱為棧底。向一個(gè)棧插入新元素又稱作進(jìn)棧、入?;驂簵?,它是把新元素放到棧頂元素的上面,使之成為新的棧頂元素2022-04-04C++實(shí)現(xiàn)LeetCode(114.將二叉樹(shù)展開(kāi)成鏈表)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(114.將二叉樹(shù)展開(kāi)成鏈表),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C++基礎(chǔ)入門(mén)教程(七):一些比較特別的基礎(chǔ)語(yǔ)法總結(jié)
這篇文章主要介紹了C++基礎(chǔ)入門(mén)教程(七):一些比較特別的基礎(chǔ)語(yǔ)法總結(jié),本文總結(jié)的都是一些特殊的語(yǔ)法,需要的朋友可以參考下2014-11-11深入探索C++中stack和queue的底層實(shí)現(xiàn)
這篇文章主要介紹了C++中的stack和dequeue的底層實(shí)現(xiàn),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09C++實(shí)現(xiàn)基于EASYX庫(kù)掃描線算法
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)基于EASYX庫(kù)掃描線算法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02