Читать книгу Ссылки и указатели в C++: от основ к безопасности и современному коду - - Страница 80
sizeof vs реальный размер почему компилятор "обманывает" нас байтами
ОглавлениеТеперь давайте разберёмся с одной из самых коварных ловушек в C++: почему sizeof иногда возвращает число, которое кажется "завышенным", и что такое "реальный" размер данных. Представьте, что вы упаковываете чемодан для поездки: "реальный" размер объём ваших вещей (рубашки, брюки), но компилятор добавляет "пустоты" (padding), чтобы чемодан был удобным в транспортировке выровненным и быстрым в доступе. В итоге sizeof это размер всего чемодана, а не только вещей внутри. Это не баг, а фича для производительности, но без понимания приведёт к трате памяти и сюрпризам.
Что такое sizeof? Оператор sizeof(T) (или sizeof expr) возвращает размер типа T или выражения в байтах это compile-time константа. Для простых типов: sizeof(char)=1, sizeof(int)=4 (обычно), sizeof(double)=8. Для массивов: sizeof(arr) = N * sizeof(T). Но для структур/классов sizeof включает padding байты, вставленные компилятором для выравнивания полей. "Реальный" размер сумма sizeof каждого поля без padding, но в памяти объект занимает sizeof, включая "пустоты".
Почему разница? Из-за выравнивания (alignment): процессор быстрее работает с данными на границах (кратно 4/8 байтам). Компилятор добавляет padding, чтобы каждое поле начиналось на нужном адресе, и весь объект был кратен своему выравниванию (для массивов). Без padding доступ замедлился бы или вызвал UB.
Аналогия: представьте полки в шкафу. Книги (поля) разной толщины: тонкая (char=1B) и толстая (int=4B). Чтобы толстая стояла ровно (выровнена), после тонкой добавляем "пустые обложки" (padding). Итоговый "шкаф" (sizeof) больше суммы толщин книг.
Пример классический:
#include <iostream>
struct Example {
char a; // 1 байт (offset 0)
int b; // 4 байта, но требует выравнивания 4 → 3 байта padding после a (offset 4)
char c; // 1 байт (offset 8)
};
int main() {
std::cout << "sizeof(Example): " << sizeof(Example) << " байт (ожидаемо 12)" << std::endl;
std::cout << "Реальный размер полей: " << sizeof(char) + sizeof(int) + sizeof(char) << " байт (6)" << std::endl;
std::cout << "Offset b: " << offsetof(Example, b) << " (4, с padding)" << std::endl;
std::cout << "Offset c: " << offsetof(Example, c) << " (8)" << std::endl;
return 0;
}
Здесь sizeof=12 (1 + 3 pad + 4 + 1 + 3 pad, чтобы весь struct был кратен 4). "Реальный" 6 байт, но память тратит 12 в 2 раза больше! В массиве Example arr[1000] это 12KB вместо 6KB.