Is this use of offsetof guaranteed to be correct?

448 views Asked by At

I have read that offsetof macro is commonly implemented as:

#define offsetof(st, m) \
    ((size_t)&(((st *)0)->m))

And according to Wikipedia there is debate about whether this is undefined behavior because it may be dereferencing a pointer. I have a buffer of information that I need to send to different places, but the buffer only takes up part of the struct. I was wondering if the following is guaranteed to give the correct size of my struct up to the end of the buffer, the last member.

struct PerObjBuffer
{
    mat4 matrix;
    mat4 otherMatrix;
    vec4 colour;
    int sampleType = 0;

    size_t getBufferSize() { return offsetof(PerObjBuffer, sampleType) + sizeof(sampleType); }
    void sendToGPU() { memcpy(destination, this, getBufferSize()); }
    String shaderStringCode =

        R"(layout(binding = 2, std140) uniform perObjectBuffer
    {
        mat4 WVPMatrix;
        mat4 worldMatrix;
        vec4 perObjColour;
        int texSampleFormat;
        };
    )";

}

If the use of offsetof isn't a good idea, what's the best way to do what I want to do?

Edit: What about sizeof(PerObjBuffer) - sizeof(String)? Would I have to take into account padding issues?

1

There are 1 answers

0
KamilCuk On BEST ANSWER

The standard specifies behaviors of things, not how are they implemented. The implementation part is left to implementators - people who write compilers and libraries - and they have to implement things in a way they work in a way specified by the standard. If that implementation of standard library has by itself undefined behavior is irrelevant - it has to work with specified compiler, so specific compiler will interpret it in a way it has the behavior implementators want. Undefined behavior means that the standard specifies no requirement on the behavior of code. Your compiler documentation may specify additional requirements specifying behaviors of code that according to the standard are undefined - thus the code may be undefined by the standard and be perfectly fine and reasonable on a specific compiler that is required/written to interpret it in that way.

C++ language uses macros from C language when proper #include is used. The C standard says C99 7.17p3:

 The macros are [...] and

        offsetof(type, member-designator)

which expands to an integer constant expression that has type size_t, the value of which is the offset in bytes, to the structure member (designated by member-designator), from the beginning of its structure (designated by type). The type and member designator shall be such that given

        static type t;

then the expression &(t.member-designator) evaluates to an address constant. (If the specified member is a bit-field, the behavior is undefined.)

As a user of the language you do not care how it's implemented. If you use a standard compliant compiler, whatever it does behind the scenes, it should result in the behavior specified by the standard. Your usage of offsetof(PerObjBuffer, sampleType) is valid - having static PerObjBuffer t; then &(t.member-sampleType) evaluates to address constant - so offsetof(PerObjBuffer, sampleType) evaluates to integer constant expression. You like do not care how the compiler arrives at the result - it can use dark magic to do it - what matters it that compiler does it and the result represents the offset in bytes. (I think the famous example is memcpy - it's not possible to implement memcpy in standard compliant way, yet the function... exists).

Still, I fear that other parts of your code will be very invalid. The memcpy will most probably result in undefined behavior - you will copy padding between members and you seem to want to send that to some hardware location. In any case, I advise to do extensive research on lifetime and representation of C++ objects, on padding within structures, types of objects (ie. POD/standard layout/trivial) and how to work with them (ie. when it's ok to use mem* functions/placement new/std::launder).