
内存管理是 C++ 的核心能力之一,而operator new和operator delete作为内存分配的底层接口,是理解 C++ 对象生命周期的关键。
operator new与operator delete的本质在 C++ 中,new和delete有两层含义:
int* p = new int(10);,负责内存分配 + 对象构造(或delete p;负责对象析构 + 内存释放)。operator new和operator delete,是new/delete表达式调用的底层内存分配 / 释放函数。核心关系:new表达式的执行流程是:
operator new分配原始内存;delete表达式的流程是:operator delete释放内存。operator new接口C++ 标准库定义了多组operator new和operator 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;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;特性 | 说明 |
|---|---|
全局默认实现 | 标准库提供的operator new底层调用malloc,operator 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表达式new表达式的底层步骤operator new:分配足够大的内存(大小为sizeof(MyClass))。MyClass::MyClass(int)。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 new与operator delete标准库的operator new基于malloc实现,虽然通用但可能在特定场景下效率不足(如高频小对象分配导致内存碎片)。通过自定义operator new,可以实现内存池、对齐优化、性能监控等高级功能。
场景 | 自定义方案 |
|---|---|
减少内存碎片 | 实现小对象内存池(如每 8 字节为一个块,预分配连续内存) |
提升分配速度 | 绕过malloc的全局锁,使用线程本地内存池 |
内存对齐优化 | 为特定类型(如图像数据、SIMD 指令数据)提供更高对齐的内存 |
内存泄漏检测 | 在分配时记录内存地址,释放时检查是否重复释放 |
调试与监控 | 统计各类型的内存使用量,定位内存分配热点 |
operator new重载最常见的自定义方式是为某个类单独重载operator new和operator 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); // 大对象直接调用free
return;
}
// 简单内存池不回收单个对象,实际可扩展为空闲链表
}
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大小)malloc;operator new和operator delete通过静态MemoryPool实例管理内存;operator new的重载全局重载会影响所有未显式定义类特定operator new的类型,需谨慎使用。常见场景是实现全局内存监控或调试工具。
示例:全局内存分配监控
通过全局重载operator new和operator 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)更高效。align_val_t对于需要高对齐的场景(如 SIMD 指令处理、GPU 数据传输),C++17 引入了align_val_t类型,允许自定义对齐的内存分配。
现代 CPU 对内存对齐有严格要求:
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释放。标准库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;
}operator delete的参数匹配operator delete的size参数(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中手动调整指针(如存储额外数据到多分配的空间中)。
std::unique_ptr和std::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);malloc调用次数,降低内存碎片;operator new;operator new和operator delete是 C++ 内存管理的 "基础设施",通过自定义实现可以:
掌握这对操作符的核心逻辑,是成为高级 C++ 开发者的必经之路。在实际项目中,建议结合性能分析工具(如 Perf、Valgrind)验证自定义分配器的效果,避免为优化而引入复杂性。