My goal is to allocate memory for two different types of objects, let's call them obj1
and obj2
, within a single contiguous memory block. A crucial aspect is that obj1
and obj2
can be of different types (e.g., int
and float
, or std::complex
, etc...). My aim is to achieve this with a single allocation call, conceptually similar to how std::make_shared
places its managed object and control block in one contiguous chunk.
This is for performance (cache locality, reduced allocation overhead) and to gain fine-grained control over the entire memory block's lifetime, allowing for its explicit deallocation.
Imagine I have a struct representing pointers to these items (though the struct itself is secondary, the primary goal is the single allocation for two items, and obtaining pointers to them for manual management):
struct DataContainer {
int* val1;
float* val2;
};
I need val1
to point to an int
and val2
to point to a float
, both located within the same memory block allocated via a single call. Crucially, I also need to retain control over when this entire block is freed.
new int[2]
won't work if the types are different. Standard new
calls for each type (new int; new float;
) result in separate allocations, which I want to avoid to minimize overhead and fragmentation.
After a single allocation call, I should have access to two objects of specified types (e.g., int
and float
), guaranteed to be within the same contiguous memory block. I need the ability to explicitly manage the destruction of these objects and the deallocation of that entire block myself.
Desired Example (simplified):
void* raw_memory = allocate_two_objects_of_different_types<int, float>();
int* p_int = get_first_object_from_raw_memory<int>(raw_memory);
float* p_float = get_second_object_from_raw_memory<float>(raw_memory);
*p_int = 10;
*p_float = 20.5f;
// I also should be able do to this
deallocate_raw_memory(raw_memory);
Is there an elegant and easy way to achieve this single allocation pattern without relying on standard library containers to internally manage the allocation and deallocation of the multi-typed data?
One way of doing this is via std::aligned_alloc
together with offsetof
:
struct Data {
struct Glue {
short s;
long l;
};
short* ps;
long* pl;
void construct() {
auto glue = static_cast<std::byte*>(std::aligned_alloc(alignof(Glue),sizeof(Glue)));
ps = reinterpret_cast<short*>(glue+offsetof(Glue,s));
pl = reinterpret_cast<long*>(glue+offsetof(Glue,l));
}
void destruct() {
std::free(reinterpret_cast<std::byte*>(ps)-offsetof(Glue,s));
}
};
I have used short and long instead of int and float because they actually have different alignments.
Here is a demo on compiler explorer with ubsan and asan.
This is only legal because Glue is trivial in every respect. This will be more complicated if any of the member types is non-trivial. In that case just allocate the memory of that size and alignment and later construct the object with placement new:
struct Glue {
alignas(type1) std::byte member1[sizeof(type1)];
alignas(type2) std::byte member2[sizeof(type2)];
};
Note: You can of course do all of this without the Glue
type, by adding the sizes of the individual members and ensuring they are correctly aligned. This will involve using max(alignment(member)...) and some additional arithmetic to find the correct location of the respective member addresses. Basically replace the calculation done by offsetof. More chances to make subtle mistakes.