#опытным
Спасибо, @Ivaneo, за любезно предоставленный примерчик в рамках рубрики #ЧЗХ.
Выравнивание и так сбивает с толку молодых работяг. Поди разберись, как там компилятор добавляет паддинги. Но вот вам совсем странный пример. Две одинаковые структуры, одна с приватными полями, а другая с публичными:
class A1
{
private: // THIS IS PRIVATE
int a;
short s;
int b;
char d;
};
class A2
{
public: // THIS IS PUBLIC
int a;
short s;
int b;
char d;
};
Дальше возьмем, отнаследуемся от них и добавим в наследники чар поле:
class B1 : public A1
{
public:
char c;
};
class B2 : public A2
{
public:
char c;
};
Какими будут размеры объектов классов B1 и B2?
std::cout << sizeof(B1) << std::endl;
std::cout << sizeof(B2) << std::endl;
Можно подумать, что если набор полей и их порядок одинаковый, то размер будет одинаковый.
Но нет!
Вывод будет такой:
16
20
Убедитесь сами.
WAT? Куда делись 4 байта?
Начнем с того, что размер структур A1 и А2 одинаковый и равен 16 байтам. Компилятор в обоих случаях вставил 2 байта паддинга после short переменной и 3 после char.
Теперь давайте посмотрим на смещение поля
c в наследниках относительно начала объекта. Это можно сделать с помощью трюка из этого поста:std::cout << std::dec << std::bit_cast<std::uintptr_t>(&B1::c) << "\n";
std::cout << std::dec << std::bit_cast<std::uintptr_t>(&B2::c) << "\n";
Вывод:
13
16
Видно, что смещение поля
c относительно начала объекта для В1 и В2 разное. Для В2, в котором поля публичные, ожидаемо c начинается с 16-го байта. А вот для В1, где поля приватные, c начинается ровно там, где заканчивается char поле d.Конечно, стандартом не регламентируется выравнивание между полями базового класса и наследника. Уменьшение размера - это оптимизация компилятора.
Ключевую роль здесь играет понятие POD в терминах старого стандарта C++03, которое до сих пор влияет на решение, можно ли использовать хвостовое заполнение (tail padding) базового класса для размещения новых членов производного класса.
Хвостовое заполение - это те самые неиспользуемые байты, которые компилятор добавляет в конец объекта для удовлетворения требований выравнивания.
POD тип - массив или класс, не имеющий пользовательских конструкторов, приватных/защищённых нестатических данных, виртуальных функций, базовых классов и обладающий тривиальными копирующими операциями и деструктором.
Для pod типов нельзя оптимизировать хвостовое заполнение для сохранение ABI. Для не pod типов - можно.
Получается, что
A1 - не pod тип: компилятор оптимизировал хвостовой паддинг и поместил поле c внутрь него., сэкономив место. Но A2 - pod тип, поэтому хвостовое заполнение остается.Align yourself. Stay cool.
#optimization #compiler