Почему sizeof(структура) не равен сумме sizeof всех её полей в C++?
Почему размер структуры больше суммы её полей?
Нет, это не баг компилятора. Это особенность работы с памятью, называемая выравниванием (alignment) и заполнением (padding). Процессор не читает память побайтово — он работает с данными блоками, размер которых обычно равен 4 или 8 байтам (в зависимости от архитектуры). Если данные не выровнены по границе их размера, процессору приходится делать два обращения к памяти вместо одного, что замедляет программу.
Рассмотрим ваш пример:
struct MyStruct {
char c; // 1 байт, выравнивание 1
int i; // 4 байта, выравнивание 4
short s; // 2 байта, выравнивание 2
};
Компилятор располагает поля в памяти так:
- c занимает байт 0.
- i должно начинаться с адреса, кратного 4. После c остались байты 1, 2, 3 — они не подходят. Компилятор пропускает их (это и есть padding) и размещает i начиная с байта 4.
- s должно начинаться с чётного адреса. После i (байты 4-7) идёт байт 8 — он чётный, поэтому s размещается на байтах 8-9.
- Теперь структура занимает байты 0-9 (10 байт). Но! Размер всей структуры должен быть кратен максимальному выравниванию среди её полей (здесь это 4 — от int). 10 не кратно 4, поэтому компилятор добавляет ещё 2 байта padding в конец (байты 10-11). Итог: 12 байт.
Вот как это выглядит в памяти (X — padding):
| c | X | X | X | i | i | i | i | s | s | X | X |
0 1 2 3 4 5 6 7 8 9 10 11
Почему порядок полей важен?
Если переставить поля, размер может измениться. Попробуйте:
struct Optimized {
int i; // байты 0-3
short s; // байты 4-5
char c; // байт 6
}; // Макс. выравнивание = 4, размер 7 → округляем до 8
Теперь padding нужен только в конце (1 байт), и структура занимает 8 байт вместо 12. Это называется оптимизацией размера структуры — группировка полей по убыванию размера.
Как проверить выравнивание?
Используйте оператор alignof:
#include <iostream>
struct Test {
char c;
double d;
};
int main() {
std::cout << "Выравнивание double: " << alignof(double) << "\n";
std::cout << "Выравнивание структуры: " << alignof(Test) << "\n";
std::cout << "Размер структуры: " << sizeof(Test) << "\n";
return 0;
}
Можно ли отключить выравнивание?
Да, с помощью директивы #pragma pack:
#pragma pack(push, 1) // упаковка по 1 байту
struct Packed {
char c;
int i;
short s;
};
#pragma pack(pop)
int main() {
std::cout << sizeof(Packed); // 7 — но это медленнее!
}
Однако так делать не рекомендуется — это ломает оптимизацию процессора и может привести к падению на некоторых архитектурах (например, ARM).
Итог
- Размер структуры всегда ≥ суммы размеров полей из-за padding.
- Padding добавляется для выравнивания полей по границам, кратным их размеру.
- Размер структуры округляется до кратного максимальному выравниванию её полей.
- Правильная сортировка полей (от большего к меньшему) экономит память.
Практический совет: как минимизировать размер структуры
В дополнение к основному ответу — вот несколько правил, которые помогут вам писать компактные структуры:
- Сортируйте поля по убыванию размера: сначала идут типы с большим выравниванием (double, long long, указатели), затем int, short, char. Это минимизирует количество "дырок" между полями.
- Группируйте мелкие поля: если у вас несколько char или bool, ставьте их рядом — они займут один блок.
- Используйте битовые поля для флагов:
struct Flags {
unsigned int a : 1; // 1 бит
unsigned int b : 3; // 3 бита
unsigned int c : 4; // 4 бита
// Итого: 8 бит = 1 байт (но может быть больше из-за выравнивания)
};
Если вам критична экономия памяти (например, в embedded-системах), используйте #pragma pack(1), но помните о возможном замедлении. В 99% случаев современные компиляторы сами хорошо оптимизируют расположение полей, если вы не вмешиваетесь.
Также полезно знать, что порядок полей в памяти гарантированно совпадает с порядком их объявления (до C++11 это было нестрого, но сейчас работает). А вот между классами с наследованием могут быть дополнительные padding'ы для выравнивания базовых классов.