C++ 的記憶體管理機制靈活強大,但也因 new (運算子) 與 operator new (函式)、delete 與 operator delete 的術語相似而讓許多開發者困惑。本文從語言標準、編譯器實作、記憶體佈局三個維度,徹底釐清這組核心機制。
| 術語 | 類型 | 可否重載 | 職責 |
|---|---|---|---|
new (new-expression) | 語言內建運算子 | ❌ 不可 | 分配記憶體 + 呼叫建構函式 |
operator new | 函式 (可重載) | ✅ 可 | 僅負責原始記憶體分配 |
delete | 語言內建運算子 | ❌ 不可 | 呼叫解構函式 + 釋放記憶體 |
operator delete | 函式 (可重載) | ✅ 可 | 僅負責原始記憶體釋放 |
string* ps = new string("hello");
編譯器產生等價程式碼:
// 1. 分配原始記憶體
void* memory = operator new(sizeof(string));
// 2. 呼叫建構函式 (placement new 語法)
new (memory) string("hello"); // 在指定記憶體建構物件
// 3. 型別轉換
string* ps = static_cast<string*>(memory);
關鍵點:new 運算子永遠執行這兩步,無法改變。可自訂的是第 1 步的分配策略(重載 operator new)。
// 標準單一物件版本
void* operator new(std::size_t size) noexcept(false); // 失敗拋 bad_alloc
void* operator new(std::size_t size, const std::nothrow_t&) noexcept; // 失敗回傳 nullptr
// 陣列版本 (C++ 標準庫提供)
void* operator new[](std::size_t size);
void* operator new[](std::size_t size, const std::nothrow_t&);
// Placement new 版本 (標準庫內建,不可替換)
void* operator new(std::size_t, void* ptr) noexcept { return ptr; }
void* operator new[](std::size_t, void* ptr) noexcept { return ptr; }
std::vector::emplace_back)#include <new> // 必須包含
class Widget {
public:
explicit Widget(int size) : size_(size) {}
~Widget() { /* 清理資源 */ }
private:
int size_;
};
// 分配原始記憶體 (來自記憶體池、mmap、共享記憶體等)
void* rawMemory = memoryPool.allocate(sizeof(Widget));
// Placement new 建構物件
Widget* pw = new (rawMemory) Widget(42);
// 使用物件...
pw->DoSomething();
// 明確呼叫解構函式 (關鍵!)
pw->~Widget();
// 釋放原始記憶體 (對應分配來源)
memoryPool.deallocate(rawMemory);
void* buf = malloc(sizeof(Widget));
Widget* pw = new (buf) Widget(10);
// ...
delete pw; // 錯誤!operator delete 會嘗試釋放 buf,但 buf 非 new 分配
正確做法:手動呼叫解構函式 + 對應來源釋放函式。
delete ps;
編譯器產生:
ps->~string(); // 1. 呼叫解構函式
operator delete(ps); // 2. 釋放記憶體
| 特性 | 單一物件 | 陣列 |
|---|---|---|
| 分配函式 | operator new | operator new[] |
| 釋放函式 | operator delete | operator delete[] |
| 建構呼叫 | 1 次 | N 次 (每個元素) |
| 解構呼叫 | 1 次 | N 次 (逆序) |
| Cookie 儲存 | 無 | 通常前置陣列大小 |
鐵律:new T[N] 必須配對 delete[] ptr,否則未定義行為(解構次數錯誤、Cookie 損毀)。
class MyAllocator {
public:
static void* Allocate(size_t size) {
// 記錄分配資訊、對齊處理、統計...
return ::operator new(size); // 呼叫全域 operator new
}
static void Deallocate(void* ptr, size_t size) {
::operator delete(ptr);
}
};
// 類別專用 new/delete
class TrackedObject {
public:
void* operator new(size_t size) { return MyAllocator::Allocate(size); }
void operator delete(void* ptr) { MyAllocator::Deallocate(ptr, 0); }
void* operator new[](size_t size) { return MyAllocator::Allocate(size); }
void operator delete[](void* ptr) { MyAllocator::Deallocate(ptr, 0); }
};
std::make_unique, std::make_shared 避免裸 new/deletestd::vector, std::array 取代 new[]std::allocator_traits、std::pmr::memory_resource (C++17)C++ 記憶體管理的核心在於區分「運算子」與「分配函式」:
new/delete:語言層面,不可變,負責物件生命週期 (建構/解構)operator new/delete:函式層面,可重載,負責原始記憶體來源理解此架構,才能正確實作記憶體池、診斷記憶體洩漏、避免雙重釋放/使用後釋放等經典錯誤。