在隐式生命周期类型的自定义分配器中寻找关于 C++20 严格别名的清晰性

问题描述 投票:0回答:1

假设我有一个分配器,它为每个请求提供整个缓存行。 因此每次分配都将从一个新的缓存行开始。

constexpr auto L1 = std::hardware_destructive_interference_size;
std::byte* allocator_cachelines = static_cast<std::byte*>(
            ::operator new(TOTAL_CACHELINES*L1, std::align_val_t{L1}));

// ALL TYPES USING THE ALLOCATOR HAVE IMPLICIT LIFETIME
// (are trivially copyable and destructable)
struct ABC { int i=0; };
struct XYZ { float f=1.f; };

auto* abc = reinterpret_cast<ABC*>(allocator.allocate(ABC_COUNT * sizeof(ABC)));
// allocate returns enough cachelines from allocator_cachelines
// to fulfill the request,
// plus the bookkeeping to mark those cachelines as in use

std::cout << abc[0].i; // OK?                                                       
// since ABC is the first type stored within the memory,
// does it's implicit lifetime begin?

参见: https://en.cppreference.com/w/cpp/language/object#Object_creation:~:text=call%20to%20following,包括%20placement%20new)

现在,如果该内存被释放并重新用于另一个隐式生命周期类型,会发生什么。

allocator.deallocate(abc);
// deallocate marks the returned abc cachelines as free for reuse

auto* xyz = reinterpret_cast<XYZ*>(allocator.allocate(XYZ_COUNT * sizeof(XYZ)));
// allocate returns the abc cachelines which were just freed

std::cout << xyz[0].f; // OK?

// or do I need to first do
xyz[0] = XYZ{};
std::cout << xyz[0].f; // OK NOW?

此外,如果对分配器的访问由 std::mutex 控制。 这会解决任何严格的别名违规问题吗? 由于互斥体引入了内存栅栏,内存重新排序不会导致严格别名的冲突,因为分配和解除分配为重用内存提供了互斥类型(假设指向先前数据类型的指针没有被缓存以供以后使用)。

最后,如果我有一个从分配器分配的浮点数数组并将其与 SIMD 内联函数一起使用。 我是否必须写入所有元素以避免严格的别名违规? 由于 SIMD 本质可以访问数组末尾从未写入的元素。
例如 浮点数组:[0,1,2,3]
如果 0、1、2 被写入而 3 未被写入(并且另一种类型之前已经占用了这些字节)。 4 个浮点的任何 SIMD 固有负载都会将 3 作为与上次写入的类型不同的类型进行访问。

c++ memory-management language-lawyer c++20 implicit-lifetime
1个回答
0
投票

我将你的问题分为三个部分:

ABC
的生命周期是否隐式开始?

,只要

ABC
是隐式生命周期类(确实如此)。但是,您必须注意,除非运行其构造函数,否则不会使用隐式成员初始值设定项 (
i = 0
),因此,
i
将获得不确定的值。

如果要初始化

ABC
对象,正确的语法是:

new (abc[0]) ABC();

不是

abc[0] = ABC();

释放一个对象并将内存用于另一个对象是否构成严格的别名违规

。这不是严格的别名规则的目的。仅当您有两个(基本上)不同类型的指针指向同一对象并同时使用它们时,才会违反它。如果您在某一时刻停止使用其中一种并开始使用另一种,那也没关系。例如。即使这样也可以:

T* t = new T;
... // use t
t->T::~T(); // destruct
U* u = new (t) U; // construct U in the place formerly occupied by T
                  // provided that U fits into memory occupied by T
... // use u, CAN'T use t
u->U::~U(); // now destruct U
::operator delete(u);

SIMD 与严格的别名规则有什么关系吗?

不。即使在访问无关元素时,您也可以自由使用 SIMD 指令,只要这些额外元素在内存中不与其他数据重叠(根据您的描述,这种情况不会发生)。

© www.soinside.com 2019 - 2024. All rights reserved.