I need to allocate a chunk of memory using malloc
and then store multiple values of different plain old data types in there. Is the following a correct way to do it?
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <iostream>
struct Foo {
int32_t a;
int32_t b;
};
struct Bar {
int8_t a;
int8_t b;
};
void* offset_bytewise(void* void_ptr, ptrdiff_t count) {
return static_cast<void*>(static_cast<char*>(void_ptr) count);
}
void test() {
auto void_ptr = std::malloc(10);
new (offset_bytewise(void_ptr, 0)) Foo{ 1, 2 };
new (offset_bytewise(void_ptr, 8)) Bar{ 3, 4 };
auto foo_ptr = static_cast<Foo*>(offset_bytewise(void_ptr, 0));
auto bar_ptr = static_cast<Bar*>(offset_bytewise(void_ptr, 8));
std::cout << (int)foo_ptr->a << ' ' << (int)foo_ptr->b << '\n';
std::cout << (int)bar_ptr->a << ' ' << (int)bar_ptr->b << '\n';
// Re-using a part of the allocation as a different type than before.
new (offset_bytewise(void_ptr, 1)) Bar{ 5, 6 };
bar_ptr = static_cast<Bar*>(offset_bytewise(void_ptr, 1));
// I suppose dereferencing `foo_ptr` would be UB now?
std::cout << (int)bar_ptr->a << ' ' << (int)bar_ptr->b << '\n';
std::free(void_ptr);
}
CodePudding user response:
Implicit object creation will cause the allocated memory block to contain an array of char
of suitable size and the pointer returned from malloc
will point to the first element of that array.
(That is assuming std::malloc
doesn't return a null pointer, which you should check for.)
Such an array can provide storage for other objects (although technically the standard currently says this is only true for unsigned char
and std::byte
, which is probably a defect).
So you are ok to create new objects in that array as you are doing.
However, when you try to obtain a pointer to the newly created object, you cannot simply cast the pointer since elements of the array are not pointer-interconvertible with the objects for which the array provides storage.
Therefore you need to apply std::launder
after casting the pointer, e.g.
auto foo_ptr = std::launder(static_cast<Foo*>(offset_bytewise(void_ptr, 0)));
Alternatively you can use the result of new
which is a pointer to the newly created object:
auto foo_ptr = new(offset_bytewise(void_ptr, 0)) Foo{ 1, 2 };
Also, you need to make sure that size and alignment of the types is correct, i.e. here you should add:
static_assert(alignof(Foo) <= 8);
static_assert(sizeof(Foo) <= 8);
static_assert(alignof(Bar) <= 1);
static_assert(sizeof(Bar) <= 2);
Creating the second Bar
object at offset 1
will cause the lifetime of the Foo
object to end, since it overlaps with the storage of the new object. Therefore foo_ptr
may afterwards be used only in the way other pointers to object out-of-lifetime of an object may be used. In particular trying to access the value of a member will be UB.