the 'struct' is sort of a 'false entity'. Down in the CPU, there is no such thing as a struct.
maybe this will help...
int * ip = (int*)bytearray;
i += sizeof(int);
double *dp = (double*)(&bytearray[i]);
string *sp = (string*)(&bytearray[i]);
this is a 'struct', sort of.
the fields are ip, dp, and sp.
this is (very, very roughly) what the compiler is doing when it turns a 'struct' into machine language, though bytearray would just be ram direct memory offsets.
so you can move the 'struct' around by moving 'bytearray' around as a 'group of bytes', but that is just organization, a 'way to look at it'. there is no 'struct' thing in reality, its just a way to say "this many bytes in a block" when talking at the struct level or "this offset from the starting address" when talking about its fields. That is why the struct and first field share an address: its just a different way of looking at the same block of bytes.
at the c++ level, a struct IS a thing, and a tool you can do a lot with. But that is just a syntax that hides what needs to be done to express it. You do not really care about the memory layout details when using a struct, you just use it and let the compiler do the ugly stuff for you.
The object representation of standard layout types are as specified by the C language,
A pointer to an object of standard-layout class type can be reinterpret_cast to pointer to its first non-static non-bitfield data member (if it has non-static data members) or otherwise any of its base class subobjects (if it has any) (since C++11), and vice versa. In other words, padding is not allowed before the first data member of a standard-layout type. Note that strict aliasing rules still apply to the result of such cast. https://en.cppreference.com/w/cpp/language/data_members#Standard_layout