Given this code:
#include <cassert>
#include <cstring>
struct base{
virtual ~base() = default;
};
class derived: public base{
public:
int x;
};
using byte = unsigned char;
int main() {
byte data[sizeof(derived)];
derived d;
memcpy(data, &d, sizeof(derived));
base* p = static_cast<base*>(reinterpret_cast<derived*>(data));
const auto offset = (long)data - (long)p;
assert(offset < sizeof(derived)); // <-- Is this defined?
}
As my comment asks, is this defined behavior by the standard? i.e does casting to base guarantee a pointer to the region occupied by the derived being cast? From my testing it works on gcc and clang, but I am wondering if it works cross platform too (obviously this version assumes 64bit pointers)
your data
array is a char array, so its alignment will be 1 byte.
your class however contains an int
member, so its alignment will be at least 4 bytes.
So you data
array is not sufficiently aligned to even contain a derived object.
You can easily fix this by providing an alignment of your data array that is at least that of derived
or greater, e.g.:
alignas(alignof(derived)) byte data[sizeof(derived)];
(godbolt demonstrating the problem)
you can also use std::aligned_storage
for this if you want.
Using memcpy
on classes will only work if they're trivially copyable (so a byte-wise copy would be identical to calling the copy constructor). Your class isn't trivially copyable due to the virtual destructor, so memcpy
isn't allowed to copy the class.
You can easily check this with std::is_trivially_copyable_v
:
std::cout << std::is_trivially_copyable_v<derived> << std::endl;
You can fix this easily by calling the copy-constructor instead of using memcpy
:
alignas(alignof(derived)) char data[sizeof(derived)];
derived d;
derived* derivedInData = new (data) derived(d);
How classes will be layed out in memory is implementation-defined, so you basically have no guarantees how the compiler will lay out your class hierarchy.
However there are a few things you can count on:
sizeof(cls)
will always return the size cls
needs, including all it's base classes, even when it uses virtual and / or multiple inheritance. (sizeof)
When applied to a class type, the result is the number of bytes occupied by a complete object of that class, including any additional padding required to place such object in an array.
static_cast<>
to baseclass is always definedYes, the base class pointer must always point to somewhere within your buffer, since it's a part of the derived class. However where exactly it'll be in the buffer is implementation-defined, so you should not rely on it.
The same thing is true about the pointer returned from placement new - it might be to the beginning of the array or somewhere else (e.g. array allocation), but it'll always be within the data array.
So as long as you stick to one of those patterns:
struct base { int i; }
struct derived : base { int j; };
alignas(alignof(derived)) char data[sizeof(derived)];
derived d;
memcpy(data, &d, sizeof(derived)); // trivially copyable
derived* ptrD = reinterpret_cast<derived*>(data);
base* ptrB = static_cast<base*>(ptrD);
/
struct base { int i; virtual ~base() = default; }
struct derived : base { int j; };
alignas(alignof(derived)) char data[sizeof(derived)];
derived d;
derived* ptrD = new(data) derived(d); // not trivially copyable
base* ptrB = static_cast<base*>(ptrD);
ptrD->~derived(); // remember to call destructor
your assertions should hold and the code should be portable.