【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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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…