Bitfileds в Little-Endian и Big-Endian [duplicate]

Простое рекурсивное решение для произвольного количества списков:

  sub permute {my ($ first_list, @remain) = @_;  кроме (определяется ($ first_list)) {return [];  # только возможность - нулевой набор} my @accum;  для моего $ elem (@ $ first_list) {push @accum, (map {[$ elem, @ $ _]} permute (@remain));  } return @accum;  }  

Не так простое нерекурсивное решение для произвольного количества списков:

  sub make_generator {my @lists = reverse @_;  my @state = map {0} @lists;  return sub {my $ i = 0;  return undef, если не определено $ state [0];  while ($ i & lt; @lists) {$ state [$ i] ++;  last, если $ state [$ i]  scalar @ {$ lists [$ i]};  $ state [$ i] = 0;  $ Я ++;  } if ($ i & gt; = @state) {## Саботаж, поэтому мы не производим больше значений $ state [0] = undef;  return undef;  } my @out;  for (0 .. $ # state) {push @out, $ lists [$ _] [$ state [$ _]];  } return [reverse @out];  };  } my $ gen = make_generator ([qw / foo bar baz /], [qw / cat dog /], [1..4]);  while ($ _ = $ gen- & gt; ()) {print join (",", @ $ _), "\n";  }  
51
задан Leonid99 18 May 2011 в 11:50
поделиться

6 ответов

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

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

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

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

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

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

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

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

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

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

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

63
ответ дан Lundin 15 August 2018 в 15:32
поделиться
  • 1
    Прохладный ответ, спасибо. – Leonid99 18 May 2011 в 14:16
  • 2
    В то же время битовое поле часто используется с прагмой, чтобы сообщить компилятору не использовать отступы (даже если это неэффективно, чтобы сделать это w.r.t. требуемое выравнивание ЦП), а поведение компилятора не является глупым. Результат по обеим причинам выше: осталось только 2 случая: один для большой конечной машины и один для маленького endian. Вот почему вы получаете только 2 версии в низкоуровневом файле заголовка. – xryl669 26 June 2015 в 10:15
  • 3
    @ xryl669 Но зачем вам две версии полностью не переносного файла, если у вас может быть одна версия 100% -ного портативного файла? В любом случае получается один и тот же машинный код. – Lundin 26 June 2015 в 10:41
  • 4
    @ xryl669 Проблема с вашим кодом не является битовыми операторами, а использованием «магических чисел». Он должен был быть записан как s [0] = ВЕРСИЯ | МГП; [d0!]. Теоретически битовые поля - хорошая идея, но стандарт C полностью не поддерживает их. По моему опыту, код, который использует битовые поля, гораздо более подвержен ошибкам, потому что используемый им программист всегда делает много неявных предположений о битовом поле, которые на практике практически не гарантируются. – Lundin 26 June 2015 в 12:45
  • 5
    @ xryl669 Наоборот, если вы делаете это каждый день, например, я работаю со встроенным программированием, бит-манипуляции становятся действительно тривиальными вещами. Вы можете решить свой случай с помощью s [0] = ВЕРСИЯ | IHL_SET (val); , где IHL_SET - простой макрос: #define IHL_SET (x) ((x & lt; IHL_OFFSET) & amp; IHL_MASK) . (Маска необязательна). Взял мне 10 секунд, чтобы написать, никаких усилий. – Lundin 26 June 2015 в 14:06

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

1
ответ дан Charles Keepax 15 August 2018 в 15:32
поделиться
  • 1
    Это именно то, чего я не понимаю. Рассмотрим пример заголовка IP, к которому я привел ссылку. Первые 4 бита, считая из lsb, являются версией, а биты 5-8 - это длина. После того, как NIC расшифровал фрейм и поместил его в память, если я прочитаю весь байт, я всегда получу одинаковые результаты, не так ли? Затем, если я использую битовые сдвиги и побитовые AND, чтобы вырезать байты в куски, я все равно получаю те же результаты, независимо от платформы. Итак, почему биттод не то же самое? – Leonid99 18 May 2011 в 12:09
  • 2
    @Leonid, короткий ответ: потому что стандарт не гарантирует, что он будет таким же. – mizo 18 May 2011 в 13:29

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

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

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

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

  
  AAAABBBB BBBBCCCC  

На маленьком конце, макет будет выглядеть следующим образом:

  BBBBAAAA CCCCBBBB  [  ! d16] 

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

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

5
ответ дан Dietrich Epp 15 August 2018 в 15:32
поделиться
  • 1
    Как я писал в комментарии выше, это именно то, чего я не понимаю. Если я прочитаю эту ячейку памяти в переменной типа unsigned int , ее значение всегда будет AAAABBBBBBBBBCCCCCC, какова бы ни была конечность, правильно? Тогда, если бы я хотел вырезать из него поле c , я бы сделал i & amp; 0xff , и он все равно будет переносимым. Почему битовые поля не совпадают? – Leonid99 18 May 2011 в 12:26
  • 2
    Это неверно, ни стандарт, ни порядок бит битового поля не заданы стандартом C. Компилятор может свободно распределять эти биты, где захочет. – Lundin 18 May 2011 в 12:29
  • 3
    Похоже, у вас есть другое ожидание переносимости из unsigned int и из битных полей. В оба случая , структуры в памяти эффективны, но не могут быть скопированы в другие системы, не выполняя операции байтового обмена байтами. – Dietrich Epp 18 May 2011 в 12:29
  • 4
    @Lundin: Я не говорю о стандарте C, я говорю о реализации стандарта C. – Dietrich Epp 18 May 2011 в 12:30
  • 5
    не могли бы вы рассказать о том, как вы придумали BBBBAAAA CCCCBBB? – Slava 18 October 2017 в 14:07

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

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

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

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

12
ответ дан Michael Burr 15 August 2018 в 15:32
поделиться

ISO / IEC 9899: 6.7.2.1 / 10

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

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

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

8
ответ дан mizo 15 August 2018 в 15:32
поделиться

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

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

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

int16_t s = 4096; // подписанное 16-битное число ...

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

fread ((void *) & amp; s, 2, fp); // читаю его с диска как двоичный ...

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

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

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

uint_8 ubyte; int_8 sbyte; int16_t s; // читаем s в нейтральном нейтральном направлении

// Давайте выберем маленький конец в качестве нашего выбранного порядка байтов:

fread ((void *) & amp; ubyte, 1, fp); // Только чтение 1 байт за время fread ((void *) & amp; sbyte, 1, fp); // Только чтение 1 байт за раз

// Реконструкция s

s = ubyte | (sByte & lt; 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»);

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

0
ответ дан user2465201 15 August 2018 в 15:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: