Как переопределить компилятор C, выравнивающий переменную размера слова в структуре по границе слова

У меня есть структура, указанная ниже

  • Член 1, 16 бит
  • Член 2, 32 бита
  • Член 3, 32 бита

который я буду читать из файла. Я хочу читать прямо из файла в структуру.

Проблема заключается в том, что компилятор C будет выравнивать переменные m1, m2 и m3 по границам слов, которые имеют длину 32 бита, поскольку я работаю над ARM Cortex M3 для следующего объявления структуры:

typedef struct
{
    uint16_t m1;
    uint32_t m2;
    uint32_t m3;
}something;

Чтение непосредственно из файла приведет к неправильным значениям в м2 и м3, а также к прочтению 2 дополнительных байтов.

Я взломал и в настоящее время использую следующее, которое работает просто отлично:

typedef struct
{
    uint16_t m1;
    struct
    {
        uint16_t lo;
        uint16_t hi;
    }m2;
    struct
    {
        uint16_t lo;
        uint16_t hi;
    }m3;
}something;

Тем не менее, это выглядит как очень грязный хак. Я не могу не желать более чистого способа заставить компилятор разделить половинки m2 и m3 другими словами, какими бы неоптимальными они ни были.

Я использую arm-none-eabi-gcc. Я знаю о битовой упаковке, но не могу обойти эту оптимизацию.

Изменить: Оказывается, я не знал достаточно о бит-упаковки:D

4 ответа

Решение

То, что вы ищете, это packed приписывать. Это заставит gcc не делать никаких отступов вокруг членов. Взято из документации GCC Online:

уплотненный

Этот атрибут, связанный с определением типа enum, struct или union, указывает, что для представления типа должен использоваться минимально необходимый объем памяти. Указание этого атрибута для типов структуры и объединения эквивалентно указанию упакованного атрибута для каждого из элементов структуры или объединения. Указание флага -fshort-enums в строке эквивалентно указанию упакованного атрибута во всех определениях enum.

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

Так что вы хотите что-то вроде:

typedef struct
{
    uint16_t m1;
    uint32_t m2;
    uint32_t m3;
} __attribute__ ((packed)) something;

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

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

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

Поэтому для вашего случая использования я бы порекомендовал:

fread(&something.m1, sizeof(something.m1), 1, fd);
fread(&something.m2, sizeof(something.m2), 1, fd);
fread(&something.m3, sizeof(something.m3), 1, fd);

(*) он почти переносим, ​​потому что предполагает, что нет никаких порядковых ошибок, которые могут быть правильными или нет в зависимости от ваших потребностей. Если вы работаете на одной машине или на одной архитектуре, это нормально, но если вы пишете struct на машине с прямым порядком байтов и читаете ее на машине с прямым порядком байтов, могут произойти плохие вещи...

Возможно #pragma pack(2), Это должно заставить компилятор использовать 2-байтовое выравнивание

__attribute__ ((aligned (2)));
Другие вопросы по тегам