是否可以以不会导致 UB 的方式分配未初始化的数组?

Raz*_*ius 5 c++ memory-management void-pointers dynamic-memory-allocation reinterpret-cast

在 C++ 中实现某些数据结构时,需要能够创建一个包含未初始化元素的数组。正因为如此,拥有

buffer = new T[capacity];
Run Code Online (Sandbox Code Playgroud)

不适合,因为new T[capacity]初始化数组元素,这并不总是可能的(如果 T 没有默认构造函数)或期望的(因为构造对象可能需要时间)。典型的解决方案是分配内存并使用placement new。

为此,如果我们知道元素的数量是已知的(或者至少我们有一个上限)并在堆栈上分配,那么,据我所知,可以使用对齐的字节或字符数组,然后使用std::launder访问成员。

alignas(T) std::byte buffer[capacity];
Run Code Online (Sandbox Code Playgroud)

但是,它只解决了栈分配的问题,并没有解决堆分配的问题。为此,我假设需要使用对齐新,并写这样的东西:

auto memory =  ::operator new(sizeof(T) * capacity, std::align_val_t{alignof(T)});
Run Code Online (Sandbox Code Playgroud)

然后将其转换为std::byte*orunsigned char*T*

// not sure what the right type for reinterpret cast should be
buffer = reinterpret_cast(memory);
Run Code Online (Sandbox Code Playgroud)

但是,有几件事我不清楚。

  1. reinterpret_cast<T*>(ptr)如果 ptr 指向可与 T 进行指针互转换的对象,则定义结果。(请参阅此答案https://eel.is/c++draft/basic.types#basic.compound-3)了解更多详细信息。我假设,将其转换T*为无效,因为 T 不一定与 new 的结果是指针可互转换的。但是,它是否为char*或定义明确std::byte
  2. 当将 的结果转换new为有效的指针类型(假设它不是实现定义的)时,它是否被视为指向数组第一个元素的指针,或者只是指向单个对象的指针?虽然,据我所知,在实践中很少(如果有的话)很重要,但存在语义差异,pointer_type + integer只有当指向的元素是数组成员并且算术结果指向另一个时,类型表达式才被很好地定义数组元素。(见https://eel.is/c++draft/expr.add#4)。
  3. 至于生命周期,数组类型的对象unsigned charstd::byte可以为新放置的结果提供存储(https://eel.is/c++draft/basic.memobj#intro.object-3),但是它是为其他类型的数组?
  4. 据我所知T::operator newT::operator new[]表情调用::operator new::operator new[]幕后。既然builtin的结果new是void,那么如何转换成正确的类型呢?这些是基于实现的还是我们有明确定义的规则来处理这些?
  5. 释放内存时,应该使用
::operator delete(static_cast<void*>(buffer), sizeof(T) * capacity, std::align_val_t{alignof(T)});
Run Code Online (Sandbox Code Playgroud)

还是有另一种方法?

PS:我可能会在实际代码中将标准库用于这些目的,但是我试图了解幕后的工作原理。

谢谢。

小智 0

我认为你的前提可能是不正确的。如果 T 是一个类,则应调用默认构造函数。但是,它可以为空,并且如果您的类包含所有 POD(普通旧数据),则不会初始化任何内容。实际上,我一直依赖这一点,因为出于性能原因,我经常不希望对事物进行初始化。

我相信对于全局数据等有一些警告,其中有些东西是零初始化的。但一般来说堆东西不是。你可以测试一下,你会发现内存中有一堆垃圾,至少在发布模式下编译时是这样。一些编译器会在调试模式下初始化内存,但这是在构造函数之外完成的。

例如,您可以在自定义放置新函数中设置数据,如果它是 POD,它仍然会存在于构造函数中。有些人会认为这是 UB,但我认为标准对 POD 说“什么也没做”,这意味着没有初始化。