Почему битовая последовательность является проблемой в битовых полях? - PullRequest
52 голосов
/ 18 мая 2011

Кажется, что любой переносимый код, использующий битовые поля, различает платформы с прямым и младшим порядком байтов. См. объявление struct iphdr в ядре Linux для примера такого кода. Я не понимаю, почему битовая последовательность является проблемой вообще.

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

Например, рассмотрим следующее битовое поле: 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).

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

Ответы [ 7 ]

68 голосов
/ 18 мая 2011

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

Неуказанное поведение

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

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

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

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

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" в качестве типа битового поля = поведение, определяемое реализацией, или если вы используете любой другой тип, кроме (без знака)int = поведение, определяемое реализацией.

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

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

14 голосов
/ 18 мая 2011

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

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

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

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

9 голосов
/ 18 мая 2011

ИСО / МЭК 9899: 6.7.2.1 / 10

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

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

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

6 голосов
/ 18 мая 2011

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

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

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

На старом порядке байтов раскладка будет примерно такой (сначала самый важный бит):

AAAABBBB BBBBCCCC

На младшем порядке, расположение будет таким:

BBBBAAAA CCCCBBBB

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

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

1 голос
/ 18 мая 2011

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

0 голосов
/ 07 марта 2019

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

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

Эта упаковка влияет на "порядковый номер бита" в битовых полях, потому что разные компиляторы могут хранить битовые поля в другом направлении, а порядковый номер бита влияет на то, как они будут извлечены.

Так что имейте в виду ОБА уровни проблемы - байтовый порядок влияет на способность компьютера читать одно скалярное значение, например, число с плавающей запятой, в то время как компилятор (и аргументы сборки) влияют на способность программы читать в статистической структуре ,

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

0 голосов
/ 02 апреля 2018

Чтобы повторить наиболее существенные моменты: если вы используете это на одной платформе компилятора / HW в качестве только программной конструкции, то проблема с порядком байтов не будет проблемой. Если вы используете код или данные на нескольких платформах ИЛИ должны соответствовать аппаратным битам, то это IS проблема. А много профессионального программного обеспечения является кроссплатформенным, поэтому оно должно заботиться.

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

Конкретный пример:

int16_t s = 4096; // a signed 16-bit number...

Допустим, моя программа поставляется с некоторыми данными на диске, которые я хочу прочитать. Скажем, я хочу загрузить ее как 4096 в этом случае ...

fread((void*)&s, 2, fp); // reading it from disk as binary...

Здесь я читаю его как 16-битное значение, а не как явные байты. Это означает, что если моя система соответствует порядку байтов, хранящемуся на диске, я получу 4096, а если нет, то получу 16 !!!!!

Таким образом, наиболее распространенное использование порядка байтов - это массовая загрузка двоичных чисел, а затем выполнение bswap, если вы не соответствуете. Раньше мы хранили данные на диске с прямым порядком байтов, потому что Intel была странной фигурой и предоставляла высокоскоростные инструкции для обмена байтами. В настоящее время Intel настолько распространена, что часто делает Little Endian по умолчанию и заменяет его в системе с прямым порядком байтов.

Более медленный, но порядковый нейтральный подход состоит в том, чтобы делать ВСЕ ввода / вывода байтами, т. Е .:

uint_8 ubyte;
int_8 sbyte;
int16_t s; // read s in endian neutral way

// Let's choose little endian as our chosen byte order:

fread((void*)&ubyte, 1, fp); // Only read 1 byte at a time
fread((void*)&sbyte, 1, fp); // Only read 1 byte at a time

// Reconstruct s

s = ubyte | (sByte << 8);

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

Я использовал пример хранимых данных, используемых программой. Другое упомянутое основное приложение - это запись аппаратных регистров, где эти регистры имеют абсолютный порядок. Это ОЧЕНЬ ОБЩЕЕ место с графикой. Получите неправильный порядок байтов и ваши каналы красного и синего цвета поменяются местами! Опять же, проблема заключается в переносимости: вы можете просто адаптироваться к конкретной аппаратной платформе и видеокарте, но если вы хотите, чтобы один и тот же код работал на разных машинах, вы должны протестировать.

Вот классический тест:

typedef union { uint_16 s; uint_8 b[2]; } EndianTest_t;

EndianTest_t test = 4096;

if (test.b[0] == 12) printf("Big Endian Detected!\n");

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...