Подтвердить что ты не робот

Битфилд-манипуляция в C

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

unsigned int mask = 1<<11;

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

An интересный пост в блоге утверждает, что это склонность к ошибкам, сложность в обслуживании и плохая практика. Сам язык 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) {
                [...]
        }
        [...]
}

Но это делает меня cringe.

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

// 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); который будет эквивалентен.

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

Ответ 3

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

Ответ 4

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

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

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

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

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

 flags.force = 0;

чем

 flags &= 0xFFFFFFFE;

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

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

Ответ 5

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

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

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

1 Следующие неуказаны:

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

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

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

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

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

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

Ответ 6

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

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

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

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

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

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

Ответ 7

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

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

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

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

Ответ 8

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

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

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

Ответ 9

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

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

mask = USER|FORCE|ZERO|COMPAT;

vs

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

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

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

Я лично иногда уклоняюсь от объявления булевых типов, потому что в какой-то момент я получу ошибочное впечатление, что поле, которое я только что переключил, не зависел (думаю, многопоточность concurrency) о состоянии r/w других "казалось бы" несвязанных полей, которые имеют одно и то же 32-битное слово.

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

Ответ 10

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

Ответ 11

Он подвержен ошибкам, да. Я видел много ошибок в этом виде кода, главным образом потому, что некоторые люди чувствуют, что им нужно возиться с ним и бизнес-логикой совершенно дезорганизованным способом, создавая кошмары для обслуживания. Они думают, что "настоящие" программисты могут писать value |= mask;, value &= ~mask; или даже худшие вещи в любом месте, и это нормально. Еще лучше, если вокруг есть какой-то инкрементный оператор, несколько тэгов memcpy, указатель и любой непонятный и подверженный ошибкам синтаксис, случается, в это время. Разумеется, нет необходимости быть последовательными, и вы можете переворачивать биты двумя или тремя различными способами, распределенными случайным образом.

Мой совет:

  • Инкапсулируйте это ---- в классе с помощью таких методов, как SetBit(...) и ClearBit(...). (Если у вас нет классов в C, в модуле.) Пока вы на нем, вы можете документировать все их поведение.
  • Unit test этот класс или модуль.

Ответ 12

Ваш первый метод предпочтительнее, ИМХО. Зачем запутывать проблему? Бит-вождение - это действительно основная вещь. C сделал все правильно. Эндианс не имеет значения. Единственное, что делает профсоюзное решение, это назвать вещи. 11 может быть загадочным, но #defined для значимого имени или enum'ed должно быть достаточно.

Программисты, которые не могут справиться с такими фундаментальными особенностями, как "| & ^ ~", вероятно, находятся в неправильном направлении работы.

Ответ 14

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

 #define  ASSERT_GPS_RESET()                    { P1OUT &= ~GPS_RESET ; }

Кстати, ваше определение соединения в исходном вопросе не будет работать на моей комбинации процессор/компилятор. Тип int имеет ширину всего 16 бит, а определения битового поля - 32. Чтобы сделать его несколько более переносимым, вам нужно будет определить новый 32-разрядный тип, который затем можно было бы сопоставить с требуемым базовым типом на каждой целевой архитектуре как часть портирование упражнений. В моем случае

typedef   unsigned long int     uint32_t

и в исходном примере

typedef unsigned int uint32_t

typedef union {
        struct {
                boolean_t user:1;
                boolean_t zero:1;
                boolean_t force:1;
                int :28;                /* unused */
                boolean_t compat:1;     /* bit 31 */
        };
        uint32_t raw;
} flags_t;

Наложенная int также должна быть сделана без знака.

Ответ 15

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

Как только вы привыкли к этому, использование масок является простым, недвусмысленным и переносимым.

Битвые поля просты, но они не переносимы без дополнительной работы.

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

Ответ 16

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

Ответ 17

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

Например, можно предположить, что макрос:

#define SET_BIT(val, bitIndex) val |= (1 << bitIndex)

Определяет атомную операцию, так как | = - один оператор. Но обычный код, созданный компилятором, не будет пытаться сделать | = atomic.

Таким образом, если несколько потоков выполняют разные операции с множеством бит, одна из операций с установленным битом может быть ложной. Поскольку оба потока будут выполняться:

  thread 1             thread 2
  LOAD field           LOAD field
  OR mask1             OR mask2
  STORE field          STORE field

В результате может быть поле '= field OR mask1 OR mask2 (intented), или результатом может быть поле' = field OR mask1 (не intented), или результатом может быть поле '= field OR mask2 (не предназначено).