Почему bit endianness является проблемой в битполях?

Любой переносимый код, который использует битовые поля, кажется, отличает мало- и big-endian платформы. См. объявление структуры iphdr в ядре linux для примера такого кода. Я не понимаю, почему bit endianness является проблемой вообще.

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

Например, рассмотрим следующее битовое поле:


struct ParsedInt {
    unsigned int f1:1;
    unsigned int f2:3;
    unsigned int f3:4;
};
uint8_t i;
struct ParsedInt *d = &i;
Здесь запись d->f2 - это просто компактный и читаемый способ сказать (i>>1) & (1<<4 - 1).

Однако операции бит хорошо определены и работают независимо от архитектуры. Итак, как же битполы не переносятся?

Ответ 1

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

Неопределенное поведение

  • Выравнивание адресного блока хранения, выделенного для хранения битового поля (6.7.2.1).

Поведение, определяемое реализацией

  • Может ли битовое поле перемещаться по границе хранилища (6.7.2.1).
  • Порядок распределения бит-полей внутри единицы (6.7.2.1).

Большой/маленький endian, конечно, также определен в реализации. Это означает, что ваша структура может быть распределена следующим образом (предполагая 16-битные ints):

PADDING : 8
f1 : 1
f2 : 3
f3 : 4

or

PADDING : 8
f3 : 4
f2 : 3
f1 : 1

or

f1 : 1
f2 : 3
f3 : 4
PADDING : 8

or

f3 : 4
f2 : 3
f1 : 1
PADDING : 8

Какой из них применяется? Угадайте или прочитайте углубленную документацию вашего компилятора. Добавьте к этому сложность 32-разрядных целых чисел, в больших или малых числах. Затем добавьте тот факт, что компилятору разрешено добавлять любое количество отступов байтов в любом месте вашего битового поля, потому что оно рассматривается как структура (он не может добавлять отступы в самом начале структуры, но и везде).

И тогда я даже не упомянул, что произойдет, если вы используете обычный "int" в качестве типа битового поля = поведение, определяемое реализацией, или если вы используете какой-либо другой тип, чем (unsigned) int = поведение, определяемое реализацией.

Итак, чтобы ответить на вопрос, нет такой вещи, как переносимый код битового поля, потому что стандарт C очень неопределен, как битовые поля должны быть реализованы. Единственное, на что можно доверять битовые поля, - это быть кусками логических значений, где программист не интересуется расположением бит в памяти.

Единственное портативное решение - использовать битовые операторы вместо битовых полей. Сгенерированный машинный код будет точно таким же, но детерминированным. Битовые операторы на 100% переносимы на любом компиляторе C для любой системы.

Ответ 2

Насколько я понимаю, битовые поля являются чисто конструкциями компилятора

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

Однако битовые поля, вероятно, используются гораздо чаще для моделирования конструкций, которые являются внешними по отношению к домену компилятора - аппаратным регистрам, протоколу "провод" для связи или компоновке формата файла. У этих вещей есть строгие требования о том, как биты должны быть выложены, а использование битовых полей для их моделирования означает, что вам приходится полагаться на определенные реализацией и, что еще хуже, неуказанное поведение того, как компилятор будет макетировать бит-поле.

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

Ответ 3

ISO/IEC 9899: 6.7.2.1/10

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

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

Также см. EXP11-C. Не применяйте операторов, ожидающих одного типа к данным несовместимого типа.

Ответ 4

Доступ к битовому полю реализуется с точки зрения операций над базовым типом. В примере unsigned int. Поэтому, если у вас есть что-то вроде:

struct x {
    unsigned int a : 4;
    unsigned int b : 8;
    unsigned int c : 4;
};

Когда вы обращаетесь к полю b, компилятор обращается к целому unsigned int, а затем сдвигает и маскирует соответствующий диапазон бит. (Ну, это не обязательно, но мы можем притвориться, что это так.)

В случае большого конца, макет будет примерно таким (наиболее значимый бит):

AAAABBBB BBBBCCCC

На маленьком конце, макет будет примерно таким:

BBBBAAAA CCCCBBBB

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

Это делает много предположений. Также обратите внимание, что sizeof(struct x) == 4 на большинстве платформ.

Ответ 5

Поля бит будут храниться в другом порядке в зависимости от конечности машины, в некоторых случаях это может не иметь значения, но в других это может иметь значение. Скажем, например, что ваша структура ParsedInt представляла флаги в пакете, отправленном по сети, небольшая машина endian и машина большого конца читают эти флаги в другом порядке от переданного байта, что, очевидно, является проблемой.