placement new 不能直接用于数组,因为标准未强制实现 operator new[](size_t, void*),编译器通常不提供该重载;new (ptr) t[n] 实际触发 operator new[](size_t) 查找,若未定义则报错,必须手动循环调用 placement new 单个构造并显式析构。

placement new 为什么不能直接 new[]
因为 operator new[](size_t, void*) 并未被标准强制要求提供,多数编译器(GCC、Clang、MSVC)根本不实现它。你写 new (ptr) int[10] 看似合法,实际触发的是 operator new[](size_t) 的重载查找——它会尝试分配内存,而不是在指定地址构造;若没定义该全局重载,就直接报错:error: no matching function for call to 'operator new[](long unsigned int, int*)'。
所以「在指定地址创建数组」必须手动循环调用 placement new 单个对象,不能依赖 new[] 语法糖。
手动构造数组的正确写法
核心是:分配足够内存 → 逐个构造 → 记录起始地址和长度 → 析构时反向调用 ~T()。
- 确保目标内存对齐:用
alignof(T)检查,或直接用std::aligned_storage_t/std::aligned_alloc - 构造必须用
new (ptr + i * sizeof(T)) T{...},不能用reinterpret_cast强转后赋值(绕过构造函数) - 初始化列表需显式写出,比如
T{0}或T{1,2,3},默认构造用T{} - 务必保存原始指针(
void*或char*),否则析构时无法遍历
示例:
char buffer[sizeof(int) * 5];
int* arr = nullptr;
for (int i = 0; i (buffer);
new (arr + i) int{i * 2}; // 注意:不是 new (buffer + i) int,而是按元素偏移
}
// 后续使用 arr[0]..arr[4]
// 析构:
for (int i = 4; i >= 0; --i) {
(arr + i)->~int();
}
常见坑:对齐错误与生命周期混淆
最常踩的两个雷:
-
char buffer[N]可能不对齐:比如double要求 8 字节对齐,但char数组起始地址未必满足——结果调用placement new触发 UB(尤其在 ARM 或严格检查的 sanitizer 下崩溃) - 把 placement new 构造的对象当成普通栈/堆对象用:比如传给
std::vector、用delete[] arr、或让其超出作用域自动析构——这些都会跳过自定义析构逻辑,导致资源泄漏或 double-delete
安全做法:用 std::aligned_storage_t<sizeof alignof></sizeof> 替代裸 char 数组;所有构造/析构必须显式配对,且只在明确控制生命周期的场景下使用(如自定义 allocator、内存池、嵌入式固定缓冲区)。
替代方案:何时该放弃 placement new
除非你真需要零分配开销、确定内存布局、或对接硬件寄存器映射,否则别硬上 placement new 数组。
- 想复用内存?用
std::vector::data()+resize()更安全 - 要避免堆分配?考虑
std::array(编译期大小)或std::span(视图) - 做高性能缓存?优先封装成类,把构造/析构逻辑收进
construct_at/destroy_at(C++20)或 RAII wrapper
真正需要手动 placement new 数组的场景极少,一旦选了这条路,就得全程自己扛对齐、异常安全、析构顺序——漏掉任意一环,问题不会立刻暴露,而是在特定平台或优化级别下突然崩掉。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











