Ответ на этот вопрос исходит не от стандарта, а от ABI Itanium (именно поэтому gcc и clang имеют одно поведение, а msvc - другое).Что ABI определяет макет , соответствующими частями которого для целей этого вопроса являются:
Для внутренних целей спецификации мы также указываем:
- dsize (O): размер данных объекта, который является размером O без дополнения хвостом.
и
Мы игнорируем хвостовое заполнение для POD, потому что ранняя версия стандарта не позволяла нам использовать его для чего-либо еще, а также потому, что иногда допускается более быстрое копирование типа.
Где размещение членов, отличных от виртуальных базовых классов, определяется как:
Начинается со смещения dsize (C), увеличивается при необходимости для выравнивания по nvalign (D) для базовых классов или для выравнивания(D) для членов данных.Поместите D в это смещение, если только [... не релевантно ...].
Термин POD исчез из стандарта C ++, но означает макет стандарта и легко копируется.В этом вопросе FooBeforeBase
- это POD.Itanium ABI игнорирует хвостовое заполнение - следовательно, dsize(FooBeforeBase)
равно 16.
Но FooAfterBase
не является POD (его легко копировать, но это , а не стандартная компоновка).В результате, отступы хвоста не игнорируются, поэтому dsize(FooAfterBase)
- это всего 12, и float
может пойти прямо туда.
Это имеет интересные последствия, как указал Quuxplusone в ответе , разработчики также обычно предполагают, что дополнение хвоста не используется повторно, что приводит к хаосу в этом примере:
#include <algorithm>
#include <stdio.h>
struct A {
int m_a;
};
struct B : A {
int m_b1;
char m_b2;
};
struct C : B {
short m_c;
};
int main() {
C c1 { 1, 2, 3, 4 };
B& b1 = c1;
B b2 { 5, 6, 7 };
printf("before operator=: %d\n", int(c1.m_c)); // 4
b1 = b2;
printf("after operator=: %d\n", int(c1.m_c)); // 4
printf("before std::copy: %d\n", int(c1.m_c)); // 4
std::copy(&b2, &b2 + 1, &b1);
printf("after std::copy: %d\n", int(c1.m_c)); // 64, or 0, or anything but 4
}
Здесь =
делает все правильно (он не отменяет заполнение хвоста B
), но copy()
имеет оптимизацию библиотеки, которая уменьшается до memmove()
- чтоне заботится о дополнении хвоста, потому что предполагает, что его не существует.