最近我突然想到 placement new 可以直接在指定位置直接构造对象而不分配内存,于是有了这篇文章。
1. 一般情况下,new
在堆上分配内存
在大多数情况下,new
确实会在 堆(heap) 上分配内存:
int* p = new int(42); // 在堆上分配内存
- 堆内存 由 C++ 运行时库(如
malloc
/free
)管理。 - 必须手动释放,否则会导致内存泄漏:
delete p; // 释放堆内存
2. new
还可以在栈上分配内存
C++ 提供了 placement new(定位 new
),允许在指定的内存地址上构造对象,而 不分配新内存。因此,我们可以让 new
在栈上分配内存:
#include <new>
void demo() {
alignas(int) char buffer[sizeof(int)]; // 栈上的内存
int* p = new (buffer) int(42); // 在栈上构造 int
std::cout << *p << std::endl; // 输出 42
// 不需要 delete,但要手动调用析构函数(如果是类对象)
p->~int(); // 对于 int 这种 trivial 类型,可以省略
}
关键点
buffer
是栈上的数组,new (buffer) int
只是在该地址构造对象。- 不能使用
delete p
,因为内存是栈上的,delete
会尝试释放堆内存,导致未定义行为(UB)。 - 必须确保内存对齐(如
alignas(int)
),否则某些 CPU 架构(如 ARM)会崩溃。
3. new
还可以在其他存储区域分配内存
除了堆和栈,new
还可以在 自定义内存池、共享内存 或 硬件寄存器映射内存 上分配内存,只要提供正确的地址即可。
示例:在静态存储区分配
#include <new>
char static_buffer[sizeof(int)]; // 静态存储区(全局变量)
int main() {
int* p = new (static_buffer) int(100); // 在静态区构造对象
std::cout << *p << std::endl;
p->~int();
return 0;
}
static_buffer
位于程序的 数据段(data segment),而不是堆或栈。
示例:在内存池中分配
#include <new>
#include <vector>
class MemoryPool {
public:
void* allocate(size_t size) {
return pool.data(); // 返回内存池地址
}
private:
std::vector<char> pool{1024}; // 模拟内存池
};
int main() {
MemoryPool pool;
int* p = new (pool.allocate(sizeof(int))) int(200); // 在内存池构造
std::cout << *p << std::endl;
p->~int();
return 0;
}
- 这里
new
在自定义的内存池上分配,而不是堆或栈。
4. 为什么需要 placement new
?
(1)避免堆分配的开销
- 高频小对象(如游戏中的粒子系统)可以使用栈或内存池,减少
new/delete
的代价。
(2)控制对象的内存位置
- 嵌入式系统可能需要将对象放在特定地址(如硬件寄存器)。
- 共享内存(IPC)需要在固定地址构造对象。
(3)实现自定义内存管理
- 内存池、对象池等技术依赖
placement new
在预分配的内存上构造对象。
因此,new
分配的内存不一定在堆上,它的行为取决于具体的使用方式。理解这一点有助于优化性能并避免未定义行为。