【C++特殊工具與技術】優化內存分配(三):operator new函數和opertor delete函數

目錄

一、基礎概念:operator new與operator delete的本質

1.1 標準庫提供的operator new接口

1.2 標準庫operator delete的接口

1.3 關鍵特性總結

二、new表達式與operator new的調用鏈解析

2.1?new表達式的底層步驟

2.2 示例:觀察new表達式的調用過程

三、自定義operator new與operator delete

3.1 自定義的動機與場景

3.2 類特定的operator new重載

3.3 全局operator new的重載

四、對齊內存分配與 C++17 的align_val_t

4.1 對齊的重要性

4.2 對齊operator new的重載

五、常見陷阱與最佳實踐

5.1 陷阱 1:內存分配失敗的處理

5.2 陷阱 2:operator delete的參數匹配

5.3 最佳實踐:與智能指針配合

5.4 最佳實踐:性能優化建議

六、總結


內存管理是 C++ 的核心能力之一,而operator newoperator delete作為內存分配的底層接口,是理解 C++ 對象生命周期的關鍵。

一、基礎概念:operator newoperator delete的本質

在 C++ 中,newdelete有兩層含義:

  1. 表達式:如int* p = new int(10);,負責內存分配 + 對象構造(或delete p;負責對象析構 + 內存釋放)。
  2. 操作符函數:即operator newoperator delete,是new/delete表達式調用的底層內存分配 / 釋放函數。

核心關系new表達式的執行流程是:

  1. 調用operator new分配原始內存;
  2. 調用對象的構造函數(若為類類型);
    反之,delete表達式的流程是:
  3. 調用對象的析構函數(若為類類型);
  4. 調用operator delete釋放內存。

1.1 標準庫提供的operator new接口

C++ 標準庫定義了多組operator newoperator delete的重載形式,覆蓋不同場景的內存分配需求。以下是最核心的 4 種形式(以 64 位系統為例):

①普通內存分配(拋出異常)

// 分配size字節的原始內存,失敗時拋出std::bad_alloc異常
void* operator new(std::size_t size);
void* operator new[](std::size_t size); // 數組版本

②不拋出異常的分配(nothrow 版本)?

// 分配失敗時返回nullptr,不拋出異常
void* operator new(std::size_t size, const std::nothrow_t&) noexcept;
void* operator new[](std::size_t size, const std::nothrow_t&) noexcept;

③定位分配(placement new)?

// 在已分配的內存地址ptr處構造對象(不分配內存)
void* operator new(std::size_t, void* ptr) noexcept;
void* operator new[](std::size_t, void* ptr) noexcept;

1.2 標準庫operator delete的接口

operator delete的職責是釋放operator new分配的內存,其接口與operator new一一對應:?

// 普通釋放(與普通operator new配對)
void operator delete(void* ptr) noexcept;
void operator delete[](void* ptr) noexcept;// 與nothrow版本配對的釋放函數
void operator delete(void* ptr, const std::nothrow_t&) noexcept;
void operator delete[](void* ptr, const std::nothrow_t&) noexcept;// 與對齊分配配對的釋放函數(C++17)
void operator delete(void* ptr, std::align_val_t align) noexcept;
void operator delete[](void* ptr, std::align_val_t align) noexcept;

1.3 關鍵特性總結

特性說明
全局默認實現標準庫提供的operator new底層調用mallocoperator delete調用free
異常行為普通版本分配失敗拋std::bad_alloc,nothrow 版本返回nullptr
內存對齊普通版本保證至少alignof(std::max_align_t)(通常為 16 字節)的對齊
數組與標量差異operator new[]operator delete[]用于數組,實現上可能多分配額外空間存儲數組大小

二、new表達式與operator new的調用鏈解析

要理解operator new的作用,必須明確new表達式的完整執行流程。以類對象為例:?

class MyClass { 
public: MyClass(int x) : val(x) {} 
private: int val; 
};MyClass* obj = new MyClass(10); // new表達式

2.1?new表達式的底層步驟

  1. 調用operator new:分配足夠大的內存(大小為sizeof(MyClass))。
  2. 調用構造函數:在分配的內存地址上調用MyClass::MyClass(int)
  3. 返回對象指針:若構造成功,返回指向對象的指針;若構造或分配失敗,釋放已分配的內存并拋出異常。

2.2 示例:觀察new表達式的調用過程

通過重載全局operator new并添加日志,可以驗證上述流程:?

#include <iostream>
#include <new>
#include <cstdlib>// 重載全局operator new(普通版本)
void* operator new(std::size_t size) {std::cout << "全局operator new被調用,分配大小:" << size << "字節" << std::endl;void* ptr = std::malloc(size); // 調用malloc分配內存if (!ptr) throw std::bad_alloc{};return ptr;
}// 重載全局operator delete(普通版本)
void operator delete(void* ptr) noexcept {std::cout << "全局operator delete被調用" << std::endl;std::free(ptr); // 調用free釋放內存
}class MyClass {
public:MyClass(int x) : val(x) { std::cout << "MyClass構造函數被調用" << std::endl; }~MyClass() { std::cout << "MyClass析構函數被調用" << std::endl; }
private:int val;
};int main() {MyClass* obj = new MyClass(10); // new表達式delete obj;                     // delete表達式return 0;
}

輸出結果

?

  • new表達式先調用operator new分配內存,再調用構造函數;
  • delete表達式先調用析構函數,再調用operator delete釋放內存;
  • 分配的內存大小等于對象類型的大小(sizeof(MyClass)=4,因int val占 4 字節)。

三、自定義operator newoperator delete

標準庫的operator new基于malloc實現,雖然通用但可能在特定場景下效率不足(如高頻小對象分配導致內存碎片)。通過自定義operator new,可以實現內存池、對齊優化、性能監控等高級功能。

3.1 自定義的動機與場景

場景自定義方案
減少內存碎片實現小對象內存池(如每 8 字節為一個塊,預分配連續內存)
提升分配速度繞過malloc的全局鎖,使用線程本地內存池
內存對齊優化為特定類型(如圖像數據、SIMD 指令數據)提供更高對齊的內存
內存泄漏檢測在分配時記錄內存地址,釋放時檢查是否重復釋放
調試與監控統計各類型的內存使用量,定位內存分配熱點

3.2 類特定的operator new重載

最常見的自定義方式是為某個類單獨重載operator newoperator delete,使該類的所有對象分配都使用自定義邏輯。

示例:為類實現內存池

以下代碼為SmallObject類實現一個簡單的內存池,用于管理高頻分配的小對象(假設對象大小≤64 字節):

#include <iostream>
#include <vector>
#include <cstddef>
#include <cstdlib>class SmallObject {
public:static void* operator new(std::size_t size);static void operator delete(void* ptr, std::size_t size);// 測試用構造函數SmallObject(int x) : value(x) {}int value;
};// 內存池實現(簡化版)
class MemoryPool {
private:static constexpr std::size_t BLOCK_SIZE = 4096;    // 每個內存塊大小(4KB)static constexpr std::size_t OBJECT_SIZE = 64;     // 最大小對象大小std::vector<char*> blocks;                         // 已分配的內存塊char* currentBlock = nullptr;                      // 當前塊指針char* currentPos = nullptr;                        // 當前分配位置public:MemoryPool() { allocateNewBlock(); }void* allocate(std::size_t size) {if (size > OBJECT_SIZE) {return std::malloc(size); // 大對象直接調用malloc}if (currentPos + size > currentBlock + BLOCK_SIZE) {allocateNewBlock(); // 當前塊不足,分配新塊}void* ptr = currentPos;currentPos += size;return ptr;}void deallocate(void* ptr, std::size_t size) {if (size > OBJECT_SIZE) {std::free(ptr); // 大對象直接調用freereturn;}// 簡單內存池不回收單個對象,實際可擴展為空閑鏈表}private:void allocateNewBlock() {currentBlock = static_cast<char*>(std::malloc(BLOCK_SIZE));if (!currentBlock) throw std::bad_alloc{};blocks.push_back(currentBlock);currentPos = currentBlock;std::cout << "分配新內存塊,地址:" << static_cast<void*>(currentBlock) << std::endl;}
};// 靜態內存池實例
static MemoryPool smallObjectPool;// 類特定的operator new
void* SmallObject::operator new(std::size_t size) {return smallObjectPool.allocate(size);
}// 類特定的operator delete
void SmallObject::operator delete(void* ptr, std::size_t size) {smallObjectPool.deallocate(ptr, size);
}int main() {// 測試小對象分配SmallObject* obj1 = new SmallObject(10);SmallObject* obj2 = new SmallObject(20);std::cout << "obj1地址:" << obj1 << std::endl;std::cout << "obj2地址:" << obj2 << std::endl;delete obj1;delete obj2;return 0;
}

輸出結果

注意:兩個對象地址連續,間隔4字節(int大小)
  • 內存池預分配 4KB 的內存塊,小對象分配時直接從塊中劃分,避免頻繁調用malloc
  • operator newoperator delete通過靜態MemoryPool實例管理內存;
  • 實際生產環境中,內存池需要實現空閑鏈表(free list)來回收釋放的內存,避免內存浪費。

3.3 全局operator new的重載

全局重載會影響所有未顯式定義類特定operator new的類型,需謹慎使用。常見場景是實現全局內存監控或調試工具。

示例:全局內存分配監控

通過全局重載operator newoperator delete,記錄每次分配的內存大小和地址,用于檢測內存泄漏:?

#include <iostream>
#include <new>
#include <unordered_map>
#include <mutex>// 全局內存分配統計
static std::unordered_map<void*, std::size_t> allocatedMemory;
static std::mutex mtx;// 重載全局operator new(普通版本)
void* operator new(std::size_t size) {void* ptr = std::malloc(size);if (!ptr) throw std::bad_alloc{};std::lock_guard<std::mutex> lock(mtx);allocatedMemory[ptr] = size; // 記錄分配的內存地址和大小std::cout << "分配內存:地址=" << ptr << ",大小=" << size << "字節" << std::endl;return ptr;
}// 重載全局operator delete(普通版本)
void operator delete(void* ptr) noexcept {std::lock_guard<std::mutex> lock(mtx);if (allocatedMemory.count(ptr)) {std::size_t size = allocatedMemory[ptr];allocatedMemory.erase(ptr);std::cout << "釋放內存:地址=" << ptr << ",大小=" << size << "字節" << std::endl;}std::free(ptr);
}int main() {int* p1 = new int(10);int* p2 = new int[5]; // 調用operator new[]delete p1;// 注意:delete[]調用operator delete[],這里未重載,使用標準庫版本(不會觸發監控)// 實際使用中需同時重載operator new[]和operator delete[]// 程序結束前檢查未釋放的內存std::lock_guard<std::mutex> lock(mtx);if (!allocatedMemory.empty()) {std::cout << "檢測到內存泄漏,未釋放的內存塊數:" << allocatedMemory.size() << std::endl;for (auto& [ptr, size] : allocatedMemory) {std::cout << "泄漏地址:" << ptr << ",大小:" << size << "字節" << std::endl;}}return 0;
}
  • 全局重載會影響所有未自定義operator new的類型;
  • 需同時重載operator new[]operator delete[]以支持數組分配;
  • 通過統計allocatedMemory可以檢測內存泄漏,但實際工具(如 Valgrind)更高效。

四、對齊內存分配與 C++17 的align_val_t

對于需要高對齊的場景(如 SIMD 指令處理、GPU 數據傳輸),C++17 引入了align_val_t類型,允許自定義對齊的內存分配。

4.1 對齊的重要性

現代 CPU 對內存對齊有嚴格要求:

  • 訪問未對齊的內存可能導致性能下降(如 x86)或硬件異常(如 ARM);
  • SIMD 指令(如 AVX-512)要求數據按 32/64 字節對齊;
  • GPU 紋理數據通常要求按 256 字節對齊。

4.2 對齊operator new的重載

C++17 允許通過align_val_t參數重載operator new,處理特定對齊需求:?

#include <iostream>
#include <new>
#include <cstdalign>// 重載對齊版本的operator new(C++17)
void* operator new(std::size_t size, std::align_val_t align) {std::cout << "對齊分配:大小=" << size << ",對齊=" << static_cast<std::size_t>(align) << "字節" << std::endl;void* ptr = std::aligned_alloc(static_cast<std::size_t>(align), size);if (!ptr) throw std::bad_alloc{};return ptr;
}// 重載對齊版本的operator delete(C++17)
void operator delete(void* ptr, std::align_val_t align) noexcept {std::cout << "對齊釋放:地址=" << ptr << ",對齊=" << static_cast<std::size_t>(align) << "字節" << std::endl;std::free(ptr); // aligned_alloc分配的內存可用free釋放
}// 定義需要32字節對齊的類(C++11 alignas關鍵字)
class AlignedData {
public:alignas(32) int data[8]; // 32字節對齊的int數組(8個int占32字節)
};int main() {AlignedData* data = new AlignedData();std::cout << "對象地址:" << data << std::endl;std::cout << "地址對齊驗證:" << (reinterpret_cast<uintptr_t>(data) % 32 == 0 ? "符合" : "不符合") << std::endl;delete data;return 0;
}
  • alignas(32)指定類的對齊要求,new表達式會調用對齊版本的operator new
  • std::aligned_alloc是 C11 提供的對齊內存分配函數,C++17 起可用;
  • 對齊分配的內存必須用對應的operator delete釋放。

五、常見陷阱與最佳實踐

5.1 陷阱 1:內存分配失敗的處理

標準庫operator new在分配失敗時拋std::bad_alloc,但自定義實現需顯式處理失敗場景:

// 錯誤示例:未檢查malloc返回值
void* operator new(std::size_t size) {return std::malloc(size); // malloc失敗返回nullptr,但未拋異常,違反標準行為
}// 正確示例:
void* operator new(std::size_t size) {void* ptr = std::malloc(size);if (!ptr) throw std::bad_alloc{}; // 必須拋異常或符合nothrow語義return ptr;
}

5.2 陷阱 2:operator delete的參數匹配

operator deletesize參數(C++14 起可選)必須與operator new的分配大小一致,否則可能導致未定義行為:

class MyClass {
public:static void* operator new(std::size_t size) {return std::malloc(size + 8); // 多分配8字節用于額外數據}static void operator delete(void* ptr, std::size_t size) {std::free(ptr); // 錯誤:釋放的內存大小應為size+8,但傳入的size是sizeof(MyClass)}
};

解決方案:若自定義operator new多分配了內存,需在operator delete中手動調整指針(如存儲額外數據到多分配的空間中)。

5.3 最佳實踐:與智能指針配合

std::unique_ptrstd::shared_ptr支持自定義刪除器,可與自定義operator delete結合:?

#include <memory>class CustomAllocator {
public:static void* allocate(std::size_t size) { /* 自定義分配 */ }static void deallocate(void* ptr) { /* 自定義釋放 */ }
};// 使用unique_ptr管理自定義分配的內存
std::unique_ptr<int, decltype(&CustomAllocator::deallocate)> ptr(static_cast<int*>(CustomAllocator::allocate(sizeof(int))), CustomAllocator::deallocate);

5.4 最佳實踐:性能優化建議

  • 小對象使用內存池:減少malloc調用次數,降低內存碎片;
  • 線程安全:多線程環境下,內存池需加鎖或使用線程本地存儲(TLS);
  • 對齊優先:對需要高對齊的類型(如圖像、SIMD 數據),顯式使用對齊operator new
  • 避免全局重載:全局重載影響范圍廣,優先使用類特定重載。?

六、總結

operator newoperator delete是 C++ 內存管理的 "基礎設施",通過自定義實現可以:

  • 優化高頻小對象的分配效率;
  • 實現高對齊內存的精準控制;
  • 監控內存使用,檢測泄漏;
  • 與特定場景(如游戲、嵌入式)的內存策略深度整合。

掌握這對操作符的核心邏輯,是成為高級 C++ 開發者的必經之路。在實際項目中,建議結合性能分析工具(如 Perf、Valgrind)驗證自定義分配器的效果,避免為優化而引入復雜性。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/84070.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/84070.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/84070.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[c#]判定當前軟件是否用管理員權限打開

有時一些軟件的邏輯中需要使用管理員權限對某些文件進行修改時&#xff0c;那么該軟件在執行或者打開的場合&#xff0c;就需要用使用管理員身份運行才能達到效果。那么在c#里&#xff0c;如何判定該軟件是否是對管理員身份運的呢&#xff1f; 1.取得當前的windows用戶。 2.取得…

如果在main中拋出異常,該如何處理

#采用 setDefaultUncaughtExceptionHandler 進行全局兜底 public static void main(String[] args) { Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> { System.err.println("全局捕獲異常: " ex.getMessage()); ex.printStackTrace(); System.exi…

HBM 讀的那些事

如下所示&#xff0c;為HBM讀的時序。注意這里說的HBM是和HBM3是有區別的. RL 的配置,是通過MR2來實現的 WDQS貌似和CK同頻。這幅圖告訴你&#xff0c;WDQS和CK的源頭是一樣的&#xff0c;都來自PLL&#xff0c;而且中間沒有經過倍頻操作。所以兩者頻率基本是一致的。這是HBM的…

省略號和可變參數模板

本文主要介紹如何展開可變參數的參數包 1.C語言的va_list展開可變參數 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 聲明va_list類型的變量va_list args;// 使用va_start將可變參數寫入變量argsva_start(args, count);for (in…

三十五、面向對象底層邏輯-Spring MVC中AbstractXlsxStreamingView的設計

在Web應用開發中&#xff0c;大數據量的Excel導出功能是常見需求。傳統Apache POI的XSSF實現方式在處理超大數據集時&#xff0c;會因全量加載到內存導致OOM&#xff08;內存溢出&#xff09;問題。Spring MVC提供的AbstractXlsxStreamingView通過流式處理機制&#xff0c;有效…

【大模型:知識圖譜】--3.py2neo連接圖數據庫neo4j

【圖數據庫】--Neo4j 安裝_neo4j安裝-CSDN博客 需要打開圖數據庫Neo4j&#xff0c; neo4j console 目錄 1.圖數據庫--連接 2.圖數據庫--操作 2.1.創建節點 2.2.刪除節點 2.3.增改屬性 2.4.建立關系 2.5.查詢節點 2.6.查詢關系 3.圖數據庫--實例 1.圖數據庫--連接 fr…

基于dify的營養分析工作流:3分鐘生成個人營養分析報告

你去醫院做體檢&#xff0c;需要多久拿到體檢報告呢&#xff1f;醫院會為每位病人做一份多維度的健康報告嗎&#xff1f;"人工報告需1小時/份&#xff1f;數據誤差率高達35%&#xff1f;傳統工具無法個性化&#xff1f; Dify工作流AI模型的組合拳&#xff0c;正在重塑健康…

Web后端基礎(基礎知識)

BS架構&#xff1a;Browser/Server&#xff0c;瀏覽器/服務器架構模式。客戶端只需要瀏覽器&#xff0c;應用程序的邏輯和數據都存儲在服務端。 優點&#xff1a;維護方便缺點&#xff1a;體驗一般 CS架構&#xff1a;Client/Server&#xff0c;客戶端/服務器架構模式。需要單獨…

MySQL(56)什么是復合索引?

復合索引&#xff08;Composite Index&#xff09;&#xff0c;也稱為多列索引&#xff0c;是在數據庫表的多列上創建的索引。它可以提高涉及多個列的查詢性能&#xff0c;通過組合多個列的值來索引數據。復合索引特別適用于需要同時過濾多列的查詢。 復合索引的優點 提高多列…

高并發下的緩存擊穿/雪崩解決方案

有效解決緩存擊穿和雪崩的方法包括&#xff1a;1. 使用互斥鎖處理緩存擊穿&#xff1b;2. 采用熔斷器模式防止雪崩&#xff1b;3. 實施緩存預熱和降級策略&#xff1b;4. 利用分片和多級緩存分散請求壓力。這些方法各有優劣&#xff0c;需根據實際業務場景靈活調整和結合使用。…

【讀論文】OpenAI o3與o4系統模型技術報告解讀

回顧一下,4月16日,OpenAI發布了一份關于其o系列新模型——OpenAI o3和OpenAI o4-mini——的System Card。這份文檔不僅揭示了這兩款模型在推理能力和工具使用方面的顯著進步,也詳細闡述了其訓練方法、數據來源、安全評估以及在圖像理解生成、數學推理等多個核心領域的表現。…

第1課、LangChain 介紹

LangChain 介紹 LangChain 是一個以大語言模型&#xff08;LLM, Large Language Model&#xff09;為核心的開發框架&#xff0c;旨在幫助開發者高效地將如 GPT-4 等大型語言模型與外部數據源和計算資源集成&#xff0c;構建智能化應用。 1.1 工作原理 如上圖所示&#xff…

【論文閱讀28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆開、篩優質因子&#xff0c;再用 CNN-BiLSTM-Attention 來動態預測每個子序列&#xff0c;最后重構出總位移&#xff0c;預測效果超越傳統模型。 文章目錄 1 引言2 方法2.1 位移時間序列加性模型2.2 變分模態分解 (VMD) 具體步驟2.3.1 樣本熵&#xff08;S…

[論文閱讀] 人工智能+軟件工程(軟件測試) | 當大語言模型遇上APP測試:SCENGEN如何讓手機應用更靠譜

當大語言模型遇上APP測試&#xff1a;SCENGEN如何讓手機應用更靠譜&#xff1f; 一、論文基礎信息 論文標題&#xff1a;LLM-Guided Scenario-based GUI Testing&#xff08;《大語言模型引導的基于場景的GUI測試》&#xff09;作者及機構&#xff1a;Shengcheng Yu等&#x…

香橙派3B學習筆記7:snap安裝管理軟件包_打包程序與依賴

有時可以嘗試把程文件與其依賴一塊打包安裝&#xff0c;這里就學習一下。 ssh &#xff1a; orangepi本地ip 密碼 &#xff1a; orangepi 操作系統發行版&#xff1a; 基于 Ubuntu 20.04.6 LTS&#xff08;Focal Fossa&#xff09;的定制版本&#xff0c;專門為 Orange Pi 設備…

Playwright 測試框架 - .NET

??親愛的技術愛好者們,熱烈歡迎來到 Kant2048 的博客!我是 Thomas Kant,很開心能在CSDN上與你們相遇~?? 本博客的精華專欄: 【自動化測試】

Model Context Protocol (MCP) 是一個前沿框架

微軟發布了 Model Context Protocol (MCP) 課程&#xff1a;mcp-for-beginners。 Model Context Protocol (MCP) 是一個前沿框架&#xff0c;涵蓋 C#、Java、JavaScript、TypeScript 和 Python 等主流編程語言&#xff0c;規范 AI 模型與客戶端應用之間的交互。 MCP 課程結構 …

【量化】策略交易 - 均線策略(Moving Average Strategy)- 代碼增強版本

策略交易 - 均線策略&#xff08;Moving Average Strategy&#xff09;- 代碼增強版本 一、前言 本文主要是針對 【量化】策略交易 - 均線策略&#xff08;Moving Average Strategy&#xff09; 中的代碼事例&#xff0c;進行邏輯的增強&#xff0c;添加了模擬買入和賣出邏輯&…

為什么要引入內聯函數?

C 中引入內聯函數主要有以下幾個目的&#xff1a; 提高程序運行效率 - 普通函數調用會有一定的開銷&#xff0c;如保存現場、傳遞參數、跳轉到函數地址執行等。內聯函數在編譯時&#xff0c;會將函數體直接插入到調用處&#xff0c;避免了函數調用的開銷&#xff0c;從而提高程…

C++.OpenGL (17/64)模型(Model)

模型(Model) 模型系統架構 #mermaid-svg-Zaji5BPdvnIkXIVg {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Zaji5BPdvnIkXIVg .error-icon{fill:#552222;}#mermaid-svg-Zaji5BPdvnIkXIVg .error-text{fill:#55222…