std::unitialized_copy 是否有未定义的行为?

Does std::unitialized_copy have undefined behavior?

为 class 类型 T 的对象构造缓冲区(比如环形缓冲区)时的一个常见习惯用法是使用从 std::malloc() 或运算符获得的内存地址初始化 T* 对象new(),然后根据需要使用 placement new 在该缓冲区中构造对象,使用 T 指针上的指针算法遍历内存块。

虽然似乎极不可能有任何编译器对此不起作用(它确实适用于 g++ 和 clang++),但在我看来,严格来说这可能具有未定义的行为。这是因为 C++17 的 §8.7/4 似乎只允许对数组进行指针运算,而 malloc、operator new 或 operator new[] 返回的一块内存不是数组——据我所知,只有 new[ ] 表达式可以在动态内存中创建一个数组,从而在构造时完全初始化。

这也让我想到 std::uninitialized_copy 的参考实现对于动态分配的未初始化内存有未定义的行为,因为它在 C++17 的 §23.10.10.4/1 中的参考实现在目标迭代器,这里是一个指针。

可以说,如果未初始化的内存是非动态获得的,那么同样适用于 std::uninitialized_copy,比如使用 unsigned char 或 std::byte 的对齐数组,如 C+ 的 §4.5/3 所允许的+17,因为 §8.7/4 中的算术意味着执行算术的目标指针类型应该是数组元素类型(unsigned char 或 std::byte),而不是使用展示位置新。

这似乎令人惊讶。谁能指出这个推理中的缺陷(如果有的话)。

是的,从 mallocoperator new 返回的指针上的指针算术具有未定义的行为,没有以前的数组放置新(它本身不能可靠地完成),使用 std::unintialized_copy 在上面,它被定义为表现得好像完成了这个指针算法。

你能做的最好的就是创建一个std::byte(或unsigned char)数组作为存储,直接使用new[],然后placement-new individual objects into the storage array ,这将使这些对象 嵌套 在缓冲区数组中。

存储数组上的指针算法定义明确,因此您可以使用 std::launder 或使用从 placement-new 返回的指针到达存储数组中单个对象位置的指针您可以获得指向嵌套对象的指针。即使那样,您也无法对指向嵌套对象的指针使用指针算法,因为它们不构成数组。

见论文P0593R5 隐式创建用于低级对象操作的对象 以获取有关这种令人惊讶的未定义行为的更多示例以及在未来对其进行定义的建议。