Thursday, April 30, 2009

The C++ Empty Struct Conundrum

Empty structs are treated differently in C++ than in C. Heck, they're treated differently by different C++ compilers :-(

Here's some weirdness with empty structures being treated differently (i.e. different offsets, sizes, binary layout) between C and C++.

struct foo { };
struct foo2 { };

struct bar
{
    foo var1;
    int var2;
};

struct bar2 : foo
{
    int var2;
};

struct bar3 : foo
{
    int a,b;
};

struct bar4 : foo
{
    bar3    var3;
};

struct bar5 : foo, foo2
{
    int    d;
};

struct bar6 : bar2, foo2
{
    int    d;
};

struct bar7 : foo2
{
    bar2    v;
    int        d;
};

C89
    sizeof(foo) == 0
    sizeof(bar) == 4
    rest are C++-only (inheritance)

C99
    empty "struct foo" not allowed
    sizeof(bar) == 4
    rest are C++-only (inheritance)

C++ Standard (comments show where the C++
        standard requires padding / wasted space)
    sizeof(foo) == 1 (* typical value)
        -- sizeof(foo) != 0   (usually 1 but 
        possibly some other value that is
        compiler/alignment dependent)
    sizeof(bar) == 8
        !!! Wasting space on empty struct !!!
    C++-only:
    sizeof(bar2) == 4
    sizeof(bar3) == 8
        OK - no padding before first member
    sizeof(bar4) == 12
        !!! pad before first member due to
        identical-empty-base conflict between
        base class and first member
    sizeof(bar5) == 4
    sizeof(bar6) == 8
    sizeof(bar7) == 8

GCC C++ (PS3): -- Fully compliant to Standards
        with best-allowed empty-base-optimization
    sizeof(foo)  == 1
    sizeof(foo2) == 1
    sizeof(bar)  == 8
    sizeof(bar2) == 4
    sizeof(bar3) == 8
    sizeof(bar4) == 12
    sizeof(bar5) == 4
    sizeof(bar6) == 8
    sizeof(bar7) == 8

MSVC++
    sizeof(foo)  == 1
    sizeof(foo2) == 1
    sizeof(bar)  == 8
    sizeof(bar2) == 4
    sizeof(bar3) == 8
    sizeof(bar4) == 8
        !!! NONCOMPLIANCE !!! -- sizeof() should be 12
         - overaggressive empty-base-optimization
        (bar4::foo not allowed to resolve to
        same address as bar4.var3::foo) 
    sizeof(bar5) == 8
        Wasted Space - standard allows
        empty-base-optimization of foo & foo2
    sizeof(bar6) == 12
        Wasted Space - standard allows
        empty-base-optimization of foo & foo2
    sizeof(bar7) == 8



-------------------



struct NullCounterNoInit
{
    FORCEINLINE void Inc()            { }
};


struct ThreadOwnedCounter
{
    UINT32    m_nCountVal;
    FORCEINLINE UINT32 Inc() { return(++m_nCountVal); }
};


template 
class Test1
{
    TCounter    counter;
    int            foo;
    int ReadFoo() { counter.Inc(); return(foo); }
}

template 
class Test2 : TCounter
{
    int        foo;
    int ReadFoo() { TCounter::Inc(); return(foo); }
}

sizeof(Test1) == 8
    !!! Wasting space on empty struct !!!
sizeof(Test2) == 4

sizeof(Test1) == 8
sizeof(Test2) == 8