Home > Software engineering >  How sizeof a not-polymorphic C class can be larger than the summed sizeof of its members?
How sizeof a not-polymorphic C class can be larger than the summed sizeof of its members?

Time:12-04

In the following example struct E inherits structs C and D and has no other data members:

struct A{};
struct B{};
struct C : A, B {};
struct D : A, B {};
struct E : C, D {};

int main() {
    static_assert(sizeof(C) == 1);
    static_assert(sizeof(D) == 1);
    //static_assert(sizeof(E) == 2); // in Clang and GCC
    static_assert(sizeof(E) == 3); //in MSVC
}

In all compilers I have tested, sizeof(C)==1 and sizeof(D)==1, and only in MSVC sizeof(E)==3 so more than the summed size of its parents/members, demo: https://gcc.godbolt.org/z/aEK7rjKcW

Actually I expected to find sizeof(E) <= sizeof(C) sizeof(D) (less in case empty base optimization). And there is hardly any padding here, otherwise sizeof(E) would be 2 or 4.

What is the purpose of extra space (sizeof(E)-sizeof(C)-sizeof(D) == 1) in E?

CodePudding user response:

First, it can be larger than the sum of sub-objects due to padding and alignment. However, you're probably aware of that, and that's not what you are asking.

To determine the layout in your case, you can print the offsets of all the sub-objects (and the sizes of their types) using the following code:

static E x;
int main() {
    E *e = &x;
    C *c = e;
    D *d = e;
    A *ca = c, *da = d;
    B *cb = c, *db = d;
#define OFF(p) printf(#p " %d %d\n",  (int)((char*)p - (char*)e), (int)sizeof(*p))
    OFF(e);
    OFF(c);
    OFF(ca);
    OFF(cb);
    OFF(d);
    OFF(da);
    OFF(db);
}

The output for gcc/clang is:

e 0 2
c 0 1
ca 0 1
cb 0 1
d 1 1
da 1 1
db 1 1

The output for MSVC is:

e 0 3
c 0 1
ca 0 1
cb 1 1
d 2 1
da 2 1
db 3 1

This indicates that the way MSVC implements EBO is different from other compilers. In particular, instead of placing A and B at the same address within C, and at the same address within D (like other compilers do), it put them at different offsets. Then, even though sizeof(C) == 1, it allocates the full two bytes for it when it is a sub-object. This is most likely done so to avoid cb aliasing some other B from another sub-object, even though it wouldn't be a problem in this scenario.

  • Related