Reputation:
What is the correct way in C++ to allocate and use a buffer with dynamically-specified alignment? The use case that I had in mind was Vulkan dynamic uniform buffers (see this previous question which discusses the required process in the abstract), for which a constraint on alignment is given via the minUniformBufferOffsetAlignment
property on VkPhysicalDeviceLimits
, which is not known at compile time.
I initially thought I might be able to use operator new(std::align_val_t)
doing something like
Foo* buffer = new(std::align_val_t{alignment}) Foo[n];
but that doesn't compile (at least on MSVC).
I've also watched Timur Doumler's CppCon presentation "Type punning in modern C++" which notes that using reinterpret_cast
on the result of something like std::aligned_alloc
leads to undefined behaviour.
So far I've come up with the following:
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
Have I missed something here that's going to cause undefined behaviour?
EDIT: I noticed that the above does not apply the alignment to any objects other than the first one, so that's definitely an oops...
(Obviously in a real application this should be encapsulated into a class to provide RAII, but leaving that out for now so as not to bloat the example code.)
Upvotes: 3
Views: 689
Reputation: 26292
You can do the following:
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);
Casting a pointer returned by std::aligned_alloc
to Foo*
is not undefined behavior. It would be UB if you tried to dereference it right after std::aligned_alloc
before std::uninitialized_default_construct_n
created Foo
objects.
Edit.
The code above is technically undefined behavior. But it seems that in C++ there is no 100% standard-conforming way to do such an allocation without UB. From the practical point to view this code is reliable and safe. std::launder(storage)
should probably be used to access Foo
objects through storage
pointer.
See this question for details and discussions.
Upvotes: 4
Reputation: 22152
You already have an answer with a better alternative, but I just want to add that your code has undefined behavior.
The array form of new
, even the placement-new form that you are using, is allowed to use an unspecified amount of memory from your allocation to store metadata for the runtime (e.g. the size of the allocation for delete[]
to know how many elements it needs to destroy).
Therefore you have no way of guaranteeing that the allocated memory size is sufficient or that the actual pointer to the array returned by the placement-new is properly aligned.
As far as I can tell from the current draft this seems to change in C++20, making this particular use of the array placement-new well-behaved, but it is not required to be so in C++17 or earlier.
Upvotes: 1