David Hollman recently tweeted the following example (which I've slightly reduced):
struct FooBeforeBase {
double d;
bool b[4];
};
struct FooBefore : FooBeforeBase {
float value;
};
static_assert(sizeof(FooBefore) > 16);
//----------------------------------------------------
struct FooAfterBase {
protected:
double d;
public:
bool b[4];
};
struct FooAfter : FooAfterBase {
float value;
};
static_assert(sizeof(FooAfter) == 16);
You can examine the layout in clang on godbolt and see that the reason the size changed is that in FooBefore
, the member value
is placed at offset 16 (maintaining a full alignment of 8 from FooBeforeBase
) whereas in FooAfter
, the member value
is placed at offset 12 (effectively using FooAfterBase
's tail-padding).
It is clear to me that FooBeforeBase
is standard-layout, but FooAfterBase
is not (because its non-static data members do not all have the same access control, [class.prop]/3). But what is it about FooBeforeBase
's being standard-layout that requires this respect of padding bytes?
Both gcc and clang reuse FooAfterBase
's padding, ending up with sizeof(FooAfter) == 16
. But MSVC does not, ending up with 24. Is there a required layout per the standard and, if not, why do gcc and clang do what they do?
There is some confusion, so just to clear up:
FooBeforeBase
is standard-layoutFooBefore
is not (both it and a base class have non-static data members, similar toE
in this example)FooAfterBase
is not (it has non-static data members of differing access)FooAfter
is not (for both of the above reasons)
FooAfter
is also 24 bytes), but gcc and clang do - and it seems like that's a conscious choice on their parts. – Alurd