c++language-lawyermemory-layoutstandard-layout

Guaranteed memory layout for standard layout struct with a single array member of primitive type


Consider the following simple struct:

struct A
{
    float data[16];
};

My question is:

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.

Why would one even care:

What is actually guaranteed:

These are the things that, to my knowledge, can be expected from struct A:

The two remaining guarantees that are not (as to my knowledge) provided by the standard are:


Solution

  • 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 be reinterpret_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.