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)
CodePudding user response:
Is the pointer from casting to base gurenteed to be a pointer into the memory region of the derived object
Not necessarily in general. If the base is virtual, and the derived object in question isn't the most derived object, then in such case the virtual base may be outside the memory of the derived object.
But outside of that corner case, for example such as in the example code, the base sub object is indeed guaranteed to be within the derived object. That's what "sub object" implies.
CodePudding user response:
potentially wrong alignment
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 is not always safe
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);
virtual inheritance, multiple inheritance and other shenanigans
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 sizecls
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.
- placement new will construct an object and return a pointer to it that is within the given buffer.
static_cast<>
to baseclass is always defined
the actual answer
Yes, 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.