Дэвид Холлман недавно написал в Твиттере следующий пример (который я немного сократил):
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нет (по обеим выше причинам)