文章目錄
- 內存分布
- 內存分布圖解
- C語言中動態內存管理方式
- malloc:
- calloc
- realloc
- C++內存管理方式
- 內置類型
- **自定義類型**
- operator new & operator delete
- operator new & operator delete函數
- operator new
- operator delete
- **new T[N]** 與**delete[]**
- **定位new表達式(placement-new)**
- 如何使用
- 注意事項
- malloc/free和new/delete的區別
類和對象三部曲:
[C++] 輕熟類和對象
[C++] 由淺入深理解面向對象思想的組成模塊
類和對象:C++11新特性與知識補充
內存分布
內存分布圖解
- 棧又叫堆棧–非靜態局部變量/函數參數/返回值等等,棧是向下增長的。
- 內存映射段是高效的I/O映射方式,用于裝載一個共享的動態內存庫。用戶可使用系統接口
創建共享共享內存,做進程間通信。- 堆用于程序運行時動態內存分配,堆是可以上增長的。
- 數據段–存儲全局數據和靜態數據。
- 代碼段–可執行的代碼/只讀常量
C語言中動態內存管理方式
malloc:
void* malloc(size_t size);
- 功能:malloc函數用于在堆上分配一塊連續的內存空間。它接受一個參數,即所需內存的大小(以字節為單位),并返回指向這塊內存的指針。
- 初始化:malloc不會對分配的內存進行初始化,內存中的內容是未定義的,可能是之前的值或者全零,具體取決于操作系統。
- 使用場景:當不需要初始化內存或者特定初始化時使用。
calloc
void* calloc(size_t num, size_t size);
- 功能:calloc也用于在堆上分配內存,但它接受兩個參數,分別是要分配的元素數量和每個元素的大小(以字節為單位)。calloc會確保分配的內存區域中的每個字節都被初始化為零。
- 初始化:與malloc不同,calloc會將分配的內存全部初始化為零,這使得它適合用于數組或結構體等需要初始化為默認值的情況。
- 使用場景:當需要一個清零的內存塊時使用,比如初始化數組。
realloc
void* realloc(void* ptr, size_t size);
- 功能:realloc用于調整先前通過malloc、calloc或realloc分配的內存塊的大小。它接受兩個參數,第一個是之前分配的內存的指針,第二個是新的大小(可以比原來大也可以比原來小)。
- 初始化:realloc不涉及初始化新分配的內存部分,如果擴大了內存塊,新增的部分通常也是未定義的值。
- 使用場景:當原先分配的內存大小不再滿足需求,需要擴大或減小內存空間時使用。需要注意的是,如果減小內存空間,超出新大小的部分數據會被截斷。
C++內存管理方式
內置類型
// 動態申請一個int類型的空間
int* ptr4 = new int;
// 動態申請一個int類型的空間并初始化為10
int* ptr5 = new int(10);
// 動態申請10個int類型的空間
int* ptr6 = new int[10];delete ptr4;
delete ptr5;
delete[] ptr6;// 其他方式
int* p3 = new int(0);
int* p4 = new int[10]{ 0 };
int* p5 = new int[10]{1,2,3,4,5}; // 未初始化的用0補齊
注意:申請和釋放單個元素的空間,使用new和delete操作符,申請和釋放連續的空間,使用
new[]
和delete[]
。
自定義類型
A* p1 = (A*)malloc(sizeof(A)); // C
A* p2 = new A(1); // C++A* p1 = new A(1);
A* p2 = new A(2,2); // 隱式類型A aa1(1, 1);
A aa2(2, 2);
A aa3(3, 3);
A* p3 = new A[3]{aa1, aa2, aa3}; A* p4 = new A[3]{ A(1,1), A(2,2), A(3,3)}; // 匿名函數//A aa1 = { 1, 1 };
A* p5 = new A[3]{ {1,1}, {2,2}, {3,3} };
C++中推薦使用
new
和delete
進行內存管理,使用這二者進行內存管理的特點為**“除了開空間還會調用構造函數和析構函數”(原理下章會提及)**
operator new & operator delete
operator new & operator delete函數
operator new
原理:
- **內置類型:**與
malloc
相似- 自定義類型:
- 調用operator new函數申請空間
- 在申請的空間上執行構造函數,完成對象的構造
源碼如下
/*
operator new:該函數實際通過malloc來申請空間,當malloc申請空間成功時直接返回;申請空間
失敗,嘗試執行空 間不足應對措施,如果改應對措施用戶設置了,則繼續申請,否
則拋異常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid* p;while ((p = malloc(size)) == 0) // 通過malloc擴容if (_callnewh(size) == 0){// report no memory// 如果申請內存失敗了,這里會拋出bad_alloc 類型異常static const std::bad_alloc nomem;_RAISE(nomem);}return (p);// 返回分配的內存指針
}
通過分析源碼可得出:
- 在底層會調用
**malloc**
分配內存:函數內部有一個while
循環,通過malloc
分配指定大小的內存。- **會自動拋異常:**當
malloc
返回nullptr
,則調用_callnewh
嘗試處理內存不足的情況,若仍然無法分配內存,則拋出std::bad_alloc
異常。- 在語法層面上會調用構造函數:
new
操作符分配內存后,會在分配的內存上調用構造函數,完成對象的初始化。
operator delete
原理:
- **內置類型:**與
free
基本類似- 自定義類型:
- 在空間上執行析構函數,完成對象中資源的清理工作
- 調用operator delete函數釋放對象的空間
源碼如下:
/*
operator delete: 該函數最終是通過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/* 獲取指針指向內存塊的頭信息 */pHead = pHdr(pUserData);/* verify block type */_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));_free_dbg(pUserData, pHead->nBlockUse); // 使用_free_dbg進行內存的釋放__FINALLY_munlock(_HEAP_LOCK); /* release other threads */__END_TRY_FINALLYreturn;
}
/*
free的實現
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
源碼分析:
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
我們可以發現,free
的底層其實是一個宏,最終還是使用_free_dbg(p, _NORMAL_BLOCK)
進行內存釋放。- 通過第一點分析可得,
delete
的底層也是通過free
,或者說_free_dbg(p, _NORMAL_BLOCK)
進行內存的釋放- 在語法層面上調用析構函數: 在釋放內存之前調用對象的析構函數,以確保對象持有的資源(如動態分配的內存、打開的文件等)得到正確釋放。
編譯器在處理 delete obj;
這行代碼時會生成以下等效的代碼:
if (obj != nullptr) {obj->~A(); // 顯式調用析構函數operator delete(obj); // 調用 operator delete 釋放內存
}
new T[N] 與delete[]
new T[N]的原理
- 調用operator new[]函數,在operator new[]中實際調用operator new函數完成N個對
象空間的申請- 在申請的空間上執行N次構造函數
delete[]的原理
- 在釋放的對象空間上執行N次析構函數,完成N個對象中資源的清理
- 調用operator delete[]釋放空間,實際在operator delete[]中調用operator delete來釋
放空間
定位new表達式(placement-new)
定位new表達式語法:void* operator new(size_t, void* place) noexcept { return place; }
- 定位new表達式(Placement New Expression),或簡稱placement new,是C++中一種特殊的內存分配式,它允許你在已經分配好的內存區域內構造對象。與標準的new操作符不同,定位new不負責內存的分配,而是直接在你指定的內存地址上調用對象的構造函數。這對于實現內存池、重復利用已分配的內存塊、在特定內存位置(如共享內存)創建對象等場景非常有用。
- 定位
new
表達式允許我們在預分配的內存上構造對象,并手動管理對象的生命周期,包括調用析構函數和釋放內存。這樣可以更好地控制內存分配和釋放過程,避免內存泄漏和資源未釋放的問題。
如何使用
舉例
#include <iostream>
#include <cstdlib> // for malloc and freeusing namespace std;class MyClass {
public:MyClass(int value) : value(value) {cout << "MyClass(int) constructor with value: " << value << endl;}~MyClass() {cout << "~MyClass() destructor with value: " << value << endl;}private:int value;
};int main() {// Step 1: Allocate raw memory using mallocsize_t numObjects = 3;void* rawMemory = malloc(numObjects * sizeof(MyClass));if (!rawMemory) {cerr << "Memory allocation failed!" << endl;return 1;}// Step 2: Use placement new to construct objects in the allocated memoryMyClass* objects = static_cast<MyClass*>(rawMemory);for (size_t i = 0; i < numObjects; ++i) {new (objects + i) MyClass(i * 10); // Construct MyClass objects with values 0, 10, 20}// Step 3: Use the objects (this step is trivial in this example)// Step 4: Manually call destructors for each objectfor (size_t i = 0; i < numObjects; ++i) {objects[i].~MyClass();}// Step 5: Free the allocated raw memoryfree(rawMemory);return 0;
}
步驟解析:
- 使用 malloc 分配原始內存:
size_t numObjects = 3;
void* rawMemory = malloc(numObjects * sizeof(MyClass));
if (!rawMemory) {cerr << "Memory allocation failed!" << endl;return 1;
}
- 使用
malloc
函數分配一塊大小為numObjects * sizeof(MyClass)
的連續內存,用來存放 3 個MyClass
對象。- 如果內存分配失敗,程序會輸出錯誤信息并返回。
- 在分配的內存中,使用
new
構建對象:
MyClass* objects = static_cast<MyClass*>(rawMemory);
for (size_t i = 0; i < numObjects; ++i) {new (objects + i) MyClass(i * 10); // Construct MyClass objects with values 0, 10, 20
}
- 使用
placement new
表達式在預分配的內存上構造MyClass
對象。- 通過
static_cast
將rawMemory
轉換為指向MyClass
類型的指針。- 在
for
循環中,調用定位new
在內存地址objects + i
上構造MyClass
對象,分別傳入 0、10 和 20 作為構造函數參數。
-
對象的使用 (省略)
-
手動調用每個對象的析構函數進行析構
for (size_t i = 0; i < numObjects; ++i) {objects[i].~MyClass();
}
- 在內存釋放之前,必須手動調用每個對象的析構函數,釋放對象的資源。
- 使用
for
循環,調用每個對象的析構函數。
- 釋放掉原始分配的內存
free(rawMemory);
使用
free
函數釋放在步驟 1 中分配的原始內存。
注意事項
- 內存管理:使用
定位new
后,對象的生命周期管理完全由程序員負責。這意味著你不能使用普通delete
來釋放這個對象,因為那會試圖釋放由malloc
分配的內存,導致未定義行為。你應該直接調用對象的析構函數,并手動歸還內存:
A->~A(); // 手動調用析構函數
std::free(p1); // 釋放內存
- 內存對齊:確保提供的內存地址是正確對齊的,以便能夠容納特定類型的對象。如果不對齊,可能導致未定義行為。
- 安全性:使用定位new時,你需要確保所指定的內存區域足夠大,以容納完整的對象實例,包括可能的內部對齊填充。否則,可能會覆蓋周邊內存,引發嚴重錯誤。
- 標準庫支持:C++標準庫提供了一個全局的
operator new(void*, std::size_t)
重載,它不執行任何實際的內存分配,專門用于定位new表達式。這個重載是固定的,不能被用戶自定義版本替代。
malloc/free和new/delete的區別
malloc/free和new/delete的共同點是:
- 都是從堆上申請空間,并且需要用戶手動釋放。
不同的地方是:
malloc
和free
是函數,new
和delete
是操作符malloc
申請的空間不會初始化,new
可以初始化malloc
申請空間時,需要手動計算空間大小并傳遞,new
只需在其后跟上空間的類型即可,
如果是多個對象,[]
中指定對象個數即可- malloc的返回值為
void*
, 在使用時必須強轉,new
不需要,因為new
后跟的是空間的類型- malloc申請空間失敗時,返回的是
NULL
,因此使用時必須判空,new
不需要,但是new
需
要捕獲異常- 申請自定義類型對象時,
malloc/free
只會開辟空間,不會調用構造函數與析構函數,而new
在申請空間后會調用構造函數完成對象的初始化,delete
在釋放空間前會調用析構函數完成
空間中資源的清理釋放