How can one not make assumptions about C++ struct layouts?
Asked Answered
C

3

5

I've just learned from Bug in VC++ 14.0 (2015) compiler? that one shouldn't make assumptions about how a struct's layout will end up in memory. However, I don't understand how it is common practice in a lot of code I've seen. For example, the Vulkan graphics API does the following:

Defines a struct

struct {
    glm::mat4 projection;
    glm::mat4 model;
    glm::vec4 lightPos;
} uboVS;

Then fills up its fields:

    uboVS.model = ...
    uboVS....

Then just copies over the struct (in host memory) to device memory via memcpy:

    uint8_t *pData;
    vkMapMemory(device, memory, 0, sizeof(uboVS), 0, (void **)&pData);
    memcpy(pData, &uboVS, sizeof(uboVS));
    vkUnmapMemory(device, memory);

Then over to the GPU, it defines a UBO to match that struct:

layout (binding = 0) uniform UBO 
{
    mat4 projection;
    mat4 model;
    vec4 lightPos;
} ubo;

Then, on the GPU side, ubo will always match uboVS.

Is this the same undefined behavior? Doesn't that code rely on the uboVS struct to be laid out exactly as defined, or for both sides (the compiled C++ code and the compiled SPIR-V shader) to basically generate the same different struct layout? (similar to the first example in https://www.securecoding.cert.org/confluence/display/c/EXP11-C.+Do+not+make+assumptions+regarding+the+layout+of+structures+with+bit-fields)

This question is not specific to Vulkan or graphics APIs, I am curious as to what exactly can one assume and when is it ok to just use a struct as a chunk of memory. I understand struct packing and alignment, but is there more to it?

Thanks

Cell answered 30/8, 2016 at 2:34 Comment(3)
One should never use a struct across compile domains. It may work sometimes for a while, but doing it as a habit you may end up having to do a lot of maintenance of that code, when other solutions could have been written once and not required regular maintenance. if your plan is to do this for job security, sure you can try that and see how well it works out for you.Trickle
You may want to look at this question and answer.Hillari
To avoid assumptions about struct layouts, don't use memcpy on structs (among other things)Affixation
H
7

It's important to recognize the difference between what you did in the question you cite and what you're doing here.

What you did in the question you showed breaks the rules of C++. It invokes undefined behavior. You tried to pretend that an object containing 16 floats is the same thing as a 16-float array. C++ doesn't permit this to be well-defined behavior, and compilers are allowed to assume you won't try it.

By contrast, converting a struct into a byte array and copying that array somewhere else actually doesn't break the rules of the C++ object model. It has a very specific clause permitting such things for appropriate types.

The difference is that it's not the C++ compiler that cares about the object's layout; it's the GPU. So long as the layout of data you provide matches what your shader said it would be, you're fine. You're not casting floats into arrays or trying to access one object through a pointer to a different one or somesuch. You're just copying bytes.

At which point, the only question that remains is whether the byte representation of that struct matches the byte representation of the expected SPIR-V data structure definition. And yes, this is something you can rely upon for most systems that Vulkan can run on.

Hillari answered 30/8, 2016 at 3:15 Comment(0)
A
5

It is true that, roughly speaking, the C++ standard does not mandate any particular internal layout of class members.

However, specialty libraries, like graphics libraries for a particular operating system, are going to be targeting the operating system's specific compiler. They know how this particular compiler arranges the layout of C/C++ class and structure members, and the library is going to supply suitable definitions that matches the actual hardware in question.

Operating systems that have more than one compiler will often have a formal specification for that operating system's binary ABI, and the compilers are going to follow that ABI, and the specialty library will provide class and structure definitions that will be in sync with that.

So, in your specific case, you can "assume and when is it ok to just use a struct as a chunk of memory" after you consult your compiler's documentation, determine how your compiler lays out the members of structures or classes, and then come up with a structure layout accordingly.

Announce answered 30/8, 2016 at 2:43 Comment(0)
P
0

Spir-V (the shading language you pass to vulkan) requires that you add layout decorations to struct members used for UniformConstant, Uniform, and PushConstant variables. You can use this to make the spir-V member offsets match the member offsets in the C++ struct.

To actually do this is tricky as it requires inspecting the spir-V code and setting the offsets as needed.

Pepsin answered 30/8, 2016 at 9:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.