Манипулирование битовыми полями в C - PullRequest
46 голосов
/ 25 июня 2009

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

unsigned int mask = 1<<11;

if (value & mask) {....} // Test for the bit
value |= mask;    // set the bit
value &= ~mask;   // clear the bit

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

typedef unsigned int boolean_t;
#define FALSE 0
#define TRUE !FALSE
typedef union {
        struct {
                boolean_t user:1;
                boolean_t zero:1;
                boolean_t force:1;
                int :28;                /* unused */
                boolean_t compat:1;     /* bit 31 */
        };
        int raw;
} flags_t;

int
create_object(flags_t flags)
{
        boolean_t is_compat = flags.compat;

        if (is_compat)
                flags.force = FALSE;

        if (flags.force) {
                [...]
        }
        [...]
}

Но это заставляет меня съеживаться .

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

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

Ответы [ 17 ]

38 голосов
/ 25 июня 2009

Битовые поля не так переносимы, как вы думаете, так как «C не дает гарантии упорядочения полей внутри машинных слов» ( Книга C )

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

29 голосов
/ 25 июня 2009

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

// off the top of my head
#define SET_BIT(val, bitIndex) val |= (1 << bitIndex)
#define CLEAR_BIT(val, bitIndex) val &= ~(1 << bitIndex)
#define TOGGLE_BIT(val, bitIndex) val ^= (1 << bitIndex)
#define BIT_IS_SET(val, bitIndex) (val & (1 << bitIndex)) 

Что делает ваш код читабельным, если вы не возражаете, что val должен быть lvalue, за исключением BIT_IS_SET. Если это не делает вас счастливыми, то вы берете назначение, заключаете его в скобки и используете его как val = SET_BIT (val, someIndex); что будет эквивалентно.

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

23 голосов
/ 28 июня 2009

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

19 голосов
/ 25 июня 2009

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

Во-первых, классический программист на Си, который замаскировал всю свою жизнь и мог делать это во сне.

Во-вторых, это новичок, который понятия не имеет, что все это такое, и прочее. Они программировали php на своей последней работе и теперь работают на вас. (Я говорю это как новичок, который делает php)

Если вы напишите, чтобы удовлетворить первую аудиторию (то есть битовую маску на весь день), вы сделаете их очень счастливыми, и они смогут поддерживать код с завязанными глазами. Тем не менее, новичку, вероятно, придется преодолеть большую кривую обучения, прежде чем они смогут поддерживать ваш код. Им нужно будет узнать о бинарных операторах, о том, как вы используете эти операции для установки / очистки битов и т. Д. Новичок почти наверняка обнаружит ошибки, поскольку он / она выполняет все приемы, необходимые для того, чтобы заставить это работать.

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

 flags.force = 0;

1012 * чем *

 flags &= 0xFFFFFFFE;

и первая аудитория просто рассердится, но трудно представить, что они не смогут проглотить и поддерживать новый синтаксис. Просто намного сложнее облажаться. Новых ошибок не будет, потому что новичку будет легче поддерживать код. Вы просто получите лекции о том, что «в свое время вы нуждались в твердой руке и намагниченной игле, чтобы устанавливать биты ... у нас даже не было битовых масок!» (спасибо XKCD ).

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

14 голосов
/ 27 июня 2009

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

Из стандарта ISO / IEC 9899: 1999 (C99) :

Приложение J - Проблемы мобильности:

1 Не указана следующая информация:

- Значение байтов заполнения при хранении значений в структурах или объединениях (6.2.6.1).

- Значение члена объединения, кроме последнего, сохраненного в (6.2.6.1).

6.2.6.1 - Концепции языка - Представление типов - Общее:

6 Когда значение сохраняется в объекте структуры или типа объединения, в том числе в элементе объект, байты представления объекта, которые соответствуют любым байтам заполнения, берут неопределенные значения. [42]) Значение структуры или объекта объединения никогда не является ловушкой представление, даже если значение члена структуры или объекта объединения может быть представление ловушки.

7 Когда значение сохраняется в элементе объекта типа объединения, байты объекта представление, которое не соответствует этому члену, но соответствует другим членам принимать неопределенные значения.

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

10 голосов
/ 25 июня 2009

Что такое подход с битовыми полями, который заставляет вас съеживаться?

Обе техники имеют свое место, и единственное решение, которое у меня есть, - какое из них использовать:

Для простого однократного использования битов я использую битовые операторы напрямую.

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

  • Битовые поля более лаконичны в использовании (за счет / немного / больше многословие писать.
  • Битовые поля более надежный (какой размер "int", в любом случае)
  • Битовые поля обычно просто так же быстро, как побитовые операторы.
  • Битовые поля очень мощные, когда вы иметь смесь одного и нескольких бит поля и извлечение многобитное поле включает в себя множество ручные смены.
  • Битовые поля эффективно самодокументирующийся. От определение структуры и, следовательно, называя элементы, я знаю, что это хотел сделать.
  • Битовые поля также беспрепятственно обрабатывают структуры, размер которых превышает одно целое.
  • Для побитовых операторов типичная (плохая) практика - убить #defines для битовых масок.

  • Единственное предостережение с битовыми полями - убедиться, что компилятор действительно упаковал объект в нужный вам размер. Я не могу вспомнить, определяется ли это стандартом, поэтому assert (sizeof (myStruct) == N) является полезной проверкой.

6 голосов
/ 25 июня 2009

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

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

mask = USER|FORCE|ZERO|COMPAT;

vs

flags.user = true;
flags.force = true;
flags.zero = true;
flags.compat = true;

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

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

Лично я иногда уклоняюсь от объявления булевых типов, потому что в какой-то момент у меня возникнет ошибочное впечатление, что поле, которое я только что переключил, не зависело (думаю, многопоточный параллелизм) от ч / б статуса других «кажущиеся» несвязанными поля, которые имеют одно и то же 32-битное слово.

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

6 голосов
/ 25 июня 2009

В блоге вы ссылаетесь на raw объединенное поле в качестве альтернативного метода доступа к битовым полям.

Цели, которые использовал автор блога raw, в порядке, однако, если вы планируете использовать его для чего-либо еще (например, для сериализации битовых полей, установки / проверки отдельных битов), катастрофа просто ждет вас за углом. Порядок расположения битов в памяти зависит от архитектуры, и правила заполнения памяти варьируются от компилятора к компилятору (см. wikipedia ), поэтому точное положение каждого битового поля может отличаться, другими словами, вы никогда не можете быть уверены, какой бит 1009 * каждому битовому полю соответствует.

Однако, если вы не планируете смешивать его, лучше выведите raw, и вы будете в безопасности.

6 голосов
/ 25 июня 2009

Так или иначе, битовые поля использовались в программном обеспечении GNU в течение десятилетий, и это не принесло им никакого вреда. Они мне нравятся как параметры функций.

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

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

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

5 голосов
/ 26 июня 2009

В C ++ просто используйте std::bitset<N>.

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