是否可以以不导致UB的方式分配未初始化的数组? [英] Is it possible to allocatate uninialized array in a way that does not result in UB?

查看:74
本文介绍了是否可以以不导致UB的方式分配未初始化的数组?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

When implementing certain data structures in C++ one needs to be able to create an array that has uninitialized elements. Because of that, having

buffer = new T[capacity];

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

is not suitable, as new T[capacity] initializes the array elements, which is not always possible (if T does not have a default constructor) or desired (as constructing objects might take time). The typical solution is to allocate memory and use placement new.

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

For that, if we know the number of elements is known (or at least we have an upper bound) and allocate on stack, then, as far as I am aware, one can use an aligned array of bytes or chars, and then use std::launder to access the members.

alignas(T) std::byte buffer[capacity];

但是,它只能解决堆栈分配问题,但不能解决堆分配问题.为此,我假设需要使用对齐的new,并编写如下内容:

However, it solves the problem only for stack allocations, but it does not solve the problem for heap alloations. For that, I assume one needs to use aligned new, and write something like this:

auto memory =  ::operator new(sizeof(T) * capacity, std::align_val_t{alignof(T)});

,然后将其强制转换为 std :: byte * unsigned char * T * .

and then cast it either to std::byte* or unsigned char* or T*.

// not sure what the right type for reinterpret cast should be
buffer = reinterpret_cast(memory);

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

However, there are several things that are not clear to me.

  1. 如果ptr指向可与T进行指针互转换的对象,则定义结果 reinterpret_cast< T *>(ptr)(请参阅 https://eel.is/c++draft/expr.add#4 ).
  2. 关于生命周期,类型为 unsigned char std :: byte 的类型的对象可以为新放置的结果提供存储空间(
  1. The result reinterpret_cast<T*>(ptr) is defined if ptr points an object that is pointer-interconvertible with T. (See this answer or https://eel.is/c++draft/basic.types#basic.compound-3) for more detail. I assume, that converting it to T* is not valid, as T is not necessarily pointer-interconvertible with result of new. However, is it well defined for char* or std::byte?
  2. When converting the result of new to a valid pointer type (assuming it is not implementation defined), is it treated as a pointer to first element of array, or just a pointer to a single object? While, as far as I know, it rarely (if at all) matters in practice, there is a semantic difference, an expression of type pointer_type + integer is well defined only if pointed element is an array member, and if the result of arithmetic points to another array element. (see https://eel.is/c++draft/expr.add#4).
  3. As for lifetimes are concerned, an object of type array unsigned char or std::byte can provide storage for result of placement new (https://eel.is/c++draft/basic.memobj#intro.object-3), however is it defined for arrays of other types?
  4. As far as I knowT::operator new and T::operator new[] expressions call ::operator new or ::operator new[] behind the scenes. Since the result of builtin new is void, how conversion to the right type is done? Are these implementation based or we have well defined rules to handle these?
  5. When freeing the memory, should one use

::operator delete(static_cast<void*>(buffer), sizeof(T) * capacity, std::align_val_t{alignof(T)});

还是有另一种方法?

PS:我可能会在真实代码中使用标准库来实现这些目的,但是我试图了解事物在幕后的工作方式.

PS: I'd probably use the standard library for these purposes in real code, however I try to understand how things work behind the scenes.

谢谢.

推荐答案

指针互转换

关于指针的互转换性,使用 T * {[unsigned] char | std :: byte} * 都没有关系.您必须将其强制转换为 T * 才能使用.

Regarding pointer-interconvertibility, it doesn't matter if you use T * or {[unsigned] char|std::byte} *. You will have to cast it to T * to use it anyway.

请注意,您必须(在转换结果上)调用 std :: launder 来访问指向的 T 对象.唯一的例外是创建对象的新放置调用,因为它们尚不存在.手动的析构函数调用不是 例外.

Note that you must call std::launder (on the result of the cast) to access the pointed T objects. The only exception is the placement-new call that creates the objects, because they don't exist yet. The manual destructor call is not an exception.

仅当您不使用 std :: launder 时,缺少指针互转换性才是问题.

The lack of pointer-interconvertibility would only be a problem if you didn't use std::launder.

当将new的结果转换为有效的指针类型(假设它没有实现定义)时,会将其视为指向数组第一个元素的指针,还是仅指向单个对象的指针?

When converting the result of new to a valid pointer type (assuming it is not implementation defined), is it treated as a pointer to first element of array, or just a pointer to a single object?

如果要更加安全,请将指针存储为 {[unsigned] char | std :: byte} * ,并在执行任何指针算术后将其存储为 reinterpret_cast .

If you want to be extra safe, store the pointer as {[unsigned] char|std::byte} * and reinterpret_cast it after peforming any pointer arithmetic.

一个数组类型为 unsigned char std :: byte 的对象可以为新放置的结果提供存储空间

an object of type array unsigned char or std::byte can provide storage for result of placement new

该标准没有在任何地方说提供存储"是新放置需要的.我认为该术语仅定义为可用于标准中其他术语的定义.

The standard doesn't say anywhere that "providing storage" is required for placement-new to work. I think this term is defined solely to be used in definitions of other terms in the standard.

考虑 [basic.life]/example-2 ,其中即使类型 T 不会提供存储",相同类型的 T .

Consider [basic.life]/example-2 where operator= uses placement-new to reconstruct an object in place, even though type T doesn't "provide storage" for the same type T.

由于内置new的结果为空,如何转换为正确的类型?

Since the result of builtin new is void, how conversion to the right type is done?

不确定标准对此要说些什么,但是除了 reinterpret_cast 之外,它还能是什么?

Not sure what the standard has to say about it, but what else can it be other than reinterpret_cast?

释放内存

您的方法看起来是正确的,但我认为您不这样做"t 必须传递大小.

Your approach looks correct, but I think you don't have to pass the size.

这篇关于是否可以以不导致UB的方式分配未初始化的数组?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆