C++中用指向已销毁对象的指针访问重建对象是否为未定义行为
C++同地址重建对象的指针访问合法性分析
核心规则前置
基于C++标准[basic.life]章节的规定,指向已销毁对象的指针,可在满足以下全部条件时,合法访问同存储地址重建的新对象,否则属于未定义行为:
- 新对象与原指针指向的对象类型完全相同(忽略顶层cv限定符)
- 原对象不是const限定的完整对象,也不是const完整对象的const子对象
- 新对象完全覆盖原对象占据的存储位置
- 若原对象是某个完整对象的子对象,新对象的类型与原子对象类型一致
析构函数调用要求
在同存储地址重建对象前,是否需要调用原对象的析构函数,取决于原对象的析构特性:
- 若原对象的析构函数非平凡(存在用户自定义析构逻辑、持有RAII资源、成员包含非平凡析构类型等):必须显式调用原对象的析构函数,否则属于未定义行为,会导致资源泄漏、逻辑异常等问题
- 若原对象的析构函数平凡(所有成员为内置类型、无自定义析构的聚合类型等):可以不显式调用析构函数,placement new操作覆盖存储时会自动结束原对象的生命周期,不属于UB
示例代码逐段合法性判断
初始构造阶段
alignas(full) std::byte storage[/* plenty of storage */]; full * pf = new (storage) full; basic * pb = &pf->base;
这部分操作完全合法,pf指向存储中新创建的full对象,pb指向该full对象的base子对象。
第一次重建basic对象
new (storage) basic; // supposedly ends lifetime of *pf (right?) // if doesn't, suppose we did pf->~full(); before this line
- 首先确认:这行placement new确实会结束原
*pf的生命周期,因为新创建的basic对象覆盖了原full对象的起始存储 - 本示例中
full是平凡析构类型,不调用pf->~full()直接重建不属于UB;如果full是非平凡析构类型,则必须先调用析构函数
pb->a; // is this legal?
✅ 合法。pb原指向的basic子对象与新创建的独立basic对象类型完全一致、存储地址完全匹配,满足指针复用规则,这里访问的是新basic对象的a成员,值为1。
第二次重建full对象
new (storage) full; // supposedly ends lifetime of *pb (right?) // if doesn't, suppose we did pb->~basic(); before this line
- 这行placement new确实会结束原
*pb的生命周期,新创建的full对象完全覆盖了原basic对象的存储 - 本示例中
basic是平凡析构类型,不调用pb->~basic()直接重建不属于UB;如果basic是非平凡析构类型,则必须先调用析构函数
pb->a; // is this still legal?
✅ 合法。pb原指向的独立basic对象与新full对象的base子对象类型完全一致、存储地址完全匹配,满足指针复用规则,访问行为合法。
pf->b; // is this legal?
✅ 合法。pf原指向的full对象与新创建的full对象类型完全一致、存储地址完全匹配,满足指针复用规则,访问的是新full对象的b成员,值为2。
pf->base.a; // is this legal?
✅ 合法。pf已经合法指向新的full对象,访问其内部子对象的成员属于正常合法操作。
内容的提问来源于stack exchange,提问作者CppNerd13373
相关产品推荐
相关产品推荐

