0

I need to create a chunk of memory tightly filled with floats. But instead of making a vector of floats, I'd like to make a vector in which each member is a group of related floats:

struct FloatStruct
{
    FloatStruct() : 
        a(1.f), b(10.f), c(100.f), d(1000.f), e(10000.f) { }

    float a;
    float b;
    float c;
    float d;
    float e;
};


int main()
{
    std::vector<FloatStruct> fvec(100);

    auto pf = &fvec[0].a;

    for(int i = 0; i < 500; ++i)
        std::cout << *pf++ << "\n";
}

The above code apparently works fine on my architecture with MSVC (and also on some online compilers with GCC or Clang). The floats are all tightly packed. But I'm worried about the portability of that code. Maybe a padding could be added on some architecture and thus it would break the tightness of the memory.

Does the standard establish any guarantee about such a case?

Tarquiscani
  • 649
  • 7
  • 20
  • 2
    Why not just use an array? Also maybe related https://stackoverflow.com/questions/5397447/struct-padding-in-c – Sami Kuhmonen Jan 26 '19 at 15:14
  • Looks like to dare for _undefined behavior_ for me. What do you really want to achieve with such construct? – πάντα ῥεῖ Jan 26 '19 at 15:18
  • 1
    @SamiKuhmonen If you mean why I don't use an `std::vector` is because I only need to access each FloatStruct as a whole. Each index should point directly to FloatStruct without any interface between the vector and the user. And at the same time I need to send the whole chunk of tight memory to the GPU, so I can't have any holes. I know that I could make a simple custom container to achieve the same aim, but if there were any guarantees about the above code I would spare time – Tarquiscani Jan 26 '19 at 15:32
  • 1
    You might consider using something like a `gsl::span` (which is in the process of standardization) to group your array into sub groups. You can find an implementation here: https://github.com/Microsoft/GSL – Galik Jan 26 '19 at 15:36
  • From en.cppreference.com, std::vector: "The elements are stored contiguously, which means that elements can be accessed not only through iterators, but also using offsets to regular pointers to elements. " This also means that float elements 'are tightly packed', and will be for any code from a conforming C++ compiler. – 2785528 Jan 26 '19 at 15:37
  • 1
    @2785528 But if you make an array of structs it is implementation defined if they contain padding or not. So the float members can not be (reliably) addressed as a contiguous block throughout the container. – Galik Jan 26 '19 at 15:39
  • 1
    I have been looking for a duplicate. This isn't really a duplicate but I think it is addressing the same issue: https://stackoverflow.com/questions/12138243/cast-a-primitive-type-pointer-to-a-structure-pointer-alignment-and-padding – Galik Jan 26 '19 at 15:46
  • 1
    This is the opposite situation but it gives the right answer: https://stackoverflow.com/questions/31082389/casting-double-array-to-a-struct-of-doubles – Galik Jan 26 '19 at 15:52
  • Thanks. That code is definitely not portable – Tarquiscani Jan 26 '19 at 16:04

1 Answers1

1

To answer your question, yes there are platforms where that can cause trouble. Since you specifically mention that it is going to be sent to the GPU, it may be problematic for some architectures. I ran into this problem recently with Metal on macOS, where I wanted to reduce my data by 25% so sent only (x, y, z) instead of (x, y, z, 1.0) for my texture coordinates, and the result was that the coordinates were all off. (I believe the issues was that the GPU architecture assumed a certain padding that the CPU architecture did not provide, though I think it would have given similarly bad results if the opposite were true.)

The solution for cross-platform compatibility is probably to define a macro that ensures correct padding for each compiler you're using. Then when defining your data structure, use that macro to get the right padding. So it would look something like this:

#if MSVC
    #define PACK_TIGHTLY <MSVC-specific definition of tight packing>
    #define END_PACK_TIGHTLY <MSVC-specific definition of ending tight packing>
#elif  Clang
    #define PACK_TIGHTLY <clang-specific definition of tight packing>
    #define END_PACK_TIGHTLY <clang-specific definition of ending tight packing>
#elif
    // ... etc. for other platforms you want to support
#endif

Then when defining your struct you'd do something like:

PACK_TIGHTLY
struct FloatStruct
{
    FloatStruct() : 
        a(1.f), b(10.f), c(100.f), d(1000.f), e(10000.f) { }

    float a;
    float b;
    float c;
    float d;
    float e;
};
END_PACK_TIGHTLY
user1118321
  • 25,567
  • 4
  • 55
  • 86