如何在C ++中创建具有动态对齐要求的对象?

问题描述 投票:2回答:2

在C ++中分配和使用具有动态指定对齐方式的缓冲区的正确方法是什么?我想到的用例是Vulkan动态统一缓冲区(请参见this previous question,该缓冲区抽象地讨论了所需的过程),针对对齐的约束是通过minUniformBufferOffsetAlignmentVkPhysicalDeviceLimits属性给出的,在编译时已知。

我最初以为我可以使用operator new(std::align_val_t)做类似的事情

Foo* buffer = new(std::align_val_t{alignment}) Foo[n];

但是不会编译(至少在MSVC上)。

[我也看过Timur Doumler的CppCon演示文稿“ Type punning in modern C++”,它指出,对reinterpret_cast之类的结果使用std::aligned_alloc会导致不确定的行为。

到目前为止,我已经提出了以下内容:

std::size_t n = getNumberOfElements(); // possibly not known at compile time
std::size_t alignment = getRequiredAlignment(); // not known at compile time

makeSureMultiplicationDoesNotOverflow(sizeof(Foo), n); // details irrelevant

void* storage = std::aligned_alloc(sizeof(Foo) * n, alignment); // _aligned_malloc on MSVC
if (!storage) { std::terminate(); }
Foo* buffer = new(storage) Foo[n];

// do stuff with buffer

for(std::size_t i = 0; i < n; ++i) { buffer[i].~Foo(); }
std::free(storage); // _aligned_free on MSVC

我在这里错过了会导致不确定行为的东西吗?

编辑:我注意到上面没有将对齐方式应用到第一个对象以外的任何对象,因此绝对是个难题...

c++ memory-management c++17
2个回答
4
投票
[Foo[n]是一个可变长度数组,它们是not part of standard C++

您可以在不使用VLA的情况下执行以下操作:

auto storage = static_cast<Foo*>(std::aligned_alloc(n * sizeof(Foo), alignment)); std::uninitialized_default_construct_n(storage, n); auto ptr = std::launder(storage); // use ptr to refer to Foo objects std::destroy_n(storage, n); free(storage);

std::aligned_alloc返回的指针投射到Foo*不是未定义的行为。如果您试图在std::aligned_alloc创建std::uninitialized_default_construct_n对象之前的Foo之后立即取消引用,则它将是UB。

编辑。

上面的代码是技术上未定义的行为。但是似乎在C ++中,没有UB的情况下没有100%符合标准的方式进行这种分配。从实际的角度来看,此代码是可靠且安全的。 std::launder(storage)可能应该用于通过Foo指针访问storage对象。

请参见this question以获取详细信息和讨论。


2
投票
您已经有了更好的选择的答案,但是我只想补充一下,您的代码具有未定义的行为。

new的数组形式,即使您正在使用的新放置形式,也允许使用分配中未指定的内存量来存储运行时的元数据(例如,delete[]的分配大小知道需要销毁多少元素。

因此,您无法保证分配的内存大小足够,也不能保证由new-place返回的数组的实际指针正确对齐。

据我从当前的草案中可以看出,这在C ++ 20中似乎有所改变,这使数组放置的这种特殊用法表现得很好,但是在C ++ 17或更高版本中并不需要如此较早。

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