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. Если я найду эквивалентные варианты упаковки структуры для различных компиляторов, будет ли это кросс-платформенным?

Ответ 1

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

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

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

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

Ответ 4

Порядковые числа говорят о байтовых порядках, а не битовых. В настоящее время на 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
 1   2   3   4   5   6   7   8
\_/ \_/ \_____/ \_____________/
 a   b     c           d

// - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856
 7   8   5   6   3   4   1   2
\_____________/ \_____/ \_/ \_/
       d           c     b   a

Ответ 5

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

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

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

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

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

Ответ 6

Спасибо @BenVoigt за ваш очень полезный комментарий, начинающийся

Нет, они были созданы для экономии памяти.

Исходный код Linux использует битовое поле для соответствия внешней структуре: /usr/include/linux/ip.h содержит этот код для первого байта дейтаграммы IP

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,
                version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,
                ihl:4;
#else
#error  "Please fix <asm/byteorder.h>"
#endif

Однако в свете вашего комментария я отказываюсь от попыток заставить это работать для многобайтового битового поля frag_off.

Ответ 7

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