C++中operator new与placement new使用后的内存释放合法性疑问
堆上分配对象时额外预留内存的几种写法合法性分析
1. 标准实现:合法且符合规范
你给出的第一种写法是该场景下的标准合规实现,在C++14及所有后续标准中均有效:
#include <new> void f(std::size_t n) { void* buf = ::operator new(sizeof(SomeClass) + n); SomeClass* obj = new(buf) SomeClass; // 使用obj进行操作 obj->~SomeClass(); ::operator delete(buf); }
- 流程清晰:先通过
::operator new分配足够容纳SomeClass与额外n字节的内存,再用placement new在该内存块上构造对象; - 销毁逻辑正确:手动调用析构函数完成对象资源清理,再通过原分配的
buf指针释放整块内存; - 兼容性:C11到C20及之后版本均支持,无版本差异。
2. 用obj转void*调用::operator delete:合法(前提未重载placement new)
第二种写法的合法性依赖默认placement new的行为:
#include <new> void f(std::size_t n) { void* buf = ::operator new(sizeof(SomeClass) + n); SomeClass* obj = new(buf) SomeClass; // 使用obj进行操作 obj->~SomeClass(); ::operator delete(static_cast<void*>(obj)); }
在你假设未重载new的前提下,C++标准规定:默认的placement new(即void* operator new(std::size_t, void*))会直接返回传入的内存指针。同时::operator new返回的内存地址满足SomeClass的对齐要求,因此obj与buf地址完全一致。此时将obj转为void*传给::operator delete,等价于传入原buf指针,完全合法。
若自定义了SomeClass的placement new,则需确保该重载返回的指针与::operator new分配的起始地址一致,否则会导致未定义行为。
3. 直接delete obj:完全不合法
第三种写法存在严重的标准违规问题:
#include <new> void f(std::size_t n) { void* buf = ::operator new(sizeof(SomeClass) + n); SomeClass* obj = new(buf) SomeClass; // 使用obj进行操作 delete obj; }
C++标准明确规定:delete表达式仅能用于普通new表达式(即new SomeClass形式)创建的对象。而placement new构造的对象不属于普通new表达式的产物:
delete obj会先调用SomeClass的析构函数(这一步本身没问题),但随后会调用::operator delete(obj);- 普通
delete默认认为分配的内存大小是sizeof(SomeClass),但实际分配的是sizeof(SomeClass)+n。即便部分编译器可能“碰巧”运行正常,这仍属于未定义行为,在任何C++版本中均不合法。
4. 重载带参数的operator new后用delete obj:合法(需注意细节)
你补充的重载写法合法,且在C++14及后续标准中均有效:
#include <new> enum class ExtraSpace : std::size_t {}; void* operator new(std::size_t size, ExtraSpace n) { return ::operator new(size + static_cast<std::size_t>(n)); } void f(std::size_t n) { SomeClass* obj = new(ExtraSpace(n)) SomeClass; // 使用obj进行操作 delete obj; }
合法性依据:
- C++标准允许重载带参数的
operator new,对应的new(ExtraSpace(n)) SomeClass表达式会调用该重载,分配sizeof(SomeClass)+n的内存后构造对象; - 对象构造成功后,
delete obj会先调用SomeClass的析构函数,随后调用普通的::operator delete(void*); obj就是重载operator new返回的起始指针,符合::operator delete的参数要求,因此完全合法。
注意事项:
- 若需处理构造异常,建议同时重载对应的placement delete:
void operator delete(void*, ExtraSpace),这样当SomeClass构造函数抛出异常时,编译器会自动调用该重载释放内存; - 该写法在C11及之后版本均有效,C03中支持重载带参数的
operator new,但enum class是C11引入的,如需兼容C03需改为普通枚举或其他参数类型。
内容的提问来源于stack exchange,提问作者Noam Elul
相关产品推荐
相关产品推荐

