Search code examples
c++inheritancememorystructsizeof

Why does class C, which only contains member variables of type B, have sizeof(C) not equal to sizeof(B)?


I have written three classes: A, B, and C. A has no member variables, B has an integer type member variable, and C has a member variable of type B, as shown below:

class A {
public:

};

class B  : public A{
private:
    int value_;
};

class C : public A{
private:
    B a;
};

What surprises me is that sizeof(C) is 8 instead of 4, as expected like sizeof(B). I had considered that this might be due to padding, but when I removed either C inheriting from A or B inheriting from A, sizeof(C) became 4 as expected.

I observe the same behavior on a 64-bit machine when using both GCC and Clang.

I want to know why the compiler adds an additional 4 bytes of storage overhead when compiling C.


Solution

  • There are two A subobjects in C: The direct A base class subobject and the A base class subobject of the B member subobject.

    The standard requires that these two objects, because they have the same type and overlapping lifetime, and because one is not nested within the other, do not have the same address. (see [intro.object]/9)

    As a consequence it isn't possible to layout the class C so that both the direct A subobject and the B member subobject are allocated at offset 0.

    Assuming allocation starts in order with the direct A base class subobject at offset 0, then the best possible allocation strategy that also respects alignment requirements is to put the B member subobject at offset 4 which is the alignment requirement of both int and B. Consequently the total size will be at least 8.

    Technically I think it would also be permitted by the standard (ignoring the issue raised at the end of this question) to layout the B member subobject at offset 0 and the A member subobject at any offset other than 0, because the A base class subobject is a subobject of zero size that may overlap with part of the int subobject. I think there is also no ordering requirement that enforces the base class subobject to have an address comparing smaller-or-equal to that of the B member subobject. With that choice the sizeof(C) == 4 would be possible. But that would be a surprising layout.

    So far about the requirements imposed on layout by the standard, but the actual specific layout that the compiler will choose is not specified by the standard. Instead it would be determined by the C++-ABI in use, which is the Itanium C++ ABI if you are compiling with GCC or Clang on a Linux system. The allocation algorithm used by this ABI is specified here (for classes that aren't POD for the purpose of layout, which C isn't). You can see the special case in there:

    Place D at this offset unless doing so would result in two components (direct or indirect) of the same type having the same offset. [...]

    MSVC, following its own C++-ABI instead, says sizeof(C) == 4 and by my tests seems to give both the direct A base class subobject as well as the B member subobject offset 0, using a different allocation strategy that doesn't conform to the above requirements.


    Unfortunately there is a defect in the standard that makes it impossible to correctly layout C.

    On the one hand as shown above, B must be placed at an offset from the direct A base subobject, however according to the current definition in the standard C is a standard-layout class, which implies per [basic.compound]/4 that both the direct A base class subobject as well as the B member subobject as first non-static member of C must be placed at the same address as the C object, i.e. at offset 0.

    This is a defect in the standard that (I guess) will probably be resolved by making it so that C is not standard-layout. Then MSCV's ABI would remain non-conforming, which however also might make adopting that resolution to the defect difficult.

    See open CWG issue 2736.