In the following code, both D1 & D2 have the same data members. However, the object size is different depending on whether the last member of the base class is initialized.
#include <cstdint>
struct B1 { int x; short y = 0; };
struct D1 : B1 { short z; };
struct B2 { int x; short y; };
struct D2 : B2 { short z; };
static_assert(sizeof(D1) == sizeof(D2));
GCC 14.2 (and Clang 18.0.1) fails with:
<source>:7:26: error: static assertion failed
7 | static_assert(sizeof(D1) == sizeof(D2));
| ~~~~~~~~~~~^~~~~~~~~~~~~
<source>:7:26: note: the comparison reduces to '(8 == 12)'
What is the explanation for this behavior?
>Solution :
This is an arbitrary ABI choice for compatibility with C.
In the Itanium C++ ABI, used by GCC and Clang here, B2 is considered POD for the purpose of layout while B1 is not. (It is reasonable because B2 is a valid C type as well, while B1 is not.)
The ABI specifies that tail padding of a type will be reused only if it is not POD for the purpose of layout. (So e.g. memcpy/memset as typically used in C is safe on the base class subobject if the base class type is POD for the purpose of layout and won’t overwrite any derived class members. And in C the compiler is always allowed to overwrite padding if a member is modified, so even without calls to memcpy, etc. using the base class subobject in C would otherwise not be compatible with C++.)
Both B1 and B2 have two bytes tail padding. D1 will reuse it to fit the z member, while D2 will not and leave the two padding bytes empty instead. Because the size of the whole class must be a multiple of its alignment which is 4 because of the int member, D2 then also needs to add two additional new tail padding bytes.