C Language
Прокладка и упаковка структуры
Поиск…
Вступление
По умолчанию компиляторы C выстраивают структуры так, чтобы к каждому участнику можно было получить быстрый доступ, без каких-либо штрафов за «неудовлетворенный доступ», проблему с RISC-машинами, такими как DEC Alpha и некоторые ARM-процессоры.
В зависимости от архитектуры процессора и компилятора структура может занимать больше места в памяти, чем сумма размеров ее компонентов. Компилятор может добавлять дополнения между членами или в конце структуры, но не в начале.
Упаковка отменяет заполнение по умолчанию.
замечания
У Эрика Раймонда есть статья о The Lost Art of C Structure Packing, которая является полезным чтением.
Упаковочные конструкции
По умолчанию структуры заполняются на C. Если вы хотите избежать такого поведения, вы должны явно запросить его. В GCC это __attribute__((__packed__))
. Рассмотрим этот пример на 64-битной машине:
struct foo {
char *p; /* 8 bytes */
char c; /* 1 byte */
long x; /* 8 bytes */
};
Структура будет автоматически дополнена, чтобы иметь 8-byte
выравнивание и будет выглядеть так:
struct foo {
char *p; /* 8 bytes */
char c; /* 1 byte */
char pad[7]; /* 7 bytes added by compiler */
long x; /* 8 bytes */
};
Поэтому sizeof(struct foo)
даст нам 24
вместо 17
. Это произошло из-за 64-битного компилятора чтения / записи из / в память в 8 байтах слова на каждом шаге и очевидного при попытке написать char c;
один байт в памяти получает 8 байтов (т.е. слово) и потребляет только первый байт, а семь его байтов остаются пустыми и недоступны для любой операции чтения и записи для заполнения структуры.
Структурная упаковка
Но если вы добавите атрибут packed
, компилятор не добавит дополнения:
struct __attribute__((__packed__)) foo {
char *p; /* 8 bytes */
char c; /* 1 byte */
long x; /* 8 bytes */
};
Теперь sizeof(struct foo)
вернет 17
.
Используются обычно упакованные структуры:
- Чтобы сэкономить место.
- Форматировать структуру данных для передачи по сети без зависимости от каждого выравнивания архитектуры каждого узла сети.
Следует иметь в виду, что некоторые процессоры, такие как ARM Cortex-M0, не позволяют использовать неравномерный доступ к памяти; в таких случаях структура упаковки может привести к неопределенному поведению и может привести к сбою процессора.
Наложение структуры
Предположим, что эта struct
определена и скомпилирована с 32-битным компилятором:
struct test_32 {
int a; // 4 byte
short b; // 2 byte
int c; // 4 byte
} str_32;
Мы могли бы ожидать, что эта struct
займет всего 10 байт памяти, но, печатая sizeof(str_32)
мы видим, что она использует 12 байтов.
Это произошло потому, что компилятор выравнивает переменные для быстрого доступа. Общий шаблон состоит в том, что когда базовый тип занимает N байтов (где N - это мощность 2, такая как 1, 2, 4, 8, 16 - и редко больше), переменная должна быть выровнена на N-байтовой границе ( кратное N байтам).
Для структуры, показанной с sizeof(int) == 4
и sizeof(short) == 2
, общий макет:
-
int a;
хранится при смещении 0; размер 4. -
short b;
хранится со смещением 4; размер 2. - неназванное заполнение со смещением 6; размер 2.
-
int c;
хранится со смещением 8; размер 4.
Таким образом, struct test_32
занимает 12 байт памяти. В этом примере нет отступающих отступов.
Компилятор обеспечит сохранение любых переменных struct test_32
, начиная с 4-байтной границы, так что члены внутри структуры будут правильно выровнены для быстрого доступа. Функции распределения памяти, такие как malloc()
, calloc()
и realloc()
, необходимы для обеспечения того, чтобы возвращаемый указатель был достаточно хорошо выровнен для использования с любым типом данных, поэтому динамически распределенные структуры также будут правильно выровнены.
Вы можете столкнуться с нечетными ситуациями, например, на 64-разрядном процессоре Intel x86_64 (например, Intel Core i7 - Mac, работающем под управлением MacOS Sierra или Mac OS X), где при компиляции в 32-разрядном режиме компиляторы размещают double
выравнивание на 4-байтовая граница; но, на том же аппаратном обеспечении, при компиляции в 64-битном режиме компиляторы располагают double
выравниванием по 8-байтовой границе.