Как переопределить компилятор 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-байтовое выравнивание