C/С++: порядок и выравнивание битового бита

Я читал, что порядок битовых полей внутри структуры является специфичным для платформы. Что делать, если я использую разные параметры упаковки для конкретного компилятора, будут ли эти данные сохранены в правильном порядке по мере их написания? Например:

struct Message
{
  unsigned int version : 3;
  unsigned int type : 1;
  unsigned int id : 5;
  unsigned int data : 6;
} __attribute__ ((__packed__));

На процессоре Intel с компилятором GCC поля были выложены в памяти по мере их отображения. Message.version - первые 3 бита в буфере, после чего - Message.type. Если я найду эквивалентные варианты упаковки структуры для разных компиляторов, будет ли это кросс-платформенным?

73
задан 29 сент. '09 в 4:07
источник поделиться
6 ответов

Нет, он не будет полностью переносимым. Параметры упаковки для структур - это расширения и сами по себе не полностью переносимы. Кроме того, в параграфе 10 C99 §6.7.2.1 говорится: "Порядок распределения бит-полей внутри единицы (от высокого порядка до низкого или низкого порядка) определяется реализацией".

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

87
ответ дан 29 сент. '09 в 4:31
источник

Поля бит сильно варьируются от компилятора к компилятору, извините.

В GCC, большие эндианские машины выкладывают биты большого конца в первую очередь, а маленькие конечные машины сначала выкладывают бит немного конца.

K & R говорит: "Смежные [бит-] члены полей структур упаковываются в блоки, зависящие от реализации, в зависимом от реализации направлении. Когда поле, следующее за другим полем, не подходит... оно может быть разделено между единицами или блок может быть дополнен. Неименованное поле шириной 0 заставляет это дополнение..."

Поэтому, если вам нужна машинная независимая бинарная компоновка, вы должны сделать это самостоятельно.

Этот последний оператор также относится к небитовым полям из-за заполнения - однако все компиляторы, похоже, имеют какой-то способ форматирования байт-упаковки структуры, как я вижу, вы уже обнаружили для GCC.

38
ответ дан 29 сент. '09 в 4:28
источник

Следует избегать битполей - они не очень переносимы между компиляторами даже для одной и той же платформы. от стандарта C99 6.7.2.1/10 - "Спецификации структуры и союза" (там же формулировка в стандарте C90):

Реализация может выделять любой адресный блок хранения, достаточно большой для хранения битового поля. Если остается достаточно места, бит-поле, которое сразу следует за другим битовым полем в структуре, должно быть упаковано в соседние биты того же блока. Если недостаточно места, то будет ли бит-поле, которое не подходит, помещается в следующий блок или перекрывает смежные единицы, определяется реализацией. Порядок распределения бит-полей внутри единицы (от высокого порядка до младшего или низкого порядка) определяется реализацией. Выравнивание адресного блока хранения не указывается.

Вы не можете гарантировать, будет ли поле бит "охватывать" границу интервала или нет, и вы не можете указать, начинается ли битовое поле в нижнем конце int или в верхнем конце int (это не зависит от того, процессор имеет большой или малый конец).

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

31
ответ дан 29 сент. '09 в 8:11
источник

endianness говорят о байтовых заказах, а не о битах. В настоящее время, на 99% уверены, что битовые заказы исправлены. Тем не менее, при использовании битполей, утверждение должно приниматься в графе. См. Пример ниже.

#include <stdio.h>

typedef struct tagT{

    int a:4;
    int b:4;
    int c:8;
    int d:16;
}T;


int main()
{
    char data[]={0x12,0x34,0x56,0x78};
    T *t = (T*)data;
    printf("a =0x%x\n" ,t->a);
    printf("b =0x%x\n" ,t->b);
    printf("c =0x%x\n" ,t->c);
    printf("d =0x%x\n" ,t->d);

    return 0;
}

//- big endian :  mips24k-linux-gcc (GCC) 4.2.3 - big endian
a =0x1
b =0x2
c =0x34
d =0x5678
// - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856
8
ответ дан 29 сент. '09 в 4:31
источник

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

Если вам действительно нужно иметь идентичную двоичную информацию, вам нужно создать битполы с битами. вы используете unsigned short (16 бит) для Message, а затем делаете такие вещи, как versionMask = 0xE000, чтобы представить три самых верхних бита.

Есть аналогичная проблема с выравниванием внутри структур. Например, процессоры Sparc, PowerPC и 680x0 являются большими, а общий по умолчанию для компиляторов Sparc и PowerPC - выравнивать элементы структуры на 4-байтных границах. Однако один компилятор, который я использовал для 680x0, выровнялся только по 2-байтным границам - и не было возможности изменить выравнивание!

Итак, для некоторых структур размеры на Sparc и PowerPC идентичны, но меньше на 680x0, а некоторые из членов находятся в разных смещениях памяти внутри структуры.

Это была проблема с одним проектом, над которым я работал, потому что серверный процесс, запущенный на Sparc, запрашивал бы клиента и выяснял, что это был большой аргумент, и предположим, что он может просто сжимать двоичные структуры в сети, и клиент может справиться. И это отлично работало на клиентах PowerPC и сильно разбилось на клиентах 680x0. Я не писал код, и для того, чтобы найти проблему, потребовалось довольно много времени. Но это было легко исправить, как только я это сделал.

6
ответ дан 29 сент. '09 в 7:53
источник

Конечно, лучший ответ - использовать класс, который читает/записывает битовые поля в виде потока. Использование структуры битового поля C просто не гарантируется. Не говоря уже о том, что это считается непрофессиональным/ленивым/глупым, чтобы использовать его в реальном мире.

-5
ответ дан 29 сент. '09 в 8:17
источник

Другие вопросы по меткам или Задайте вопрос