Влияние __attribute __ ((упаковано)) на вложенный массив структур? - PullRequest
16 голосов
/ 31 октября 2011

Проблема

Я работаю над отправкой необработанной структуры по сети в известную программу на другой стороне, но мне нужно беспокоиться о незаметно введенной памяти, используемой для выравнивания структур (рассматриваются другие вопросы, такие как порядок байтов). Я работаю с чем-то вроде:

typedef struct __attribute__((packed))
{
   uint16_t field1;
   uint16_t field2;
   uint16_t field3;
} packed_1_s;

typedef struct __attribute__((packed))
{
   uint16_t fieldA;
   uint16_t fieldB;
   packed_1_s structArray[10];
} packed_2_s;

typedef struct __attribute__((packed))
{
   uint16_t fieldX;
   packed_2_s fieldY;
   uint8_t arrayZ[20];
} data_s;

Я понимаю, что обычно для структуры pack_1_s может быть выделено дополнительное пространство для каждого экземпляра структуры, чтобы заполнить его до предпочтительного размера компилятора (в зависимости от аппаратного обеспечения, для которого он создается), и этот предпочтительный размер может быть где угодно от 2 до 64 байтов (совсем недавно). Обычно, если бы у меня был один экземпляр pack_1_s в pack_2_s, проблем не было бы, но я дал понять, что есть некоторые различия, когда вы пытаетесь поместить элементы в массив.

Попытки решения

Документация gcc, по-видимому, предполагает, что при простом включении упакованного атрибута в определение pack_2_s поля, даже если они являются массивами, будут упакованы настолько плотно, насколько это возможно, и не будут добавлять пространство в структуру pack_2_s для выровнять элементы массива. Документация по атрибуту align (), тем не менее, предполагает, что массивы обрабатываются не так, как другие поля, и для них необходимо установить атрибут align / pack непосредственно в поле, чтобы изменить дополнительный интервал, добавленный в соответствии с указанным выравниванием (или его отсутствием). Я попытался установить упакованный атрибут в поле structArray, и когда это не сработало, провел тест, установив упакованный атрибут для arrayZ в приведенном выше коде:

packed_1_s structArray[10] __attribute__((packed));

uint8_t arrayZ[20] __attribute__((packed));

Обе попытки дали мне предупреждение компилятора о том, что упакованный атрибут не был понят в этом контексте и будет пропущен (хорошо, что я строю с "-Wall").

Я надеялся, что одним из способов решения этой проблемы было бы использование атрибута align (1), указывающего желаемое выравнивание на 1 байт, которое сопоставимо с упакованным атрибутом, но документация говорит, что атрибут align () может только увеличить выравнивание и упаковывать следует использовать для уменьшить выравнивание.

Вопросы

Из того, что я могу определить из документации GCC, кажется, что есть 3 основных случая вставки дополнительной памяти.

  1. Содержанию структуры может быть выделена дополнительная память для сама структура для изменения расстояния между полями.
    Эффективно, определение карты памяти структуры содержимое в структуре может измениться (но не порядок элементов).
  2. Структурам может быть выделена дополнительная память для заполнения их в более эффективный общий размер. Это вообще предназначено, чтобы другие переменные приходили после объявления одного из их экземпляры не попадают в тот же «блок», что и Экземпляр структуры, где "блок" определяется системой / компилятором.
  3. Массивы, будь то внутри структуры или нет, могут иметь дополнительные к ним добавлена ​​память для смещения элементов до эффективного выравнивания.

Насколько я могу судить, упакованный атрибут может использоваться, чтобы влиять на структуры и блокировать дополнительную память, добавленную в случаях 1 и 2 выше, но, похоже, нет способа обработать случай 3 выше на моем компилятор (ы).

Вопрос

Есть ли способ гарантировать, что к структуре data_s не будет добавлено абсолютно никакого дополнительного пространства или какой-либо из его подструктур, поэтому у меня нет зависимых от компилятора сдвигов в карте памяти? Я неправильно понимаю случаи, когда компилятор может вставить пробел для преднамеренного смещения карты памяти?

EDIT

Я обсуждал некоторые вопросы с моим местным гуру, и похоже, что у меня есть какое-то недопонимание в случае 3 выше.Элементам в массиве не вставлено пространство между ними, но дополнительное пространство для гарантии их правильного выравнивания добавляется к самой структуре.Очевидно, это предполагает, что такие вещи, как «sizeof (structureOnlyConisting_uint32_t)», не всегда будут возвращать «4», так как может быть добавлено дополнительное пространство для выравнивания типа данных uint32_t в используемом компиляторе.В результате на самом деле есть только 2 случая:

  1. Большие смещения между полями в карте памяти структуры.
    Пространство между полями может быть изменено для выравнивания каждогополе.Это можно изменить с помощью атрибутов pack или align ().
  2. Заполнение конца структуры.Размер структуры, возвращаемый функцией sizeof (), можно изменить, чтобы массивы структур были правильно выровнены для системы.Это позволяет всем системам предполагать, что начало структур всегда будет выровнено, в противном случае возникают проблемы.Это кажется незатронутым атрибутами pack или align.

Из-за нового случая 2, элементы массива в структуре не обязательно подчиняются атрибутам pack или align (), указанным вструктура, хотя начало массива и поле, следующее непосредственно за массивом, делают.

Мой вопрос тогда о том, как обращаться с structArray в pack_2_s, так как размер массива в целом не может быть гарантирован чистопо упакованному атрибуту.Есть ли способ гарантировать фиксированный размер поля structArray в целом?Следует отметить, что я не могу слишком сильно увеличить размер pack_1_s, поскольку структуру data_s необходимо поддерживать как можно меньше (ее замену аудио / видео данными в сценарии потоковой передачи).

1 Ответ

22 голосов
/ 31 октября 2011

Обратите внимание на следующие моменты о __attribute__((packed)):

  • Когда packed используется в объявлении структуры, оно сжимает свои поля так, что sizeof (структура) == sizeof (first_member) + ... + sizeof (last_member).

  • Здесь массив - это всего лишь один член структуры. Упаковка содержащей структуры массива не изменит размер массива. Фактически, размер (любого) массива равен всегда sizeof (элемент) * number_of_elements.

  • Аналогичным образом, упаковка вмещающей структуры внутренней структуры не изменит размер внутренней структуры. Размер структуры полностью определяется ее объявлением и является одинаковым независимо от того, где вы используете.

  • Упаковка структуры приведет к ее необходимому выравниванию на один байт (то есть она может быть помещена в любое место в памяти).

  • Упаковка создаст проблемы с выравниванием при доступе к полям упакованной структуры. Компилятор будет учитывать это, когда к полям обращаются напрямую, но не тогда, когда к ним обращаются через указатели . Конечно, это не относится к полям с необходимым выравниванием (например, к символам или другим упакованным структурам). См. мой ответ на аналогичный вопрос , который включает в себя программу, демонстрирующую проблему с доступом к членам с помощью указателей.

Наконец, чтобы ответить на вопрос,

Есть ли способ гарантировать, что структура data_s будет иметь абсолютно никакое дополнительное пространство не добавлено к этому или любому из его подструктуры, поэтому у меня нет зависимых от компилятора сдвигов в памяти карта

Да. Объявите структуру как упакованную, а также все структуры, которые она содержит, рекурсивно.

Также обратите внимание, что упакованный атрибут применяется к объявлению структуры, а не к типу. Нет такой вещи, как упакованная версия структуры , которая объявлена ​​неупакованной. Когда вы где-то используете структуру, она (ее члены) будет упакована, если и только если сама структура была объявлена ​​упакованной. Это отчасти подразумевается тем фактом, что размер структуры полностью определяется ее объявлением.

ОБНОВЛЕНИЕ : По какой-то причине вы все еще не уверены в массивах. Предоставленное мною решение (объявлять все структуры упакованными) работает и с массивами . Например:

struct elem_struct {
    uint32_t x;
} __attribute__((packed));
// packed guarantees that sizeof(struct elem_struct) = sizeof(uint32_t) = 4

struct array_struct {
    struct elem_struct arr[10];
} __attribute__((packed));
// packed guarantees that sizeof(struct array_struct) =
// = sizeof(struct elem_struct[10]) = 10 * sizeof(struct elem_struct)
// = 10 * 4 = 40

Два дополнительных замечания, которые вы сделали относительно массивов, верны - но только когда структуры не упакованы. Упаковка заставляет поля структуры быть непрерывными, и это действительно создает проблемы выравнивания, которые, если бы не использовалась упаковка, были бы решены путем вставки пустого пространства между членами и заполнения структуры (см. Пункт I поднят о выравнивании).

...