Consider the following simple struct:
struct A
{
float data[16];
};
Assuming a platform where float
is a 32-bit IEEE754 floating point number (if that matters at all), does the C++ standard guarantee the expected memory layout for struct A
? If not, what does it guarantee and/or what are the ways to enforce the guarantees?
By the expected memory layout I mean that the struct takes up 16*4=64
bytes in memory, each consecutive 4
bytes occupied by a single float
from the data
array. In other words, expected memory layout means the following test passes:
static_assert(sizeof(A) == 16 * sizeof(float));
static_assert(offsetof(A, data[0]) == 0 * sizeof(float));
static_assert(offsetof(A, data[1]) == 1 * sizeof(float));
...
static_assert(offsetof(A, data[15]) == 15 * sizeof(float));
(offsetof
is legal here since A
is standard layout, see below)
In case this bothers you, the test actually passes on wandbox with gcc 9 HEAD. I have never met a combination of a platform and compiler which would provide evidence that this test may fail, and I would love to learn about them in case they do exist.
alignas
specifier).write_bytes(&x, sizeof(A))
.data
array to pass a single object of this type, but for a sequence of these (say, for uploading matrix-type vertex attributes) a specific memory layout is still needed.These are the things that, to my knowledge, can be expected from struct A
:
A
can be reinterpret_cast
to a pointer to its first data member (which is, presumably, data[0]
?), i.e. there is no padding before the first member.The two remaining guarantees that are not (as to my knowledge) provided by the standard are:
data
array inside struct A
.One thing that is not guaranteed about the layout is endianness i.e. the order of bytes within a multi-byte object. write_bytes(&x, sizeof(A))
is not portable serialisation across systems with different endianness.
A
can bereinterpret_cast
to a pointer to its first data member (which is, presumably,data[0]
?)
Correction: The first data member is data
, which you can reinterpret cast with. And crucially, an array is not pointer-interconvertible with its first element, so you cannot reinterpret cast between them. The address however is guaranteed to be the same, so reinterpreting as data[0]
should be fine after std::launder
as far as I understand.
There is no padding in between elements of an array of primitive type
Arrays are guaranteed to be contiguous. sizeof
of an object is specified in terms of padding required to place elements into an array. sizeof(T[10])
has exactly the size sizeof(T) * 10
. If there is padding between non-padding bits of adjacent elements, then that padding is at the end of the element itself.
Primitive type is not guaranteed to not have padding in general. For example, the x86 extended precision long double
is 80 bits, padded to 128 bits.
char
, signed char
and unsigned char
are guaranteed to not have padding bits. C standard (to which C++ delegates the specification in this case) guarantees that the fixed width intN_t
and uintN_t
aliases do not have padding bits. On systems where that is not possible, these fixed width types are not provided.
If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member. Otherwise, its address is the same as the address of its first base class subobject (if any). [Note: There might therefore be unnamed padding within a standard-layout struct object, but not at its beginning, as necessary to achieve appropriate alignment. — end note]
Hence, the standard guarantees that
static_assert(offsetof(A, data[0]) == 0 * sizeof(float));
An object of array type contains a contiguously allocated non-empty set of N subobjects of type T.
Hence, the following are true
static_assert(offsetof(A, data[0]) == 0 * sizeof(float));
static_assert(offsetof(A, data[1]) == 1 * sizeof(float));
...
static_assert(offsetof(A, data[15]) == 15 * sizeof(float));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With