Распространенная ошибка, которую следует избегать при объявлении структуры в C

Мы часто объявляем и используем структуры (или структуры) в C в основном из-за гибкости, которую он обеспечивает при обработке данных. Язык программирования C от Kernighan & Ritchie определяет структуры как:

«Структура - это набор из одной или нескольких переменных, возможно, разных типов, сгруппированных вместе под одним именем для удобства работы»

В этой статье мы не будем фокусироваться на том, как определять, объявлять и инициализировать структуры. Если вы уже знакомы со структурами и заинтересованы в том, чтобы научиться использовать их с эффективным использованием памяти, вы нашли нужную статью. Давайте углубимся в обсуждение !!

Рассмотрим следующую структуру.

struct Node1 {
    uint8_t num1;
    uint64_t num2;
    uint8_t num3;
} node1;

Какого размера вы ожидаете от node1? 10 байт (1 + 8 + 1), основная математика !!! К сожалению, это НЕПРАВИЛЬНО.

printf("Size of node1: %zu \n", sizeof(node1));

Этот оператор printf покажет, что фактический размер составляет 24 байта (в 64-битной системе). Подождите минуту!!! Как это возможно?? Чтобы понять это, мы должны сначала понять, как работает выравнивание и заполнение памяти.

Выравнивание и отступ

Объем данных, выбранных и обработанных процессором за одну инструкцию, называется «размером слова». Просто процессор будет читать по одному слову за раз. В 32-битной системе размер слова составляет 32 бита (4 байта), аналогично в 64-битной системе размер слова составляет 64 бита (8 байтов). Мы будем основывать остальную часть обсуждения на 64-битной системе.

Давайте посмотрим на рисунок 1. Каждый квадратный квадрат представляет 1 байт памяти. Для простоты адреса памяти обозначены 0–23. Поскольку мы имеем дело с 64-битной системой, процессор будет читать ячейки памяти 0–7 (синий сегмент) вместе, 8–15 (желтый сегмент) вместе и 16–23 (зеленый сегмент) вместе. Предположим, если мы объявим целое число так:

int var;

Обычно для типа данных int требуется 32 бита или 4 байта. Вы можете выделить память для var где угодно от 0 до 23. Некоторые из возможностей следующие.

Одно очевидное, но важное наблюдение заключается в том, что выравнивания 2, 3 и 4 заставляют процессор извлекать наш var за два цикла (процессор должен получать синий сегмент в одном цикле и желтый сегмент в следующем цикле), тогда как выравнивания 1 и 5 требуют только одного цикла. (только синий сегмент для трассы 1 и только желтый сегмент для трассы 5). Итак, очевидно, что выравнивания 1 и 5 повышают эффективность. Ждать!! При чем здесь наша проблема?

Оказывается, система не распределяет память случайным образом, но требует выравнивания данных. Переменным в C выделяется память таким образом, что они начинаются с адреса памяти, кратного их размеру. Например, char длиной один байт может начинаться откуда угодно, short длиной 2 байта должен начинаться с адресов памяти, кратных 2, int длиной 4 байта должен начинаться с адресов памяти, кратных 4, и т. Д. Если вы рассматриваете выравнивания 1 и 5, выделенные ячейки памяти начинаются с 4 и 8, которые кратны 4.

Далее мы должны понять, что такое отступы, зачем они нужны и как они работают. Теперь мы знаем, что для выделения памяти существует требование выравнивания. Чтобы обеспечить это требование выравнивания, система использует отступы. Заполнение - это просто вставка пустых байтов (иногда оставляя их пустыми). Если мы вернемся к нашему struct Node1 примеру,

Так выглядит фактическое выделение памяти. Чтобы сделать его еще более ясным, если мы посмотрим на объявление ourstruct, то на самом деле происходит следующее:

struct Node1 {
    uint8_t num1;
    char padding[7];     //padding
    uint64_t num2;
    uint8_t num3;
    char padding[7];     //padding
}

По этой причине он потребляет 24 байта памяти вместо 10 байтов.

Однако мы можем обойти эту проблему и уменьшить объем потребляемой памяти с 24 до 16 байт.

struct Node1 {
    uint8_t num1;
    uint8_t num3;
    char padding[6];
    uint64_t num2;
}

В этом примере мы экономим только байт, что ничтожно мало по сравнению с объемом памяти, доступной на типичной машине той эпохи (которая часто содержит гигабайты памяти). Однако, когда структура имеет большое количество переменных и / или когда вы создаете большое количество структурных переменных, вы можете сэкономить значительный объем памяти. Кроме того, не все системы имеют гигабайты памяти. Особенно людям, которые работают со встроенными системами, приходится иметь дело с очень небольшим объемом памяти. Я оставлю обсуждение таких приложений в другой статье.

Надеюсь, эта статья окажется полезной !!!