一文詳解C++中動態(tài)內(nèi)存管理
前言
在我們?nèi)粘懘a的過程中,我們對內(nèi)存空間的需求有時候在程序運行的時候才能知道,這時候我們就需要使用動態(tài)開辟內(nèi)存的方法。
1、C/C++程序的內(nèi)存開辟
首先我們先了解一下C/C++程序內(nèi)存分配的幾個區(qū)域:
int globalVar = 1; static int staticGlobalVar = 1; void Test() { static int staticVar = 1; int localVar = 1; int num1[10] = { 1, 2, 3, 4 }; char char2[] = "abcd"; const char* pChar3 = "abcd"; int* ptr1 = (int*)malloc(sizeof(int) * 4); int* ptr2 = (int*)calloc(4, sizeof(int)); int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4); free(ptr1); free(ptr3); }
- 1. 棧區(qū)(stack):在執(zhí)行函數(shù)時,函數(shù)內(nèi)局部變量的存儲單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時這些存儲單元自動被釋放。棧內(nèi)存分配運算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。 棧區(qū)主要存放運行函數(shù)而分配的局部變量、函數(shù)參數(shù)、返回數(shù)據(jù)、返回地址等。
- 2. 堆區(qū)(heap):一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時可能由OS回收 。分配方式類似于鏈表。
- 3. 數(shù)據(jù)段(靜態(tài)區(qū))(static)存放全局變量、靜態(tài)數(shù)據(jù)。程序結(jié)束后由系統(tǒng)釋放。
- 4. 代碼段:存放函數(shù)體(類成員函數(shù)和全局函數(shù))的二進制代碼。
這幅圖中,我們可以發(fā)現(xiàn)普通的局部變量是在棧上分配空間的,在棧區(qū)中創(chuàng)建的變量出了作用域去就會自動銷毀。但是被static修飾的變量是存放在數(shù)據(jù)段(靜態(tài)區(qū)),在數(shù)據(jù)段上創(chuàng)建的變量直到程序結(jié)束才銷毀,所以數(shù)據(jù)段上的數(shù)據(jù)生命周期變長了。
2.C語言中動態(tài)內(nèi)存管理方式:malloc/calloc/realloc/free
在C語言中,我們經(jīng)常會用到malloc,calloc和realloc來進行動態(tài)的開辟內(nèi)存;同時,C語言還提供了一個函數(shù)free,專門用來做動態(tài)內(nèi)存的釋放和回收。其中他們?nèi)齻€的區(qū)別也是我們需要特別所強調(diào)區(qū)別的。
2.1malloc、calloc、realloc區(qū)別?
malloc函數(shù)是向內(nèi)存申請一塊連續(xù)可用的空間,并返回指向這塊空間的指針。
calloc與malloc的區(qū)別只在于calloc會在返回地址之前把申請的空間的每個字節(jié)初始化為0。
realloc函數(shù)可以做到對動態(tài)開辟內(nèi)存大小的調(diào)整。
我們通過這三個函數(shù)的定義也可以進行功能的區(qū)分:
void Test () { int* p1 = (int*) malloc(sizeof(int)); free(p1); int* p2 = (int*)calloc(4, sizeof (int)); int* p3 = (int*)realloc(p2, sizeof(int)*10); free(p3 ); }
3.C++內(nèi)存管理方式
我們都知道,C++語言是兼容C語言的,因此C語言中內(nèi)存管理方式在C++中可以繼續(xù)使用。但是有些地方就無能為力了,并且使用起來也可能比較麻煩。因此,C++擁有自己的內(nèi)管管理方式:通過new和delete操作符進行動態(tài)內(nèi)存管理。
3.1 new/delete操作內(nèi)置類型
int main() { // 動態(tài)申請一個int類型的空間 int* ptr1 = new int; // 動態(tài)申請一個int類型的空間并初始化為10 int* ptr2 = new int(10); // 動態(tài)申請3個int類型的空間(數(shù)組) int* ptr3 = new int[3]; // 動態(tài)申請3個int類型的空間,初始化第一個空間值為1 int* ptr4 = new int[3]{ 1 }; delete ptr1; delete ptr2; delete[] ptr3; delete[] ptr4; return 0; }
我們首先通過畫圖分析進行剖析代碼:
我們在監(jiān)視窗口看看這3個變量
注意:申請和釋放單個元素的空間,使用new和delete操作符,申請和釋放連續(xù)的空間,使用new[]和delete[],要匹配起來使用。
3.2 new和delete操作自定義類型
class A { public: A(int a = 0) : _a(a) { cout << "A():" << this << endl; } ~A() { cout << "~A():" << this << endl; } private: int _a; }; int main() { A* p1 = (A*)malloc(sizeof(A)); A* p2 = new A(1); free(p1); delete p2; return 0; }
在這段代碼中,p1是我們使用malloc開辟的,p2是通過new來開辟的。我們編譯運行這段代碼。
發(fā)現(xiàn)輸出了這兩句,那這兩句是誰調(diào)用的呢?我們通過調(diào)試逐語句來分析這個過程
內(nèi)置類型區(qū)別
注意:在申請自定義類型的空間時,new會自動調(diào)用構(gòu)造函數(shù),delete時會調(diào)用析構(gòu)函數(shù),而malloc和free不會。
3.3new和malloc處理失敗
int main() { void* p0 = malloc(1024 * 1024 * 1024); cout << p0 << endl; //malloc失敗,返回空指針 void* p1 = malloc(1024 * 1024 * 1024); cout << p1 << endl; try { //new失敗,拋異常 void* p2 = new char[1024 * 1024 * 1024]; cout << p2 << endl; } catch (const exception& e) { cout << e.what() << endl; } return 0; }
我們能夠發(fā)現(xiàn),malloc失敗時會返回空指針,而new失敗時,會拋出異常。
4.operator new與operator delete函數(shù)
4.1 operator new與operator delete函數(shù)
C++標準庫還提供了operator new和operator delete函數(shù),但是這兩個函數(shù)并不是對new和delete的重載,operator new和operator delete是兩個庫函數(shù)。(這里C++大佬設(shè)計時這樣取名確實很容易混淆)
4.1.1 我們看看operator new庫里面的源碼
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 new的作用是封裝了malloc,如果malloc失敗,拋出異常。
4.1.2 operator delete庫里面的源碼
該函數(shù)最終是通過free來釋放空間的
//operator delete 源碼 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)
4.1.3 operator new和operator delete的價值(重點)
class A { public: A(int a = 0) : _a(a) { cout << "A():" << this << endl; } ~A() { cout << "~A():" << this << endl; } private: int _a; }; int main() { //跟malloc功能一樣,失敗以后拋出異常 A* ps1 = (A*)operator new(sizeof(A)); operator delete(ps1); A* ps2 = (A*)malloc(sizeof(A)); free(ps2); A* ps3 = new A; delete ps3; return 0; }
我們使用new的時候,new要開空間,要調(diào)用構(gòu)造函數(shù)。new可以轉(zhuǎn)換成call malloc,call 構(gòu)造函數(shù)。但是call malloc 一旦失敗,會返回空指針或者錯誤碼。在面向?qū)ο蟮恼Z言中更喜歡使用異常。而operator new相比較malloc的不同就在于如果一旦失敗會拋出異常,因此new的底層實現(xiàn)是調(diào)用operator new,operator new會調(diào)用malloc(如果失敗拋出異常),再調(diào)用構(gòu)造函數(shù)。
我們通過匯編看一下ps3
operator delete同理。
總結(jié):通過上述兩個全局函數(shù)的實現(xiàn)知道,operator new 實際也是通過malloc來申請空間,如果malloc申請空間成功就直接返回,否則執(zhí)行用戶提供的空間不足應(yīng)對措施,如果用戶提供該措施就繼續(xù)申請,否則就拋異常。operator delete 最終是通過free來釋放空間的。
4.2 重載operator new 與 operator delete(了解)
專屬的operator new技術(shù),提高效率。應(yīng)用:內(nèi)存池
class A { public: A(int a = 0) : _a(a) { cout << "A():" << this << endl; } // 專屬的operator new void* operator new(size_t n) { void* p = nullptr; p = allocator<A>().allocate(1); cout << "memory pool allocate" << endl; return p; } void operator delete(void* p) { allocator<A>().deallocate((A*)p, 1); cout << "memory pool deallocate" << endl; } ~A() { cout << "~A():" << this << endl; } private: int _a; }; int main() { int n = 0; cin >> n; for (int i = 0; i < n; ++i) { A* ps1 = new A; //operator new + A的構(gòu)造函數(shù) } return 0; }
注意:一般情況下不需要對 operator new 和 operator delete進行重載,除非在申請和釋放空間時候有某些特殊的需求。比如:在使用new和delete申請和釋放空間時,打印一些日志信息,可以簡單幫助用戶來檢測是否存在內(nèi)存泄漏。
5.new 和 delete 的實現(xiàn)原理
5.1 內(nèi)置類型
如果申請的是內(nèi)置類型的空間,new和malloc,delete和free基本類似,不同的地方是:new/delete申請和釋放的是單個元素的空間,new[]和delete[]申請的是連續(xù)空間,而且new在申請空間失敗時會拋異常,malloc會返回NULL。
5.2 自定義類型
5.2.1 new原理
- 1、調(diào)用operator new函數(shù)申請空間
- 2、再調(diào)用構(gòu)造函數(shù),完成對對象的構(gòu)造。
5.2.2 delete原理
- 1、先調(diào)用析構(gòu)函數(shù),完成對對象中資源的清理工作。
- 2、調(diào)用operator delete函數(shù)釋放對象的空間
5.2.3 new T[N]原理
- 1、先調(diào)用operator new[]函數(shù),在operator new[]中世紀調(diào)用operator new函數(shù)完成N個對象空間的申請
- 2、在申請的空間上執(zhí)行N次構(gòu)造函數(shù)
5.2.4 delete[]原理
- 1、在釋放的對象空間上執(zhí)行N次析構(gòu)函數(shù),完成對N個對象中資源的清理
- 2、調(diào)用operator delete[]釋放空間,實際在operator delete[]中調(diào)用operator delete來釋放空間。
6.malloc/free和new/delete的異同
6.1malloc/free和new/delete的共同點
都是從堆上申請空間,都需要用戶手動釋放空間。
6.2malloc/free和new/delete的不同點
- 1:malloc和free是函數(shù),new和delete是操作符
- 2:malloc申請的空間不會初始化,new可以初始化
- 3:malloc申請空間時,需要手動計算空間大小并傳遞,new只需在其后跟上空間的類型即可,如果是多個對象,[]中指定對象個數(shù)即可
- 4:malloc的返回值為void*, 在使用時必須強轉(zhuǎn),new不需要,因為new后跟的是空間的類型
- 5:malloc申請空間失敗時,返回的是NULL,因此使用時必須判空,new不需要,但是new需要捕獲異常
- 6:申請自定義類型對象時,malloc/free只會開辟空間,不會調(diào)用構(gòu)造函數(shù)與析構(gòu)函數(shù),而new在申請空間后會調(diào)用構(gòu)造函數(shù)完成對象的初始化,delete在釋放空間前會調(diào)用析構(gòu)函數(shù)完成空間中資源的清理
到此這篇關(guān)于一文詳解C++中動態(tài)內(nèi)存管理的文章就介紹到這了,更多相關(guān)C++動態(tài)內(nèi)存管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Visual Studio C++指針靠前靠后的問題全面解析
這篇文章主要介紹了Visual Studio C++指針靠前靠后的問題全面解析,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04淺析C++模板類型中的原樣轉(zhuǎn)發(fā)和可變參數(shù)的實現(xiàn)
可變參數(shù)模板(variadic templates)是C++11新增的強大的特性之一,它對模板參數(shù)進行了高度泛化,能表示0到任意個數(shù)、任意類型的參數(shù),這篇文章主要介紹了C++可變參數(shù)模板的展開方式,需要的朋友可以參考下2022-08-08Vs2022環(huán)境下安裝低版本.net framework的實現(xiàn)步驟
本文主要介紹了Vs2022環(huán)境下安裝低版本.net framework的實現(xiàn)步驟,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04