C++17中在单次内存分配内合法构造对象的方法问询
在同一块内存分配中存储不同类型对象的合法性分析
原问题代码
#include<cstdlib> #include<new> struct A { int a; int b; }; struct B { int c; }; int main() { void* storage = std::malloc(1024); char* ptr = reinterpret_cast<char*>(storage); ptr += 16; new(ptr) A; ptr += sizeof(A); // construct other objects in the same way new(ptr) B; }
代码合法性分析
根据已被采纳且具有追溯效力的P0593提案,reinterpret_cast<char*>(storage)会隐式创建一个char数组,因此初始的指针运算ptr += 16是定义良好的。
但执行new(ptr) A时,会覆盖ptr到ptr + sizeof(A) - 1范围内的内存,结束该范围内隐式创建的char元素的生命周期。此时ptr指向的是已结束生命周期的对象,后续的ptr += sizeof(A)属于未定义行为(UB)。
基于此,相关问题的答案中存在UB,因为其在placement new后执行了指针递增操作。
可行解决方案
1. 使用std::launder(仍存在风险)
new(ptr) A; // std::launder(ptr)以char*指向已构造的A实例,char指针可别名任何对象 ptr = std::launder(ptr) + sizeof(A); // ptr += 16; // 仍为UB
这种方式仍可能存在UB:std::launder(ptr)指向的是A对象而非数组对象,对非数组对象进行指针运算属于非法操作,例如后续执行ptr += 128仍为UB。
2. 先递增指针再执行placement new(推荐)
ptr += sizeof(A); // 预先将指针移至A对象之后的位置 new(ptr - sizeof(A)) A; // ptr始终指向隐式创建的char数组中存活的元素,后续指针运算合法
通过预先移动指针,避免ptr指向已销毁的对象,确保后续所有指针操作都基于存活的char数组元素,完全符合标准定义。
3. 先构造对象再用std::memcpy复制(简单可靠)
先在栈或其他内存区域构造对象,再通过std::memcpy将对象内容复制到目标内存区域。这种方式逻辑简单,完全符合标准,不存在UB风险。
结论
方案2和方案3是定义良好的可行解决方案。
内容的提问来源于stack exchange,提问作者wanwan
相关产品推荐
相关产品推荐

