首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C++ 記憶體管理深度解析:new/delete、placement new 與運算子重載完整指南

     編輯:關於C語言

C++ 記憶體管理深度解析:new/delete、placement new 與運算子重載完整指南

C++ 的記憶體管理機制靈活強大,但也因 new (運算子) 與 operator new (函式)、delete 與 operator delete 的術語相似而讓許多開發者困惑。本文從語言標準、編譯器實作、記憶體佈局三個維度,徹底釐清這組核心機制。

核心概念辨析

術語類型可否重載職責
new (new-expression)語言內建運算子❌ 不可分配記憶體 + 呼叫建構函式
operator new函式 (可重載)✅ 可僅負責原始記憶體分配
delete語言內建運算子❌ 不可呼叫解構函式 + 釋放記憶體
operator delete函式 (可重載)✅ 可僅負責原始記憶體釋放

new-expression 執行流程

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)。

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; }

Placement New:在指定記憶體建構物件

應用場景

  • 記憶體池 / 物件池分配器
  • 共享記憶體 / 記憶體映射檔案
  • 嵌入式系統固定位址硬體暫存器
  • 標準庫容器內部實作 (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);

⚠️ 致命錯誤:對 placement new 物件使用 delete

void* buf = malloc(sizeof(Widget));
Widget* pw = new (buf) Widget(10);
// ... 
delete pw;  // 錯誤!operator delete 會嘗試釋放 buf,但 buf 非 new 分配

正確做法:手動呼叫解構函式 + 對應來源釋放函式。

delete-expression 執行流程

delete ps;

編譯器產生:

ps->~string();           // 1. 呼叫解構函式
operator delete(ps);     // 2. 釋放記憶體

陣列 new/delete 的特殊性

特性單一物件陣列
分配函式operator newoperator new[]
釋放函式operator deleteoperator 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); }
};

現代 C++ 最佳實踐

  1. 優先使用智慧指標:std::make_unique, std::make_shared 避免裸 new/delete
  2. 容器管理陣列:std::vector, std::array 取代 new[]
  3. Placement new 僅用於底層基礎設施:分配器、容器實作、共享記憶體
  4. 自訂分配器遵循 Allocator 概念:配合 std::allocator_traits、std::pmr::memory_resource (C++17)

總結

C++ 記憶體管理的核心在於區分「運算子」與「分配函式」:

  • new/delete:語言層面,不可變,負責物件生命週期 (建構/解構)
  • operator new/delete:函式層面,可重載,負責原始記憶體來源
  • Placement new:橋接兩者,在既有記憶體上建構物件
  • 陣列版本:額外管理元素計數,嚴禁混用

理解此架構,才能正確實作記憶體池、診斷記憶體洩漏、避免雙重釋放/使用後釋放等經典錯誤。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved