1

I have a structure that looks like this:

struct UploadConfig {
private:
    const void *                m_data;                  //  4 bytes (32bit)
    glm::u16vec4                m_size;                  // 12 bytes (uint16 * 4 = +8)
    uint8                       m_mipmapsCount;
    TextureDim::Type            m_depthType;
    TextureInternalFormat::Type m_internalFormat;   
    TextureUploadFormat::Type   m_uploadFormat;          // 16 bytes
    TextureUploadComp::Type     m_uploadCompType;
    uint8                       m_swizzleR          :3;     
    uint8                       m_swizzleG          :3;
    uint8                       m_swizzleB          :3;
    uint8                       m_swizzleA          :3;
    bool                        m_minFilter         :1;
    bool                        m_minmipFilterUsed  :1;
    bool                        m_minmipFilter      :1;
    bool                        m_maxFilter         :1;
    bool                        m_edgeClampX        :1;
    bool                        m_edgeMirrorX       :1;
    bool                        m_edgeClampY        :1;
    bool                        m_edgeMirrorY       :1;
    bool                        m_edgeClampZ        :1;
    bool                        m_edgeMirrorZ       :1; // 20 bytes probably
};

const int iogwhgakfj = sizeof UploadConfig;  // this is reported as 16 (???)

Where the enums are defined as uint8 like this:

struct TextureDim {
    enum Type : uint8 {
        dim2D,
        dim3D,
        dimCubic
    };
};

But the size of this structure is really odd to me at 16 bytes, I expected it to be larger at 20 or even 24. Is the compiler turning my enums into bitfields behind my back? I mean... nice, but also seems weird it would do that with an enum type, and not a series of boolean. (Without the bitfields specified, this structure's size is 28)

edit

I tried adding some curveballs to confuse the compiler, but it still reports a size of 16 in the IDE (hovering over the value iogwhgakfj)

My pitches were:

void UploadConfig::setMinFilter() {
    // there's no way the compiler can predict this
    m_minFilter = (uintptr(&m_internalFormat) & 7) == (uintptr(&m_uploadCompType) & 7);
}

Curiously too, it refuses to compute offsetof(TextureUploadConfig, m_uploadCompType); in the IDE, but will do this for the values of m_mipmapsCount and the members that appear prior to it.

Conclusion

It was because the enum wasn't defined yet. Define your enum types before you use them, because Visual Studio will very nicely highlight the syntax as if it were. Also maybe don't mess your code up so bad that you can't compile for a week straight.

Anne Quinn
  • 12,609
  • 8
  • 54
  • 101
  • 1
    Try taking the address of the field. If that works, it cannot be a bitfield. If it doesn't you might have your answer. – gexicide Sep 17 '21 at 17:22
  • Comment out some of the fields and check sizeof again. Repeat until you understand where the compiler is deviating from your assumptions. – Ben Voigt Sep 17 '21 at 17:34
  • Adding to what @BenVoigt says - a good candidate would be the `glm::u16vec4` member. If that's defined in *anything* like the way a `std::vector` is, then its size may not be what you suppose it to be. – Adrian Mole Sep 17 '21 at 17:35
  • Your curveball shouldn't even compile without a cast to `uintptr_t`. You may also want some parentheses. – Ben Voigt Sep 17 '21 at 17:36
  • @AdrianMole - it's a numerical vector of 4 uint16, x, y, z, w. It's size is 8 – Anne Quinn Sep 17 '21 at 17:36
  • Be sure to also inspect the value properly, don't rely on the mouseover tooltip you get in the editor. The compiler used for Intellisense is designed to work on broken code in the middle of being edited, and does not always match what the real compiler does. – Ben Voigt Sep 17 '21 at 17:43
  • What compiler, architecture, ABI? Can you make it a [mcve]? – Nate Eldredge Sep 17 '21 at 17:45
  • @BenVoigt - Indeed it is... I found my problem, I'm a fool's dog idiot bastard, I had the enum defined after the structure, so it was just not sure what it was and didn't want to underline the undefined type. It now correctly reports 20 – Anne Quinn Sep 17 '21 at 17:46

1 Answers1

2

This is not a proper answer. Rather an extended comment with formatting.

On my machine (which has 8-byte pointers), I'm seeing this:

              m_data 0
              m_size 8
      m_mipmapsCount 16
         m_depthType 17
    m_internalFormat 18
      m_uploadFormat 19
    m_uploadCompType 20
          m_swizzleR 21[.....210]
          m_swizzleG 21[..543...]
          m_swizzleB 22[.....210]
          m_swizzleA 22[..543...]
         m_minFilter 22[.6......]
  m_minmipFilterUsed 22[7.......]
      m_minmipFilter 23[.......0]
         m_maxFilter 23[......1.]
        m_edgeClampX 23[.....2..]
       m_edgeMirrorX 23[....3...]
        m_edgeClampY 23[...4....]
       m_edgeMirrorY 23[..5.....]
        m_edgeClampZ 23[.6......]
       m_edgeMirrorZ 23[7.......]

The quick-and-dirty code for that output is:

#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <cstring>

using std::cout;
using std::memset;
using std::ostringstream;
using std::setw;
using std::string;

using uint8 = unsigned char;
using uint16 = unsigned short;

namespace glm {
struct u16vec4 { uint16 data[4]; };
}

struct TextureDim { enum Type : uint8 { }; };
struct TextureInternalFormat { enum Type : uint8 { }; };
struct TextureUploadFormat { enum Type : uint8 { }; };
struct TextureUploadComp { enum Type : uint8 { }; };

struct UploadConfig {
    const void *                m_data;                  //  4 bytes (32bit)
    glm::u16vec4                m_size;                  // 12 bytes (uint16 * 4 = +8)
    uint8                       m_mipmapsCount;
    TextureDim::Type            m_depthType;
    TextureInternalFormat::Type m_internalFormat;
    TextureUploadFormat::Type   m_uploadFormat;          // 16 bytes
    TextureUploadComp::Type     m_uploadCompType;
    uint8                       m_swizzleR          :3;
    uint8                       m_swizzleG          :3;
    uint8                       m_swizzleB          :3;
    uint8                       m_swizzleA          :3;
    bool                        m_minFilter         :1;
    bool                        m_minmipFilterUsed  :1;
    bool                        m_minmipFilter      :1;
    bool                        m_maxFilter         :1;
    bool                        m_edgeClampX        :1;
    bool                        m_edgeMirrorX       :1;
    bool                        m_edgeClampY        :1;
    bool                        m_edgeMirrorY       :1;
    bool                        m_edgeClampZ        :1;
    bool                        m_edgeMirrorZ       :1;
};

const int iogwhgakfj = sizeof(UploadConfig);

static int diff(void* b, void* m) {
    auto bb = static_cast<char*>(b);
    auto mm = static_cast<char*>(m);
    return static_cast<int>(mm - bb);
}

static string zrange(void* b, size_t len) {
    auto bb = static_cast<unsigned char*>(b);
    ostringstream ss;
    auto sep = "";
    for (size_t pos = 0; pos < len; ++pos) {
        if (bb[pos] == 0xFF) continue;
        ss << sep << pos << "[";

        auto u = bb[pos];
        if ((u & 0b1000'0000) == 0) ss << "7"; else ss << ".";
        if ((u & 0b0100'0000) == 0) ss << "6"; else ss << ".";
        if ((u & 0b0010'0000) == 0) ss << "5"; else ss << ".";
        if ((u & 0b0001'0000) == 0) ss << "4"; else ss << ".";
        if ((u & 0b0000'1000) == 0) ss << "3"; else ss << ".";
        if ((u & 0b0000'0100) == 0) ss << "2"; else ss << ".";
        if ((u & 0b0000'0010) == 0) ss << "1"; else ss << ".";
        if ((u & 0b0000'0001) == 0) ss << "0"; else ss << ".";

        ss << "]";
        sep = " ";
    }

    return ss.str();
}

int main() {
    cout << "Size of UploadConfig: " << iogwhgakfj << "\n";
    UploadConfig u;
#define DIFF(member) cout << setw(20) << #member << " " << diff(&u, &u.member) << "\n"
#define DIFFX(bitmember) memset(&u, 0xFF, sizeof u); u.bitmember = 0; cout << setw(20) << #bitmember << " " << zrange(&u, sizeof u) << "\n"
    DIFF(m_data);
    DIFF(m_size);
    DIFF(m_mipmapsCount);
    DIFF(m_depthType);
    DIFF(m_internalFormat);
    DIFF(m_uploadFormat);
    DIFF(m_uploadCompType);
    DIFFX(m_swizzleR);
    DIFFX(m_swizzleG);
    DIFFX(m_swizzleB);
    DIFFX(m_swizzleA);
    DIFFX(m_minFilter);
    DIFFX(m_minmipFilterUsed);
    DIFFX(m_minmipFilter);
    DIFFX(m_maxFilter);
    DIFFX(m_edgeClampX);
    DIFFX(m_edgeMirrorX);
    DIFFX(m_edgeClampY);
    DIFFX(m_edgeMirrorY);
    DIFFX(m_edgeClampZ);
    DIFFX(m_edgeMirrorZ);
#undef DIFF
#undef DIFFX
}
Eljay
  • 4,648
  • 3
  • 16
  • 27
  • I feel extraordinarily bad that my oversight prompted you to investigate this with such rigor. This ends up being correct to my question as answered, which is that what I did was impossible, and the compiler should generate this layout. – Anne Quinn Sep 17 '21 at 17:52
  • @AnneQuinn • No problem. :-) This is what I do while I'm compiling my project so I don't go stir crazy. It was a fun one. – Eljay Sep 17 '21 at 17:53