Влияние __attribute__((упаковано)) на вложенный массив структур?

Эта проблема

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

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(), тем не менее, предполагает, что массивы обрабатываются не так, как другие поля, и для них требуется, чтобы атрибут выравнивания / запаковки устанавливался непосредственно в поле, чтобы модифицировать дополнительный интервал, добавленный в соответствии с указанным выравниванием (или его отсутствием). Я попытался установить упакованный атрибут в поле 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 не будет добавлено абсолютно никакого дополнительного пространства или какой-либо из его подструктур, поэтому у меня нет зависимых от компилятора сдвигов в карте памяти? Я неправильно понимаю случаи, когда компилятор может вставить пространство для преднамеренного смещения карты памяти?

РЕДАКТИРОВАТЬ

Я обсудил некоторые вопросы с моим местным гуру, и похоже, что у меня есть какое-то недопонимание в случае 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 ответ

Решение

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

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

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

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

Другие вопросы по тегам