在C ++中分配和使用具有动态指定对齐方式的缓冲区的正确方法是什么?我想到的用例是Vulkan动态统一缓冲区(请参见this previous question,该缓冲区抽象地讨论了所需的过程),针对对齐的约束是通过minUniformBufferOffsetAlignment
的VkPhysicalDeviceLimits
属性给出的,在编译时已知。
我最初以为我可以使用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
我在这里错过了会导致不确定行为的东西吗?
编辑:我注意到上面没有将对齐方式应用到第一个对象以外的任何对象,因此绝对是个难题...
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以获取详细信息和讨论。
new
的数组形式,即使您正在使用的新放置形式,也允许使用分配中未指定的内存量来存储运行时的元数据(例如,delete[]
的分配大小知道需要销毁多少元素。
因此,您无法保证分配的内存大小足够,也不能保证由new-place返回的数组的实际指针正确对齐。
据我从当前的草案中可以看出,这在C ++ 20中似乎有所改变,这使数组放置的这种特殊用法表现得很好,但是在C ++ 17或更高版本中并不需要如此较早。