Дэвид Холлман недавно написал в Твиттере следующий пример (который я немного сократил):
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);
Вы можете проверить макет в clang на godbolt и увидеть, что причина изменения размера заключается в FooBefore
, что в FooBefore
value
элемента размещается по смещению 16 (с сохранением полного выравнивания 8 из FooBeforeBase
), тогда как в FooAfter
value
элемента размещается в смещение 12 (эффективно с использованием FooAfterBase
tail-padding).
Для меня очевидно, что FooBeforeBase
- это стандартная компоновка, а FooAfterBase
- нет (поскольку не все элементы нестатических данных имеют одинаковый контроль доступа, [class.prop]/3). Но каково то, что FooBeforeBase
является стандартным макетом, который требует этого отношения байтов заполнения?
И gcc, и clang повторно FooAfterBase
заполнение FooAfterBase
, заканчиваясь на sizeof(FooAfter) == 16
. Но MSVC этого не делает, заканчивая 24. Есть ли требуемая раскладка в соответствии со стандартом и, если нет, почему gcc и clang делают то, что они делают?
Существует некоторая путаница, поэтому просто чтобы прояснить:
-
FooBeforeBase
является стандартным макетом -
FooBefore
нет (и it, и базовый класс имеют нестатические члены-данные, аналогичноE
в этом примере) -
FooAfterBase
- нет (он имеет нестатические элементы данных разного доступа) -
FooAfter
нет (по обеим выше причинам)