Эффекты __attribute __ ((упакованные)) на вложенном массиве структур?

Проблема

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

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;

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

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

Документация gcc, похоже, предполагает, что, просто включив упакованный атрибут в определение pack_2_s, поля, даже если они являются массивами, будут как можно более плотно упакованы и не будут добавлять пространство в структуру упакованных_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 выше на моем компилятор (ы).

Вопрос

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

ИЗМЕНИТЬ

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

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

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

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

Ответ 1

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

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

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

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

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

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

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

Есть ли способ гарантировать, что структура 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

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