如何创建一个数组并开始它的生命周期而不开始它的任何元素的生命周期?
Posted
技术标签:
【中文标题】如何创建一个数组并开始它的生命周期而不开始它的任何元素的生命周期?【英文标题】:How to create an array and start its lifetime without starting the lifetime of any of its elements? 【发布时间】:2021-06-18 05:10:03 【问题描述】:任何类型的数组都是implicit-lifetime objects,也可以是begin the lifetime of implicit-lifetime object, without beginning the lifetime of its subobjects。
据我所知,在不以不会导致 UB 的方式开始其元素的生命周期的情况下创建数组的可能性是隐式生命周期对象的动机之一,请参阅http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0593r6.html。
现在,正确的做法是什么?分配内存并返回指向数组的指针就足够了吗?或者还有什么需要注意的?
也就是说,这段代码是否有效,它是否创建了一个包含未初始化成员的数组,或者我们仍然有 UB?
// implicitly creates an array of size n and returns a pointer to it
auto arrPtr = reinterpret_cast<T(*)[]>(::operator new(sizeof(T) * n, std::alignval_talignof(T)) );
// is there a difference between reinterpret_cast<T(*)[]> and reinterpret_cast<T(*)[n]>?
auto arr = *arrPtr; // de-reference of the result in previous line.
这个问题可以重述如下。
根据https://en.cppreference.com/w/cpp/memory/allocator/allocate,allocate
函数函数在存储中创建T[n]
类型的数组并开始其生命周期,但不会开始其任何元素的生命周期。强>
一个简单的问题 - 它是如何完成的? (忽略constexpr
部分,但我不介意答案中是否也解释了constexpr
部分)。
PS:提供的代码对 c++20 有效(假设它是正确的),但据我所知,不适用于早期标准。
我相信这个问题的答案也应该回答我之前提出的两个类似问题。
-
Arrays and implicit-lifetime object
creation。
Is it possible to allocatate uninialized array in a way that does
not result in
UB。
编辑:我添加了一些代码 sn-ps,以使我的问题更清楚。我将不胜感激,解释哪些是有效的,哪些是无效的。
PS:请随意将malloc
替换为对齐版本,或::operator new
变体。据我所知,这并不重要。
示例 #1
T* allocate_array(std::size_t n)
return reinterpret_cast<T*>( malloc(sizeof(T) * n) );
// does it return an implicitly constructed array (as long as
// subsequent usage is valid) or a T* pointer that does not "point"
// to a T object that was constructed, hence UB
// Edit: if we take n = 1 in this example, and T is not implicit-lifetime
// type, then we have a pointer to an object that has not yet been
// constructed and and doesn't have implicit lifetime - which is bad
示例 #2。
T* allocate_array(std::size_t n)
// malloc implicitly constructs - reinterpet_cast should a pointer to
// suitably created object (a T array), hence, no UB here.
T(*)[] array_pointer = reinterpret_cast<T(*)[]>(malloc(sizeof(T) * n) );
// The pointer in the previous line is a pointer to valid array, de-reference
// is supposed to give me that array
T* array = *array_pointer;
return array;
示例 #3 - 与 2 相同,但数组的大小是已知的。
T* allocate_array(std::size_t n)
// malloc implicitly constructs - reinterpet_cast should a pointer to
// suitably created object (a T array), hence, no UB here.
T(*)[n] n_array_pointer = reinterpret_cast<T(*)[n]>(malloc(sizeof(T) * n) );
// The pointer in the previous line is a pointer to valid array, de-reference
// is supposed to give me that array
T* n_array = *n_array_pointer;
return n_array;
这些都有效吗?
答案
虽然标准的措辞不是 100% 清楚,但在更仔细地阅读论文之后,动机是使转换为 T*
合法,而不是转换为 T(*)[]
。 Dynamic construction of arrays。此外,the changes to the standard by the authors of the paper 暗示演员应该是T*
而不是T(*)[]
。因此,接受the answer by Nicol Bolas 作为我问题的正确答案。
【问题讨论】:
我看到 C++ 不断地从简单到 WTF 的土地。 @user14063792468:他所说的“变化”从 C++03 开始就存在了。这不是新的。指针算法仅在对象数组的上下文中定义(单个活动对象被计为 1 元素数组)。如果你只是分配了一些内存,里面没有任何对象,所以你不能只对它进行指针运算。 @dvix - 数组是隐式生命周期对象。 eel.is/c++draft/basic.types “标量类型、隐式生命周期类类型 ([class.prop])、数组类型和这些类型的 cv 限定版本统称为隐式生命周期类型”。它说的是数组类型,而没有说空初始化。隐式生命周期的概念是 c++20 标准的新概念,而 vacuous initialization 不是。她们不一样。请注意,隐式生命周期对象(数组)可以具有不是隐式生命周期对象eel.is/c++draft/intro.object#note-3 的子对象。 @dvix "某些操作被描述为在指定的存储区域内隐式创建对象。对于指定为隐式创建对象的每个操作,该操作隐式创建并开始零生命周期或更多隐式生命周期类型([basic.types])的对象,如果这样做会导致程序具有已定义的行为" ... "这样的操作不会启动此类对象本身不是隐式生命周期类型的子对象的生命周期". @dxiv:请注意,该问题的某些答案在 C++20 中不再有效。 【参考方案1】:隐式对象创建的全部意义在于它是隐式。也就是说,你不做任何事情来让它发生。一旦 IOC 发生在一块内存上,您就可以使用该内存,就好像有问题的对象存在一样,只要您这样做,您的代码就可以工作。
当你从allocator_traits<>::allocate
得到你的T*
时,如果你给指针加 1,那么函数返回了一个至少有 1 个元素的数组(新指针可能是过去的指针数组)。如果再次加 1,则该函数返回了一个至少包含 2 个元素的数组。等等。这些都不是未定义的行为。
如果你做了一些与此不一致的事情(转换为不同的指针类型并表现得好像那里有一个数组),或者你表现得好像数组超出了 IOC 应用的存储大小,那么你获得 UB。
所以allocator_traits::allocate
真的不需要做任何事情,只要分配器分配的内存隐式创建对象。
// does it return an implicitly constructed array (as long as // subsequent usage is valid) or a T* pointer that does not "point" // to a T object that notconstructed, hence UB
两者都不是。它返回一个指针(类型为T
),指向可能已经隐式创建的对象的存储。 隐式创建了哪些对象取决于您如何使用此存储。并且仅仅做一个演员并不构成“使用”存储。
导致 UB 的不是 reinterpret_cast
; using the pointer 由不正确的reinterpret_cast
返回,这就是问题所在。而且由于 IOC 基于会导致 UB 的操作进行工作,因此 IOC 并不关心您将指针投射到什么。
国际奥委会规则的重要组成部分是推论“suitable created object”规则。这条规则说某些操作(如malloc
和operator new
)返回一个指向“合适的创建对象”的指针。从本质上讲,它回到了量子叠加:如果 IOC 追溯创建一个对象以使您的代码工作,那么这些函数会追溯返回一个指针,该指针指向所创建的使您的代码工作的任何对象。
因此,如果您的代码将指针用作T*
并对该指针进行指针运算,则malloc
返回指向T
s 数组的第一个元素的指针。这个数组有多大?这取决于:分配有多大,你的指针算术做了多远?里面有直播T
s 吗?这取决于:您是否尝试访问数组中的任何 T
s?
【讨论】:
您介意用代码示例(使用 new 和 malloc)而不是内置的allocate
函数来澄清一下吗?我很感兴趣如何产生类似的行为。我将添加一些代码 sn-ps 以使我的问题更清楚。
@RazielMagius:IOC 的全部意义在于使您现在拥有的“工作”代码实际上在对象模型中工作,但不会使对象模型不是对象模型。我发现您对 T(*)[]
的转换很可疑(您应该只是转换为 T*
),但我尽量避免理解 C 样式数组的细节以及它们与指针的关系。
有些事情让我很困扰。 malloc
或 new
应该返回一个指向合适的创建对象的指针。当我返回reinterpret_cast<T*>(malloc(... ))
时,这里合适的创建对象是什么?它指向尚未创建的数组的第一个元素(并且实际上可能根本没有创建,以防我想使用“未初始化的数组”来实现类似哈希集的东西(除非它是无效的)用法,我不确定,我应该只使用 )。
(就像我稍后可能会创建数组 [13] 和数组 [37],具体取决于哈希函数,但从不创建数组 [0])。隐式生命周期是否也涵盖这种情况?还是只有元素连续存储时才有效?
@RazielMagius:你的问题现在变成了“隐式对象创建是如何工作的”,在链接的答案中得到了回答。以上是关于如何创建一个数组并开始它的生命周期而不开始它的任何元素的生命周期?的主要内容,如果未能解决你的问题,请参考以下文章
Camerax 在服务中运行。如何在前台服务中获取生命周期所有者或在没有它的情况下运行?