Search code examples
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:

  • SSE-like optimizations require certain memory layout (and alignment, which I ignore in this question, since it can be dealt with using the standard alignas specifier).
  • Serialization of such a struct would simply boil down to a nice and portable write_bytes(&x, sizeof(A)).
  • Some APIs (e.g. OpenGL, specifically, say, glUniformMatrix4fv) expect this exact memory layout. Of course, one could just pass the pointer to 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.

What is actually guaranteed:

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

  • It is standard layout
  • As a consequence of being standard-layout, a pointer to 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:

  • There is no padding in between elements of an array of primitive type (I am sure that this is false, but I failed to find a confirmative reference),
  • There is no padding after the data array inside struct A.

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.