Как вы устанавливаете, очищаете и переключаете один бит? - PullRequest
2337 голосов
/ 07 сентября 2008

Как установить, очистить и немного переключить в C / C ++?

Ответы [ 26 ]

3315 голосов
/ 07 сентября 2008

Установка бита

Используйте битовый оператор ИЛИ (|) для установки бита.

number |= 1UL << n;

Это установит n -й бит number. n должен быть равен нулю, если вы хотите установить 1-й бит и т. Д. До n-1, если вы хотите установить n-й бит.

Используйте 1ULL, если number шире, чем unsigned long; продвижение 1UL << n не происходит до тех пор, пока после оценки 1UL << n не произойдет неопределенное поведение смещения более чем на ширину long. То же самое относится ко всем остальным примерам.

Очистить немного

Используйте побитовый оператор AND (&), чтобы очистить немного.

number &= ~(1UL << n);

Это очистит n -й бит number. Вы должны инвертировать битовую строку с помощью побитового оператора NOT (~), затем AND it.

Переключение немного

Оператор XOR (^) может использоваться для переключения немного.

number ^= 1UL << n;

Это переключит n th бит number.

Проверка немного

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

Чтобы проверить немного, сдвиньте число n вправо, затем поразрядно И это:

bit = (number >> n) & 1U;

Это поместит значение n-го бита number в переменную bit.

Изменение n -го бита на x

Установка n-го бита на 1 или 0 может быть достигнута с помощью следующего в реализации C ++, дополненной 2:

number ^= (-x ^ number) & (1UL << n);

Бит n будет установлен, если x равен 1, и сброшен, если x равен 0. Если x имеет другое значение, вы получите мусор. x = !!x будет логизировать его до 0 или 1.

Чтобы сделать это независимым от поведения отрицания дополнения 2 (где -1 имеет все установленные биты, в отличие от реализации C ++ дополнения или знака / величины), используйте отрицание без знака.

number ^= (-(unsigned long)x ^ number) & (1UL << n);

или

unsigned long newbit = !!x;    // Also booleanize to force 0 or 1
number ^= (-newbit ^ number) & (1UL << n);

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

или

number = (number & ~(1UL << n)) | (x << n);

(number & ~(1UL << n)) очистит n-й бит, а (x << n) установит n -й бит на x.

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

428 голосов
/ 18 сентября 2008

Использование стандартной библиотеки C ++: std::bitset<N>.

Или версия Boost : boost::dynamic_bitset.

Нет необходимости бросать свои собственные:

#include <bitset>
#include <iostream>

int main()
{
    std::bitset<5> x;

    x[1] = 1;
    x[2] = 0;
    // Note x[0-4]  valid

    std::cout << x << std::endl;
}

[Alpha:] > ./a.out
00010

Boost-версия допускает набор битов размером во время выполнения по сравнению со стандартной библиотекой набор битов размера компиляции.

225 голосов
/ 11 сентября 2008

Другой вариант - использовать битовые поля:

struct bits {
    unsigned int a:1;
    unsigned int b:1;
    unsigned int c:1;
};

struct bits mybits;

определяет 3-битное поле (на самом деле это три 1-битных поля). Битовые операции теперь стали немного (ха-ха) проще:

Чтобы установить или очистить немного:

mybits.b = 1;
mybits.c = 0;

Чтобы немного переключиться:

mybits.a = !mybits.a;
mybits.b = ~mybits.b;
mybits.c ^= 1;  /* all work */

Проверка немного:

if (mybits.c)  //if mybits.c is non zero the next line below will execute

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

155 голосов
/ 05 ноября 2008

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

/* a=target variable, b=bit number to act upon 0-n */
#define BIT_SET(a,b) ((a) |= (1ULL<<(b)))
#define BIT_CLEAR(a,b) ((a) &= ~(1ULL<<(b)))
#define BIT_FLIP(a,b) ((a) ^= (1ULL<<(b)))
#define BIT_CHECK(a,b) (!!((a) & (1ULL<<(b))))        // '!!' to make sure this returns 0 or 1

/* x=target variable, y=mask */
#define BITMASK_SET(x,y) ((x) |= (y))
#define BITMASK_CLEAR(x,y) ((x) &= (~(y)))
#define BITMASK_FLIP(x,y) ((x) ^= (y))
#define BITMASK_CHECK_ALL(x,y) (((x) & (y)) == (y))   // warning: evaluates y twice
#define BITMASK_CHECK_ANY(x,y) ((x) & (y))
111 голосов
/ 09 сентября 2008

Иногда стоит использовать от enum до имя биты:

enum ThingFlags = {
  ThingMask  = 0x0000,
  ThingFlag0 = 1 << 0,
  ThingFlag1 = 1 << 1,
  ThingError = 1 << 8,
}

Затем используйте имена позже. То есть написать

thingstate |= ThingFlag1;
thingstate &= ~ThingFlag0;
if (thing & ThingError) {...}

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

Кроме этого, я одобряю решение Джереми.

41 голосов
/ 17 сентября 2008

Из snip-c.zip bitops.h:

/*
**  Bit set, clear, and test operations
**
**  public domain snippet by Bob Stout
*/

typedef enum {ERROR = -1, FALSE, TRUE} LOGICAL;

#define BOOL(x) (!(!(x)))

#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))

ОК, давайте разберемся ...

Общее выражение, с которым у вас, похоже, возникают проблемы во всех этих случаях, это "(1L << (posn))". Все это создает маску с одним битом на и который будет работать с любым целочисленным типом. Аргумент "posn" указывает положение, где вы хотите немного. Если posn == 0, то это выражение будет оценить: </p>

    0000 0000 0000 0000 0000 0000 0000 0001 binary.

Если posn == 8, это будет равно

    0000 0000 0000 0000 0000 0001 0000 0000 binary.

Другими словами, он просто создает поле из 0 с 1 в указанном позиция. Единственная сложная часть в макросе BitClr (), где нам нужно установить один бит 0 в поле 1. Это достигается с помощью 1 дополнение того же выражения, которое обозначено оператором тильда (~).

Как только маска создана, она применяется к аргументу, как вы предлагаете, с помощью побитовых и (&), или (|), и xor (^) операторов. С маской имеет тип long, макросы будут работать так же хорошо на char, short, int, или длинные.

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

убежденный? Вот некоторый тестовый код - я использовал Watcom C с полной оптимизацией и без использования _cdecl, чтобы результирующая разборка была бы такой же чистой, как возможно:

---- [TEST.C] -------------------------------------- --------------------------

#define BOOL(x) (!(!(x)))

#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))

int bitmanip(int word)
{
      word = BitSet(word, 2);
      word = BitSet(word, 7);
      word = BitClr(word, 3);
      word = BitFlp(word, 9);
      return word;
}

---- [TEST.OUT (разобрано)] ----------------------------------- ------------

Module: C:\BINK\tst.c
Group: 'DGROUP' CONST,CONST2,_DATA,_BSS

Segment: _TEXT  BYTE   00000008 bytes  
 0000  0c 84             bitmanip_       or      al,84H    ; set bits 2 and 7
 0002  80 f4 02                          xor     ah,02H    ; flip bit 9 of EAX (bit 1 of AH)
 0005  24 f7                             and     al,0f7H
 0007  c3                                ret     

No disassembly errors

---- [finis] ---------------------------------------- -------------------------

34 голосов
/ 07 сентября 2008

Используйте побитовые операторы: & |

Чтобы установить последний бит в 000b:

foo = foo | 001b

Чтобы проверить последний бит в foo:

if ( foo & 001b ) ....

Чтобы очистить последний бит в foo:

foo = foo & 110b

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

31 голосов
/ 05 июня 2012

Для новичка я хотел бы объяснить немного больше с примером:

Пример:

value is 0x55;
bitnum : 3rd.

Используется оператор &, проверьте бит:

0101 0101
&
0000 1000
___________
0000 0000 (mean 0: False). It will work fine if the third bit is 1 (then the answer will be True)

Переключить или отразить:

0101 0101
^
0000 1000
___________
0101 1101 (Flip the third bit without affecting other bits)

| оператор: установить бит

0101 0101
|
0000 1000
___________
0101 1101 (set the third bit without affecting other bits)
25 голосов
/ 13 июля 2010

Вот мой любимый битовый арифметический макрос, который работает для любого типа целочисленного массива без знака от unsigned char до size_t (это самый большой тип, с которым следует работать):

#define BITOP(a,b,op) \
 ((a)[(size_t)(b)/(8*sizeof *(a))] op ((size_t)1<<((size_t)(b)%(8*sizeof *(a)))))

Чтобы установить бит:

BITOP(array, bit, |=);

Чтобы немного очистить:

BITOP(array, bit, &=~);

Для переключения немного:

BITOP(array, bit, ^=);

Для проверки немного:

if (BITOP(array, bit, &)) ...

и т.д.

23 голосов
/ 14 июня 2012

Поскольку это помечено как «встроенный», я предполагаю, что вы используете микроконтроллер. Все вышеперечисленные предложения действительны и работают (чтение-изменение-запись, объединения, структуры и т. Д.).

Тем не менее, во время отладки на основе осциллографа я был поражен, обнаружив, что эти методы имеют значительные издержки в циклах ЦП по сравнению с записью значения непосредственно в регистры PORTnSET / PORTnCLEAR микроэлемента, что дает реальную разницу в местах с ограниченным доступом. шлейфы / высокочастотные переключающие штыри ISR.

Для тех, кто незнаком: в моем примере микро имеет общий регистр состояния выводов PORTn, который отражает выходные выводы, поэтому выполнение PORTn | = BIT_TO_SET приводит к чтению-модификации-записи в этот регистр. Однако регистры PORTnSET / PORTnCLEAR принимают «1» для обозначения «пожалуйста, сделайте этот бит 1» (SET) или «пожалуйста, сделайте этот бит нулевым» (CLEAR) и «0» для «оставьте пин-код в покое». Таким образом, вы получите два адреса портов в зависимости от того, устанавливаете ли вы или очищаете бит (не всегда удобно), но реакция на намного быстрее и меньший собранный код.

...