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

6 ответов

Решение

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

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

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

С GCC, машины с прямым порядком байтов сначала выстраивают биты с конца, а машины с обратным порядком байтов - сначала с конца.

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

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

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

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

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

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

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

Порядковые числа говорят о байтовых порядках, а не битовых. В настоящее время на 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

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

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

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

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

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

Спасибо @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.

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

Другие вопросы по тегам