@TOC
📝new和delete操作自定義類型
我們先看malloc與free,調試可以發現並不會調用析構函數
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));
free(p1);
return 0;
}
再看new和delete
A* p2 = new A(1);
delete p2;
總結: new/delete 和 malloc/free最大區別是 new/delete對於【自定義類型】除了開空間還會調用構造函數和析構函數
而對於內置類型幾乎是一樣的
int* p3 = (int*)malloc(sizeof(int));
int* p4 = new int;
free(p3);
delete p4;
這是彙編一覽圖: 此時多出來了一個operator new這是什麼,為什麼new會去調用operator new?
00482C36 call operator new(048114Ah )
🌠 operator new與operator delete函數
🌉operator new與operator delete函數
new和delete是用户進行動態內存申請和釋放的操作符,operator new 和operator delete是系統提供的全局函數,new在底層調用operator new全局函數來申請空間,delete在底層通過operator delete全局函數來釋放空間。
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
// 如果申請內存失敗了,這裏會拋出bad_alloc 類型異常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
operator new:該函數實際通過malloc來申請空間,當malloc申請空間成功時直接返回;申請空間失敗,嘗試執行空 間不足應對措施,如果改應對措施用户設置了,則繼續申請,否則拋異常。
// report no memory
// 如果申請內存失敗了,這裏會拋出bad_alloc 類型異常
static const std::bad_alloc nomem;
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
/* 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的實現
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
如拋異常例子:
double Division(int a, int b)
{
// 當b == 0時拋出異常
if (b == 0)
throw "Division by zero condition!";
else
return ((double)a / (double)b);
}
void Func()
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
int main()
{
try
{
Func();
}
catch(const char* errmsg)
{
//當 Division(len, time) 函數拋出這種異常時,異常對象會被賦值給 errmsg 變量。
//然後,這個 catch 塊會輸出 errmsg 的內容,即 "Division by zero condition!"
cout << errmsg << endl;
}
catch (...)
{
//這個 catch 塊用於捕獲任何其他類型的未知異常。
//當 try 塊中發生任何其他類型的異常時,這個 catch 塊會被執行。
//它會輸出 "unkown exception"。
cout << "unkown exception" << endl;
}
return 0;
}
當你輸入兩個數讓b == 0時程序拋異常,拋出"Division by zero condition!"他不會再回到Func()函數中的cout << Division(len, time) << endl;而是會跳到catch(const char* errmsg)中,異常對象會被賦值給 errmsg 變量。然後,這個 catch 塊會輸出 errmsg 的內容,即 "Division by zero condition!",程序結束。
總結: 通過上述兩個全局函數的實現知道,
operator new實際也是通過malloc來申請空間,如果malloc申請空間成功就直接返回,否則執行用户提供的空間不足應對措施,如果用户提供該措施就繼續申請,否則就拋異常。operator delete最終是通過free來釋放空間的。
🌠new和delete的實現原理
🌉內置類型
如果申請的是內置類型的空間,new和malloc,delete和free基本類似,不同的地方是:new/delete申請和釋放的是單個元素的空間,new[]和delete[]申請的是連續空間,而且new在申請空間失敗時會拋異常,malloc會返回NULL。
🌉自定義類型
new的原理
- 調用
operator new函數申請空間 - 在申請的空間上執行構造函數,完成對象的構造
delete的原理 - 在空間上執行析構函數,完成對象中資源的清理工作
- 調用
operator delete函數釋放對象的空間
class Stack
{
public:
// 構造函數
Stack(int initialCapacity = 10)
: _a(new int[initialCapacity])
, _top(0)
, _capacity(initialCapacity)
{}
// 析構函數
~Stack()
{
delete[] _a;
}
// 壓入元素
void push(int value)
{
// 如果棧已滿,需要擴容
if (_top == _capacity)
{
resize(2 * _capacity);
}
// 將元素壓入棧頂
_a[_top++] = value;
}
// 彈出棧頂元素
int pop()
{
// 如果棧為空,拋出異常
if (_top == 0)
{
throw runtime_error("Stack is empty");
}
// 彈出棧頂元素並返回
return _a[--_top];
}
// 判斷棧是否為空
bool empty() const
{
return _top == 0;
}
// 返回棧中元素的個數
int size() const
{
return _top;
}
private:
// 擴容函數,分配新的數組空間並移動元素
void resize(int newCapacity)
{
// 分配新的數組空間
int* newArray = new int[newCapacity];
// 使用 std::move 將原數組中的元素移動到新數組中
move(_a, _a + _top, newArray);
// 釋放原數組空間
delete[] _a;
// 更新數組指針和容量
_a = newArray;
_capacity = newCapacity;
}
// 存儲元素的數組指針
int* _a;
// 棧頂元素的索引
int _top;
// 數組的容量
int _capacity;
};
int main()
{
// 創建一個棧
Stack* p3 = new Stack;
// 釋放棧的內存
delete p3;
//// 壓入三個元素
//p3->push(1);
//p3->push(2);
//p3->push(3);
//// 依次彈出並打印元素
//while (!p3->empty())
//{
// cout << p3->pop() << " ";
//}
//cout << endl;
return 0;
}
先析構_a指向的空間,再釋放p3指向的空間
new T[N]的原理
- 調用
operator new[]函數,在operator new[]中實際調用operator new函數完成N個對象空間的申 請 - 在申請的空間上執行
N次構造函數
delete[]的原理 3. 在釋放的對象空間上執行N次析構函數,完成N個對象中資源的清理 4. 調用operator delete[]釋放空間,實際在operator delete[]中調用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()
{
A* p1 = new A;
A* p2 = new A[10];
delete p1;
delete[] p2;
return 0;
}
A類只有一個4字節大小的int成員變量。那麼,創建一個A類型的對象應該需要4字節的內存空間。A* p2 = new A[10];這我們動態創建了一個包含10個A對象的數組。10 * 4 = 40 bytes,為什麼是44bite呢?
在動態分配數組內存時,編譯器通常會在實際的數組內存之前分配一些額外的空間,用於存儲數組的元素個數等信息。這樣做的目的是為了在執行delete[]操作時,能夠正確地調用所有元素的析構函數。
總結:都開
4byte存儲對象個數,方便delete[]時,知道有多少個對象,要調用多少次析構函數
內置類型就沒有額外開空間: 因為,內置類型已經固定好,無需調用析構函數
int* p3 = new int[10];
delete[] p3;
🌠定位new表達式(placement-new)
定位new表達式是在已分配的原始內存空間中調用構造函數初始化一個對象。使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必須是一個指針,initializer-list是類型的初始化列表
使用場景:定位new表達式在實際中一般是配合內存池使用。因為內存池分配出的內存沒有初始化,所以如果是自定義類型的對象,需要使用new的定義表達式進行顯示調構造函數進行初始化。
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
// 定位new/replacement new
int main()
{
// p1現在指向的只不過是與A對象相同大小的一段空間,還不能算是一個對象,因為構造函數沒有執行
A* p1 = (A*)malloc(sizeof(A));
new(p1)A; // 注意:如果A類的構造函數有參數時,此處需要傳參
p1->~A();
free(p1);
A* p2 = (A*)operator new(sizeof(A));
new(p2)A(10);
p2->~A();
operator delete(p2);
return 0;
}
A* p1 = (A*)malloc(sizeof(A));
使用malloc()函數分配了一塊與A類對象大小相同的內存空間,但此時p1指向的只是一塊內存空間,還不是一個真正的A對象,因為A的構造函數還沒有被調用。
new(p1)A;
使用"定位new"的語法在已分配的內存空間上構造一個A對象。如果A類的構造函數有參數,需要在這裏傳入參數,例如new(p2)A(10);。
p1->~A();
顯式地調用A對象的析構函數,釋放對象佔用的資源。
free(p1);
free()函數釋放之前使用malloc()分配的內存空間。
不要錯配使用delete和free,一定匹配使用,否則結果是不確定