Манипулирование битовыми полями в 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 ]

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

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

Мой совет:

  1. Инкапсулируйте это ---- в классе с помощью таких методов, как SetBit(...) и ClearBit(...). (Если у вас нет классов на C в модуле.) Пока вы в нем, вы можете задокументировать все их поведение.
  2. Модульное тестирование этого класса или модуля.
3 голосов
/ 25 июня 2009

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

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

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

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

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

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

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

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

Когда я гуглю "операторы c"

Первые три страницы:

http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V40F_HTML/AQTLTBTE/DOCU_059.HTM http://www.cs.mun.ca/~michael/c/op.html

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

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

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

 #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 тоже должен быть без знака.

1 голос
/ 30 августа 2016

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

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

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

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

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

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

Результатом может быть поле '= поле ИЛИ маска1 ИЛИ маска2 (намеренное), или результат может быть полем' = поле ИЛИ маска1 (не намеренное), или результатом может быть поле '= поле ИЛИ маска2 (не предназначенное)

1 голос
/ 25 июня 2009

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

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