C++中placement-new创建对象的数组指针算术是否存在未定义行为?
关于C++容器
Box的未定义行为分析 首先看核心代码:
template<class T> class Box { public: using DataType = T; Box() : sz(def_size), data_ptr(allocate(def_size)) {} T* data() { return reinterpret_cast<T*>(data_ptr); } protected: using BlockType = alignas(alignof(T)) char[sizeof(T)]; BlockType * allocate(size_t sz) { BlockType* ptr = new BlockType[sz]; for(auto p = ptr, ptrE = ptr + sz; ptr != ptrE; ++ptr ) { auto* obj = new(p) T; // do some initialization on obj } return ptr; } private: size_t sz; BlockType *data_ptr; };
问题1:data()返回的指针能否当作数组元素指针进行算术操作?
不能,这种操作属于未定义行为。
C++标准对指针算术有明确限制:只有指向同一个数组对象的元素(或数组末尾的合法尾后位置)的指针,才能进行合法的指针算术运算(如ptr + n、ptr++)。
在Box的实现中,allocate函数创建的是BlockType类型的数组,每个BlockType元素的内存上单独用placement new构造了一个T对象。这些T对象是独立的个体,并没有构成一个真正的T类型数组——内存中不存在T[]类型的对象,只是多个T对象恰好连续存放。
因此,data()返回的T*仅指向第一个独立的T对象,将其当作数组指针进行算术操作(比如data() + 1),完全不符合标准对指针算术的合法场景要求,属于未定义行为。
问题2:块指针是否一定等同于T实例指针?
在当前实现的内存布局下,地址值大概率相同,但标准不保证这种等价性,部分场景下可能存在未定义行为。
- 地址值的巧合:
BlockType被定义为alignas(alignof(T)) char[sizeof(T)],意味着每个BlockType的起始地址满足T的对齐要求,且内存大小恰好等于sizeof(T)。用placement new在BlockType的起始地址构造T对象时,T对象的起始地址必然和BlockType元素的起始地址一致,因此reinterpret_cast<T*>(p)(p为BlockType*)的地址值通常会和new(p) T返回的T*相等。 - 标准层面的不确定性:C++标准并未规定
reinterpret_cast在这种跨类型转换下的行为必须等价于placement new返回的指针。虽然实际中多数编译器会给出相同的地址,但这属于实现定义行为,而非标准强制要求。另外,如果T是非标准布局类型,这种转换的合法性会更模糊,甚至直接触发未定义行为。
补充:你提到的对齐问题,alignas(alignof(T))是标准允许的语法——只要alignof(T)是当前编译器支持的有效对齐值,编译器就必须遵守该对齐要求;如果alignof(T)超过了编译器支持的最大对齐,代码会直接编译失败,而非运行时的未定义行为。
内容的提问来源于stack exchange,提问作者Swift - Friday Pie
相关产品推荐
相关产品推荐

