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