#новичкам
Сегодня поговорим о выравнивании чуть поближе, чтобы вы в первом приближении могли разобраться, как лежат данные в структурах.
🔍 Однобайтовые данные не нужно выравнивать. Да, процессор читает данные определенными порциями, которые больше 1 байта. Но для любой взятой char переменной, при попытке ее прочитать ее значение будет лежат в самом начале порции. Минимальная адресуемая единица - это 1 байт, поэтому однобайтовые данные неделимы. Не нужно никаких дополнительных плясок.
Для наглядности можно представить себе массив чаров:
char array[10];
std::cout << sizeof(array) << std::endl;
// OUTPUT: 10
Это просто 10 подряд идущих символов. И адреса каждого символа не нужно выравнивать. Поэтому и количество байт, которое занимает этот массив, равно просто количеству его элементов. Без всяких дополнений.
🔍 Адрес поля тривиального типа должен быть выровнен по размеру этого типа
- для short адреса должны быть кратными 2
- для int адреса должны быть кратными 4
- для size_t адреса должны быть кратными 8
- для float адреса должны быть кратными 4
- для double адреса должны быть кратными 8
- для указателей адреса должны быть кратными 8
Если сделать меньше, то придется читать несколькими порциями и вычислять из них нужное значение, что долго.
Если сделать больше, то будет понапрасну тратится память.
Все это актуально для современных 64-битных десктопов.
🔍 Адреса полей кастомных классов должны быть выровнены по длине самого жирного тривиального типа, входящего в состав класса. Не по размеру самого объекта, а по самому жирному его полю.
Например:
struct A {
char a;
char b;
};
struct B {
char first;
A second;
};
std::cout << sizeof(A) << std::endl;
std::cout << sizeof(B) << std::endl;
// OUTPUT:
// 2
// 3У нас есть составная структура А, размер которой равен 2 байтам. Структура B содержит А в качестве поля. Так как в А только char поля, для доступа к ним не нужны паддинги. Поэтому размер В равен 3-м.
Но если будет вот так:
struct A {
char a;
double b;
};
struct B {
char first;
A second;
};
std::cout << sizeof(A) << std::endl;
std::cout << sizeof(B) << std::endl;
// OUTPUT:
// 16
// 24Мало того, что паддинг размером 7 добавися перед полем
b структуры А, так он же еще добавится перед `second` в В. И в B он такого размера именно потому, чтобы доступ до b в итоге всех размещений был выровнен.🔍 Паддинг добавляется в основном перед тем полем, адрес которого должен быть выровнен. Мы это уже увидели на практике.
В реальности это также значит, что паддинг может добавляться в конце, чтобы обеспечить корректное выравнивание следующих данных.
Допустим, у нас есть структура и мы хотим из нее сделать массив:
struct A {
double a;
char b;
};
A array[10];Паддинг в 7 байт нужно добавить для того, чтобы адреса поля
a из всех элементов, начиная со второго, тоже были выровнены по границе 8.Не нужно добавлять паддинги после каждого поля, чтобы для всех них было одинаковое выравнивание. Выровненным по определенной границе должен быть лишь адрес данного конкретного поля. Паддинги добавляются независимо.
🔍 Чтобы оптимально использовать память, лучше располагать поля в порядке убывания их требований к выравниванию. Так вы предыдущим полем гарантируете нужное выравнивание следующему. Сравните:
struct A {
char a;
// 7 byte padding
double d;
short b;
// 2 byte padding
int c;
};
struct B {
double d;
int c;
short b;
char a;
// 1 byte padding
};
std::cout << sizeof(A) << std::endl;
std::cout << sizeof(B) << std::endl;
// OUTPUT:
// 24
// 16Реальный размер данных - 15 байт. Для double поля в любом случае нужно выравнивание по 8-ке. Но правильно расположив данные, мы экономим 8 байт.
Тема выравнивания довольно большая и мы прошлись только по верхам. Но даже эти простые принципы помогут вам правильно оценивать размер структур и организовывать их поля оптимальным образом.
Align yourself. Stay cool.
#cppcore #optimization #compiler