#новичкам
Мы все про выравнивание, да про выравнивание. Куча средств языка, которые позволяют грамотно работать с требованиями к alignment'у данных.
А что если меня это все достало и я просто хочу понятной человеческой укладки данных? Подряд, без всяких паддингов непонятных. Можно так?
В С++ можно все. Ну почти.
Решение лежит за рамками стандарта. Оно и понятно, правила для выравнивания данных в нем очень гибкие, в основном все дается на откуп реализации. И инструменты для мануального управления выравниванием тоже находятся в руках конкретных компиляторов.
✅
#pragma pack. Это нестандартная директива препроцессора, которая тем не менее поддерживается большой тройкой компиляторовВ общем виде
#pragma pack может использоваться тремя основными способами(будет немного нудно, но дождитесь примеров):#pragma pack(push, n)
#pragma pack(pop)
#pragma pack(n)
-
n - целое число, обычно степень двойки: 1, 2, 4, 8, 16 и т.д. Оно задаёт максимальное выравнивание для каждого члена. Член будет размещён по смещению, кратному min(n, alignof(тип)). Фактически n ограничивает выравнивание сверху.-
push - помещает текущее значение упаковки в стек (сохраняет его). Если после pushуказано n, то сначала сохраняется текущее, а затем устанавливается новое значение.-
pop - извлекает последнее сохранённое значение из стека и восстанавливает его.На примерах это выглядит так:
struct Test {
char c; // смещение 0
int i; // смещение 4 (3 байта паддинга)
};
static_assert(sizeof(Test) == 8);
static_assert(alignof(Test) == 4);
#pragma pack(push, 1)
struct Packed {
char c; // смещение 0
int i; // смещение 1 (паддинга нет)
};
#pragma pack(pop)
static_assert(sizeof(Packed) == 5);
static_assert(alignof(Packed) == 1);
#pragma pack(push, 2)
struct Packed2 {
char c; // смещение 0
double d; // смещение 2 (1 байт паддинга после c)
};
#pragma pack(pop)
static_assert(sizeof(Packed2) == 10);
static_assert(alignof(Packed2) == 2);Особенность механики работы со стеком в том, что мы можем применять одинаковое выравнивание для целого набора структур одной директивой.
✅
attribute((packed)). Этот атрибут поддерживается gcc и clang. Механика у него чуть попроще - полностью убирает паддинги и выставляет выравнивание 1 для самого типа:struct attribute((packed)) PackedStruct {
char c;
int i;
short s;
};
static_assert(sizeof(PackedStruct) == 7);
static_assert(alignof(PackedStruct) == 1);Удобно, если не нужно сложной логики.
✅ У нас же есть стандартный плюсовый синтаксис атрибутов. Давайте его и используем. В C++11 и новее также можно написать
[[gnu::packed]] и эффект будет такой же, как в предыдущем пункте:struct [[gnu::packed]] PackedStruct1 {
char c;
int i;
short s;
};
static_assert(sizeof(PackedStruct1) == 7);
static_assert(alignof(PackedStruct1) == 1);Самая главная причина использовать плотную упаковку данных - это когда вам нужно в точности соответствовать компоновке данных в языке какому-либо требованию на уровне битов (аппаратура или протокол) и для этого требуется нарушить обычное выравнивание.
Большинство сетевых протоколов определяют строгую последовательность полей без лишних байтов. Использование упакованных структур позволяет им в точности соответствовать спецификации.
Но надо обязательно помнить про досуп к невыровненным данным: на некоторых архитектурах это в принципе приводит к ошибкам, а на других - к потенциальной деградации производительности(но это не точно, надо мерять).
Align yourself. Stay cool.
#cpp11 #compiler #NONSTANDARD