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?
4
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.
3