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

C++ 動態記憶體管理:new/delete、智能指針、RAII、自定義分配器與對齊完全解析

     編輯:C++ 入門

C++ 手動管理堆記憶體 (new/delete) 易導致洩漏、雙重釋放、懸空指針。現代 C++ (C++11/14/17/20) 以 RAII、智能指針、標準容器、自定義分配器構建安全、高效、無洩漏的記憶體模型。

原始 new/delete (僅教學/遺留代碼)

int* p = new int(42);        // 單對象
int* arr = new int[10]{};    // 陣列 (值初始化)
delete p;                    // 單對象釋放
delete[] arr;                // 陣列釋放 (必須配對 [])

::operator new(size_t);      // 全域分配函數 (可替換)
::operator delete(void*);    // 全域釋放函數
// 類專屬:static void* operator new(size_t); static void operator delete(void*);

陷阱:new T[3] 返回 T*,無長度資訊,delete[] 依賴編譯器內部元數據 (通常存塊頭)。混用 delete/delete[] 未定義行為。

智能指針三劍客 (C++11)

類型所有權語義拷貝/移動適用場景開銷
unique_ptr<T>獨占所有權不可拷貝、可移動單一擁有者、工廠返回、Pimpl、RAII 包裝零 (等同裸指針)
shared_ptr<T>共享所有權 (引用計數)可拷貝、可移動多擁有者、觀察者模式、緩存、跨線程共享控制塊 (2指針+計數)、原子操作
weak_ptr<T>非所有權觀察者可拷貝、可移動打破循環引用、緩存、臨時訪問共享控制塊、無原子增減

unique_ptr 最佳實踐

auto p = std::make_unique<Widget>(args); // C++14 偏好,異常安全、單次分配
std::unique_ptr<Widget[]> arr(new Widget[10]); // 陣列特化,自動 delete[]
// 自定義刪除器
auto fp = std::unique_ptr<FILE, decltype(&fclose)>(fopen("a.txt","w"), fclose);
auto cuda = std::unique_ptr<int, CudaDeleter>(ptr); // lambda/function object

shared_ptr 陷阱與對策

  • 循環引用:A 持有 B 的 shared_ptr,B 持有 A 的 shared_ptr → 永不釋放。解決:一方改用 weak_ptr。
  • 控制塊雙重分配:shared_ptr<T>(new T) 兩次分配 (對象+控制塊)。解決:std::make_shared<T>() 單次分配 (但無法自定義刪除器/分配器)。
  • this 指針構造 shared_ptr:shared_ptr<T>(this) 導致雙重控制塊。解決:繼承 std::enable_shared_from_this<T>,用 shared_from_this()。
  • 線程安全:引用計數增減原子,但對象訪問需外部同步。

RAII (Resource Acquisition Is Initialization) 核心模式

class FileHandle {
    FILE* fp_;
public:
    FileHandle(const char* path, const char* mode) : fp_(fopen(path, mode)) {
        if (!fp_) throw std::runtime_error("open failed");
    }
    ~FileHandle() { if (fp_) fclose(fp_); }
    // 禁用拷貝,支援移動
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;
    FileHandle(FileHandle&& other) noexcept : fp_(other.fp_) { other.fp_ = nullptr; }
    FileHandle& operator=(FileHandle&& other) noexcept {
        if (this != &other) { if (fp_) fclose(fp_); fp_ = other.fp_; other.fp_ = nullptr; }
        return *this;
    }
    FILE* get() const { return fp_; }
};
// 使用
{ FileHandle f("data.bin", "rb"); /* 自動關閉 */ }

自定義分配器

// C++17 std::allocator 概念簡化
template <typename T>
struct PoolAllocator {
    using value_type = T;
    PoolAllocator() = default;
    template <typename U> PoolAllocator(const PoolAllocator<U>&) {}
    T* allocate(std::size_t n) {
        // 從記憶體池取塊 (略)
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }
    void deallocate(T* p, std::size_t) { ::operator delete(p); }
    template <typename U> bool operator==(const PoolAllocator<U>&) const { return true; }
};

// 使用
std::vector<int, PoolAllocator<int>> vec;
std::allocate_shared<Widget>(PoolAllocator<Widget>(), args);

記憶體對齊 (C++17/20)

// 對齊需求
alignas(64) int cache_line_aligned[16]; // 64 字節對齊,避免偽共享
struct alignas(32) AlignedStruct { double x, y, z; }; // 32 字節對齊

// aligned_alloc / std::aligned_alloc (C++17)
void* ptr = std::aligned_alloc(64, 1024); // 64 字節對齊分配 1KB
std::free(ptr); // 必須用 free 釋放

// new (C++17) 支援對齊
struct Aligned { alignas(128) char data[128]; };
Aligned* p = new Aligned; // 自動滿足 128 字節對齊

// std::assume_aligned (C++20) 提示編譯器
void process(int* p) {
    p = std::assume_aligned<64>(p); // 編譯器假設 p 64 字節對齊,生成向量化指令
}

現代 C++ 記憶體管理清單

  • ✅ 零裸指針擁有權:所有堆對象由 unique_ptr/shared_ptr/容器管理。
  • ✅ 工廠返回 unique_ptr:std::make_unique 避免洩漏。
  • ✅ 共享所有權用 shared_ptr + weak_ptr 打破環。
  • ✅ 自定義資源封裝 RAII 類 (FileHandle, SocketHandle, LockGuard)。
  • ✅ 標準容器替代手動陣列:vector, string, array。
  • ✅ 高效能場景自定義分配器:物件池、竞技場分配器、共享記憶體。
  • ✅ 對齊敏感熱點用 alignas/aligned_alloc 啟用 SIMD、避免偽共享。
  • ✅ 工具檢測:AddressSanitizer (-fsanitize=address)、Valgrind、Dr. Memory、Visual Studio 診斷工具。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved