Как вы устанавливаете, очищаете и переключаете один бит?


2570

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


65
прочитайте это: graphics.stanford.edu/~seander/bithacks.html и, когда вы
освоите

9
Вы также можете быть заинтересованы в проверке из битового Twiddler , с битами Хаки и агрегированные Волшебные алгоритмами .

Ответы:


3600

Установка немного

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

number |= 1UL << n;

Это будет nнемного number. nдолжен быть равен нулю, если вы хотите установить 1бит st и так далее n-1, если вы хотите установить nбит th.

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

Прояснение немного

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

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

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

Немного тумблер

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

number ^= 1UL << n;

Это будет переключать nй бит number.

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

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

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

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

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

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

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

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

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

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

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бит th и (x << n)установит nбит th вx .

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


128
Я хотел бы отметить, что на платформах, которые имеют встроенную поддержку установки / сброса битов (например, микроконтроллеры AVR), компиляторы часто переводят 'myByte | = (1 << x)' в собственные инструкции установки / сброса битов, когда x равен константа, например: (1 << 5) или const без знака x = 5.
Аарон

52
бит = число & (1 << x); не будет помещать значение бита x в бит, если только бит не имеет тип _Bool (<stdbool.h>). В противном случае, бит = !! (число & (1 << x)); будет ..
Крис Янг

23
почему бы вам не сменить последний наbit = (number >> x) & 1
aaronman

42
1это intлитерал, который подписан. Таким образом, все операции здесь выполняются на подписанных номерах, что не очень хорошо определено стандартами. Стандарты не гарантируют двоичного дополнения или арифметического сдвига, поэтому его лучше использовать 1U.
Сиюань Рен

50
Я предпочитаю number = number & ~(1 << n) | (x << n);менять n-й бит на x.
Jiasli

459

Использование стандартной библиотеки 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 допускает размер набора битов времени выполнения по сравнению со стандартным набором битов размера библиотеки .


34
+1. Не то, чтобы std :: bitset можно было использовать из "C", но поскольку автор отметил свой вопрос как "C ++", AFAIK, ваш ответ здесь самый лучший ... std :: vector <bool> - это другой способ, если знать его плюсы и минусы
paercebal

23
@andrewdotnich: vector <bool> - это (к сожалению) специализация, которая хранит значения в виде битов. См. Gotw.ca/publications/mill09.htm для получения дополнительной информации ...
Niklas

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

17
@ Мартин Это очень верно. Помимо определенных факторов, снижающих производительность, таких как STL и шаблоны, многие встроенные системы даже полностью избегают использования стандартных библиотек, потому что их очень сложно проверить. Большая часть встраиваемой ветви охватывает стандарты, такие как MISRA, для которых требуются инструменты статического анализа кода (кстати, такие инструменты должны использовать любые профессионалы в области программного обеспечения, а не просто встраиваемые люди). Обычно у людей есть дела поважнее, чем проводить статический анализ через всю стандартную библиотеку - если ее исходный код даже доступен для них на конкретном компиляторе.
Лундин

37
@Lundin: Ваши высказывания слишком широки (поэтому спорить бесполезно). Я уверен, что смогу найти ситуации, если они правдивы. Это не меняет мою начальную точку. Оба эти класса прекрасно подходят для использования во встроенных системах (и я точно знаю, что они используются). Ваше первоначальное мнение о том, что STL / Boost не используется во встроенных системах, также неверно. Я уверен, что есть системы, которые их не используют, и даже системы, которые их используют, они используются разумно, но говорить, что они не используются, просто не правильно (потому что есть системы, в которых они используются).
Мартин Йорк,

248

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

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

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


68
Я всегда считал использование битовых полей плохой идеей. У вас нет контроля над порядком, в котором распределяются биты (сверху или снизу), что делает невозможным сериализацию значения стабильным / переносным способом, кроме как по битам. Также невозможно смешать битовую арифметику DIY с битовыми полями, например, создать маску, которая проверяет несколько битов одновременно. Конечно, вы можете использовать && и надеяться, что компилятор оптимизирует его корректно ...
R .. GitHub ОСТАНОВИТЬ ПОМОЩЬ ICE

34
Битовые поля плохи во многих отношениях, я мог бы почти написать книгу об этом. Фактически, мне почти пришлось сделать это для небольшой программы, которая требовала соответствия требованиям MISRA-C. MISRA-C принудительно документирует все поведение, определяемое реализацией, поэтому я написал довольно эссе обо всем, что может пойти не так в битовых полях. Порядок битов, порядковый номер, биты заполнения, байты заполнения, различные другие проблемы выравнивания, неявные и явные преобразования типов в и из битового поля, UB, если int не используется, и так далее. Вместо этого используйте побитовые операторы для уменьшения количества ошибок и переносимого кода. Битовые поля полностью избыточны.
Лундин

44
Как и большинство языковых функций, битовые поля можно использовать правильно или ими можно злоупотреблять. Если вам нужно упаковать несколько небольших значений в одно целое, битовые поля могут быть очень полезны. С другой стороны, если вы начинаете делать предположения о том, как битовые поля отображаются на фактическое, содержащее int, вы просто напрашиваетесь на неприятности.
Ферруччо

4
@endolith: Это не будет хорошей идеей. Вы можете заставить его работать, но он не обязательно будет переносимым на другой процессор, или на другой компилятор, или даже на следующую версию того же компилятора.
Ферруччо

3
@Yasky и Ferruccio, получающие разные ответы на sizeof () для этого подхода, должны проиллюстрировать проблемы совместимости не только между компиляторами, но и с оборудованием. Мы иногда обманываем себя тем, что решили эти проблемы с помощью языков или определенных сред выполнения, но на самом деле все сводится к тому, «будет ли это работать на моей машине?». Вы, ребята, встраиваемые, уважайте меня (и симпатии).
Келли С. Френч

181

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

/* 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))

17
Э-э, я понимаю, что это 5-летний пост, но ни в одном из этих макросов нет дублирования аргументов, Дэн
Роберт Келли,

11
BITMASK_CHECK(x,y) ((x) & (y))в ((x) & (y)) == (y)противном случае он возвращает неверный результат в многобитовой маске (например, 5против 3) / * Привет всем могильщикам :) * /
brigadir

7
1должно быть (uintmax_t)1или похоже на случай, если кто-то попытается использовать эти макросы для более longкрупного типа
ММ

2
BITMASK_CHECK_ALL(x,y)может быть реализовано как!~((~(y))|(x))
Handy999

3
@ Handy999 Немного легче понять, почему это работает после применения закона Де Моргана и реорганизации, чтобы получить!(~(x) & (y))
Тавиан Барнс

114

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

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

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

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

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

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


1
Поочередно вы можете сделать clearbits()функцию вместо &= ~. Почему вы используете enum для этого? Я думал, что они предназначены для создания набора уникальных переменных со скрытым произвольным значением, но вы назначаете определенное значение каждой из них. Так в чем же выгода от определения их как переменных?
Эндолит

4
@endolith: использование enums для наборов связанных констант имеет большое значение в программировании на c. Я подозреваю, что с современными компиляторами единственное преимущество по сравнению с const shortтем, что они явно сгруппированы вместе. И когда вы хотите , чтобы они что - то другое , чем битмаски вы получаете автоматическую нумерацию. В C ++, конечно, они также формируют различные типы, что дает вам немного больше статической проверки ошибок.
dmckee --- котенок экс-модератора

Вы попадете в неопределенные константы перечисления, если не определите константу для каждого из возможных значений битов. Какое enum ThingFlagsзначение ThingError|ThingFlag1, например?
Луис Колорадо

6
Если вы используете этот метод, имейте в виду, что константы перечисления всегда имеют тип со знаком int. Это может вызывать любые тонкие ошибки из-за неявного целочисленного продвижения или побитовых операций со знаковыми типами. thingstate = ThingFlag1 >> 1будет, например, вызывать поведение, определяемое реализацией. thingstate = (ThingFlag1 >> x) << yможет вызвать неопределенное поведение. И так далее. Чтобы быть в безопасности, всегда приводите к типу без знака.
Лундин

1
@Lundin: Начиная с C ++ 11, вы можете установить базовый тип перечисления, например: enum My16Bits: unsigned short { ... };
Aiken Drum

47

Из snop-c.zip 's 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, то это выражение будет оцениваться как:

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 или long.

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

Убежденный? Вот некоторый тестовый код - я использовал 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] ------------------------------------------- ----------------------


3
2 вещи об этом: (1) при просмотре ваших макросов, некоторые могут ошибочно полагать, что макросы фактически устанавливают / сбрасывают / переворачивают биты в аргументе, однако присваивания нет; (2) ваш test.c не завершен; Я подозреваю, что если бы вы запустили больше дел, вы бы обнаружили проблему (упражнение читателя)
Дан

19
-1 Это просто странное запутывание. Никогда не изобретайте язык C, скрывая синтаксис языка за макросами, это очень плохая практика. Затем некоторые странности: сначала подписывается 1L, что означает, что все битовые операции будут выполняться со знаком типа. Все, что передано в эти макросы, вернется как подписано долго. Фигово. Во-вторых, это будет очень неэффективно работать на меньших процессорах, так как оно будет работать долго, когда операции могли выполняться на уровне int. В-третьих, функционально-подобные макросы являются корнем всего зла: у вас нет никакой безопасности типов. Кроме того, предыдущий комментарий об отсутствии назначения очень действителен.
Лундин

2
Это не удастся, если argесть long long. 1Lдолжен быть максимально широкого типа, поэтому (uintmax_t)1. (Вы можете сойти с рук 1ull)
ММ

Вы оптимизировали для размера кода? На основных процессорах Intel вы получите частичный регистр при чтении AX или EAX после возврата этой функции, поскольку она записывает 8-битные компоненты EAX. (Это нормально для процессоров AMD или других, которые не переименовывают частичные регистры отдельно от полного регистра. Haswell / Skylake не переименовывают AL отдельно, но они переименовывают AH. ).
Питер Кордес

37

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

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

foo = foo | 001b

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

if ( foo & 001b ) ....

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

foo = foo & 110b

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


7
В C. нет двоичных обозначений. Двоичные целочисленные константы являются нестандартным расширением.
Лундин

Используйте XOR, чтобы немного переключиться:foo = foo ^ MY_MASK
Питер Л

Используйте НЕ, чтобы инвертировать маску, чтобы сделать очистку:foo = foo & ~MY_MASK
Питер Л

32

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

Пример:

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)

26

Вот мой любимый битовый арифметический макрос, который работает для любого типа целочисленного массива без знака от 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, &)) ...

и т.п.


5
Это хорошо читать, но нужно знать о возможных побочных эффектах. Использование BITOP(array, bit++, |=);в цикле, скорее всего, не будет делать то, что хочет вызывающий.
Форайдт

Верно. =) Один вариант, который вы можете предпочесть, состоит в том, чтобы разделить его на 2 макроса, 1 для адресации элемента массива и другой для смещения бита на место, аля BITCELL(a,b) |= BITMASK(a,b);(оба принимают aв качестве аргумента для определения размера, но последний никогда не будет вычислять, aтак как это появляется только в sizeof).
R .. GitHub ОСТАНОВИТЬ ЛЬДА

1
@R .. Этот ответ очень старый, но в этом случае я бы предпочел функцию макросу.
ПК Luddite

Minor: 3 - й (size_t)бросок , кажется, есть только обеспечить некоторую беззнаковую математику с %. Мог (unsigned)там.
chux - Восстановить Монику

(size_t)(b)/(8*sizeof *(a))Ненужно может быть сужена bдо разделения. Только проблема с очень большими битовыми массивами. Все еще интересный макрос.
chux - Восстановить Монику

25

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

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

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


Микро был Coldfire MCF52259, используя C в Codewarrior. Рассмотрение дизассемблера / ассемблера является полезным упражнением, поскольку оно показывает все шаги, которые ЦП должен пройти, чтобы выполнить даже самые основные операции. <br> Мы также заметили другие инструкции по переключению ЦП в циклы, критичные ко времени - ограничение переменной путем выполнения var% = max_val стоит кучу циклов ЦП каждый раз, в то время как мы выполняем, если (var> max_val) var- = max_val использует только пара инструкций. <br> Хорошее руководство по нескольким трюкам здесь: codeproject.com/Articles/6154/…
John U

Еще важнее то, что регистры ввода-вывода с отображением вспомогательной памяти обеспечивают механизм атомарных обновлений. Чтение / изменение / запись могут пойти очень плохо, если последовательность прервана.
Бен Фойгт

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

24

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

struct HwRegister {
    unsigned int errorFlag:1;  // one-bit flag field
    unsigned int Mode:3;       // three-bit mode field
    unsigned int StatusCode:4;  // four-bit status code
};

struct HwRegister CR3342_AReg;

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

Затем вы можете читать, писать, тестировать отдельные значения, как и раньше.


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

1
@Lundin - Правда, но битовая встроенная система (особенно в аппаратных регистрах, к которой относится мой ответ) никогда не будет полезной в любом случае.
Родди

1
Возможно, не между совершенно разными процессорами. Но вы, скорее всего, хотите, чтобы он был переносимым между компиляторами и между различными проектами. И есть много встроенных «разборов», которые вообще не связаны с аппаратным обеспечением, таких как кодирование / декодирование протокола данных.
Лундин

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

20

Проверьте бит в произвольном месте в переменной произвольного типа:

#define bit_test(x, y)  ( ( ((const char*)&(x))[(y)>>3] & 0x80 >> ((y)&0x07)) >> (7-((y)&0x07) ) )

Пример использования:

int main(void)
{
    unsigned char arr[8] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };

    for (int ix = 0; ix < 64; ++ix)
        printf("bit %d is %d\n", ix, bit_test(arr, ix));

    return 0;
}

Примечания: Это разработано, чтобы быть быстрым (учитывая его гибкость) и не ветвиться. Это приводит к эффективному машинному коду SPARC при компиляции Sun Studio 8; Я также проверил это, используя MSVC ++ 2008 на amd64. Можно сделать похожие макросы для установки и очистки битов. Ключевое отличие этого решения по сравнению со многими другими здесь заключается в том, что оно работает для любого местоположения практически во всех типах переменных.


20

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

#define BITS 8
#define BIT_SET(  p, n) (p[(n)/BITS] |=  (0x80>>((n)%BITS)))
#define BIT_CLEAR(p, n) (p[(n)/BITS] &= ~(0x80>>((n)%BITS)))
#define BIT_ISSET(p, n) (p[(n)/BITS] &   (0x80>>((n)%BITS)))

2
CHAR_BITуже определено limits.h, вам не нужно вставлять свои собственные BITS(и фактически вы делаете свой код хуже, делая это)
ММ

14

Эта программа должна изменить любой бит данных с 0 на 1 или с 1 на 0:

{
    unsigned int data = 0x000000F0;
    int bitpos = 4;
    int bitvalue = 1;
    unsigned int bit = data;
    bit = (bit>>bitpos)&0x00000001;
    int invbitvalue = 0x00000001&(~bitvalue);
    printf("%x\n",bit);

    if (bitvalue == 0)
    {
        if (bit == 0)
            printf("%x\n", data);
        else
        {
             data = (data^(invbitvalue<<bitpos));
             printf("%x\n", data);
        }
    }
    else
    {
        if (bit == 1)
            printf("elseif %x\n", data);
        else
        {
            data = (data|(bitvalue<<bitpos));
            printf("else %x\n", data);
        }
    }
}

14

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

const unsigned char TQuickByteMask[8] =
{
   0x01, 0x02, 0x04, 0x08,
   0x10, 0x20, 0x40, 0x80,
};


/** Set bit in any sized bit mask.
 *
 * @return    none
 *
 * @param     bit    - Bit number.
 * @param     bitmap - Pointer to bitmap.
 */
void TSetBit( short bit, unsigned char *bitmap)
{
    short n, x;

    x = bit / 8;        // Index to byte.
    n = bit % 8;        // Specific bit in byte.

    bitmap[x] |= TQuickByteMask[n];        // Set bit.
}


/** Reset bit in any sized mask.
 *
 * @return  None
 *
 * @param   bit    - Bit number.
 * @param   bitmap - Pointer to bitmap.
 */
void TResetBit( short bit, unsigned char *bitmap)
{
    short n, x;

    x = bit / 8;        // Index to byte.
    n = bit % 8;        // Specific bit in byte.

    bitmap[x] &= (~TQuickByteMask[n]);    // Reset bit.
}


/** Toggle bit in any sized bit mask.
 *
 * @return   none
 *
 * @param   bit    - Bit number.
 * @param   bitmap - Pointer to bitmap.
 */
void TToggleBit( short bit, unsigned char *bitmap)
{
    short n, x;

    x = bit / 8;        // Index to byte.
    n = bit % 8;        // Specific bit in byte.

    bitmap[x] ^= TQuickByteMask[n];        // Toggle bit.
}


/** Checks specified bit.
 *
 * @return  1 if bit set else 0.
 *
 * @param   bit    - Bit number.
 * @param   bitmap - Pointer to bitmap.
 */
short TIsBitSet( short bit, const unsigned char *bitmap)
{
    short n, x;

    x = bit / 8;    // Index to byte.
    n = bit % 8;    // Specific bit in byte.

    // Test bit (logigal AND).
    if (bitmap[x] & TQuickByteMask[n])
        return 1;

    return 0;
}


/** Checks specified bit.
 *
 * @return  1 if bit reset else 0.
 *
 * @param   bit    - Bit number.
 * @param   bitmap - Pointer to bitmap.
 */
short TIsBitReset( short bit, const unsigned char *bitmap)
{
    return TIsBitSet(bit, bitmap) ^ 1;
}


/** Count number of bits set in a bitmap.
 *
 * @return   Number of bits set.
 *
 * @param    bitmap - Pointer to bitmap.
 * @param    size   - Bitmap size (in bits).
 *
 * @note    Not very efficient in terms of execution speed. If you are doing
 *        some computationally intense stuff you may need a more complex
 *        implementation which would be faster (especially for big bitmaps).
 *        See (http://graphics.stanford.edu/~seander/bithacks.html).
 */
int TCountBits( const unsigned char *bitmap, int size)
{
    int i, count = 0;

    for (i=0; i<size; i++)
        if (TIsBitSet(i, bitmap))
            count++;

    return count;
}

Обратите внимание, что для установки бита 'n' в 16-битном целом числе вы делаете следующее:

TSetBit( n, &my_int);

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


2
Не используйте таблицу для функции, которая может быть реализована одним оператором. TQuickByteMask [n] эквивалентен (1 << n). Кроме того, короткая аргументация - очень плохая идея. / И% на самом деле будет делением, а не битовым сдвигом / побитовым, и потому что знаковое деление со степенью 2 не может быть реализовано побитовым. Вы должны сделать тип аргумента unsigned int!
R .. GitHub ОСТАНОВИТЬ ЛЬДА

Какой в ​​этом смысл? Это только делает код медленнее и сложнее для чтения? Я не вижу в этом ни одного преимущества. 1u << n легче читать для программистов на C, и, мы надеемся, может быть преобразовано в инструкцию CPU с одним тактом. С другой стороны, ваше деление будет переведено примерно в 10 тиков, или даже так плохо, как до 100 тиков, в зависимости от того, насколько плохо конкретная архитектура справляется с делением. Что касается функции растрового изображения, то было бы более разумно иметь справочную таблицу, переводящую каждый битовый индекс в байтовый индекс, чтобы оптимизировать скорость.
Лундин

2
Что касается big / little endian, big endian будет отображать целые числа и необработанные данные (например, строки) одинаково: слева направо от msb до lsb по всему растровому изображению. Хотя little endian будет отображать целые числа слева направо как 7-0, 15-8, 23-18, 31-24, но необработанные данные все еще остаются слева направо от msb до lsb. Так что, как мало endian лучше для вашего конкретного алгоритма, полностью вне меня, кажется, наоборот.
Лундин

2
@R .. Таблица может быть полезна, если ваша платформа не может эффективно сдвигаться, как старый микрочип MCU, но, конечно, тогда деление в выборке абсолютно неэффективно
джеб

12

Использовать этот:

int ToggleNthBit ( unsigned char n, int num )
{
    if(num & (1 << n))
        num &= ~(1 << n);
    else
        num |= (1 << n);

    return num;
}

5
Ну, это использует неэффективное ветвление.
asdf

3
@asdf Задача компилятора - вывести наиболее эффективный двоичный файл, а задача программиста - написать чистый код
ММ,

3
Это хорошая демонстрация тестирования, установки и очистки определенного бита. Однако это очень плохой подход к переключению немного.
Бен Фойгт

10

Расширяя bitsetответ:

#include <iostream>
#include <bitset>
#include <string>

using namespace std;
int main() {
  bitset<8> byte(std::string("10010011");

  // Set Bit
  byte.set(3); // 10010111

  // Clear Bit
  byte.reset(2); // 10010101

  // Toggle Bit
  byte.flip(7); // 00010101

  cout << byte << endl;

  return 0;
}

10

Если вы хотите выполнить всю эту операцию с программированием на C в ядре Linux тогда я предлагаю использовать стандартные API ядра Linux.

См. Https://www.kernel.org/doc/htmldocs/kernel-api/ch02s03.html.

set_bit  Atomically set a bit in memory
clear_bit  Clears a bit in memory
change_bit  Toggle a bit in memory
test_and_set_bit  Set a bit and return its old value
test_and_clear_bit  Clear a bit and return its old value
test_and_change_bit  Change a bit and return its old value
test_bit  Determine whether a bit is set

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


9

Visual C 2010 и, возможно, многие другие компиляторы имеют прямую поддержку встроенных логических операций. Бит имеет два возможных значения, как и логическое, поэтому мы можем использовать вместо него логические - даже если они занимают больше места, чем один бит в память в этом представлении. Это работает, даже sizeof()оператор работает правильно.

bool    IsGph[256], IsNotGph[256];

//  Initialize boolean array to detect printable characters
for(i=0; i<sizeof(IsGph); i++)  {
    IsGph[i] = isgraph((unsigned char)i);
}

Итак, на ваш вопрос IsGph[i] =1, илиIsGph[i] =0 сделайте настройку и очистку bools легко.

Чтобы найти непечатаемые символы:

//  Initialize boolean array to detect UN-printable characters, 
//  then call function to toggle required bits true, while initializing a 2nd
//  boolean array as the complement of the 1st.
for(i=0; i<sizeof(IsGph); i++)  {
    if(IsGph[i])    {
         IsNotGph[i] = 0;
    }   else   {
         IsNotGph[i] = 1;
    }
}

Обратите внимание, что в этом коде нет ничего «особенного». Это немного похоже на целое число, что технически так и есть. 1-битное целое число, которое может содержать 2 значения и только 2 значения.

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


std :: bitset действительно реализован в виде битов большинством компиляторов
galinette

@galinette, Согласен. Заголовочный файл #include <bitset> является хорошим ресурсом в этом отношении. Кроме того, специальный класс vector <bool> для случаев, когда вам нужно изменить размер вектора. C ++ STL, 2nd Edition, Nicolai Josuttis подробно описывает их на страницах 650 и 281 соответственно. C ++ 11 добавляет несколько новых возможностей в std :: bitset, особый интерес для меня представляет хеш-функция в неупорядоченных контейнерах. Спасибо за головы! Я собираюсь удалить свой комментарий о судорогах. Уже достаточно мусора в сети. Я не хочу добавлять к этому.

3
Это использует по крайней мере целый байт памяти для каждого bool. Может быть, даже 4 байта для установок C89, которые используют intдля реализацииbool
ММ

@ MattMcNabb, вы правы. В C ++ размер типа int, необходимого для реализации логического значения, не указан стандартом. Я понял, что этот ответ был ошибочным некоторое время назад, но решил оставить его здесь, так как люди, по-видимому, находят его полезным. Для тех, кто хочет использовать биты, наиболее полезен комментарий Galinette, как и моя библиотека битов здесь ... stackoverflow.com/a/16534995/1899861

2
@RocketRoy: Возможно, стоит изменить предложение, которое утверждает, что это пример «битовых операций».
Бен Фойгт

6

Используйте один из операторов, как определено здесь .

Чтобы установить бит, используется int x = x | 0x?;где ?позиция бита в двоичной форме.


2
0xэто префикс для литерала в шестнадцатеричном, а не двоичном.
Бен Фойгт

5

Вот некоторые макросы, которые я использую:

SET_FLAG(Status, Flag)            ((Status) |= (Flag))
CLEAR_FLAG(Status, Flag)          ((Status) &= ~(Flag))
INVALID_FLAGS(ulFlags, ulAllowed) ((ulFlags) & ~(ulAllowed))
TEST_FLAGS(t,ulMask, ulBit)       (((t)&(ulMask)) == (ulBit))
IS_FLAG_SET(t,ulMask)             TEST_FLAGS(t,ulMask,ulMask)
IS_FLAG_CLEAR(t,ulMask)           TEST_FLAGS(t,ulMask,0)

5

Переменная используется

int value, pos;

value - Data
pos - позиция бита, который нам нужно установить, очистить или переключить.

Установить немного:

value = value | 1 << pos;

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

value = value & ~(1 << pos); 

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

value = value ^ 1 << pos;

5
int set_nth_bit(int num, int n){    
    return (num | 1 << n);
}

int clear_nth_bit(int num, int n){    
    return (num & ~( 1 << n));
}

int toggle_nth_bit(int num, int n){    
    return num ^ (1 << n);
}

int check_nth_bit(int num, int n){    
    return num & (1 << n);
}

Вернуть тип check_nth_bitможно bool.
Xeverous

@Xeverous да, это зависит от намерения звонящих
Саззад Хисейн Хан

5

Давайте предположим, что сначала
num = 55 Integer выполняет побитовые операции (set, get, clear, toggle).
n = 40 битовая позиция для выполнения побитовых операций.

Как получить немного?

  1. Чтобы получить nthнемного правого сдвига num, nраз. Затем выполните побитовое И &с 1.
bit = (num >> n) & 1;

Как это работает?

       0011 0111 (55 in decimal)
    >>         4 (right shift 4 times)
-----------------
       0000 0011
     & 0000 0001 (1 in decimal)
-----------------
    => 0000 0001 (final result)

Как установить немного?

  1. Чтобы установить конкретный бит числа. Сдвиг влево 1 nраз. Затем выполните побитовую |операцию ИЛИ с num.
num |= (1 << n);    // Equivalent to; num = (1 << n) | num;

Как это работает?

       0000 0001 (1 in decimal)
    <<         4 (left shift 4 times)
-----------------
       0001 0000
     | 0011 0111 (55 in decimal)
-----------------
    => 0001 0000 (final result)

Как очистить немного?

  1. Сдвиг влево 1, nто есть раз 1 << n.
  2. Выполните побитовое дополнение с вышеуказанным результатом. Так что n-й бит становится неустановленным, а остаток бита становится установленным, т.е.~ (1 << n) .
  3. Наконец, выполните побитовую &операцию И с вышеуказанным результатом и num. Вышеупомянутые три шага вместе могут быть записаны как num & (~ (1 << n));

Шаги, чтобы очистить немного

num &= (~(1 << n));    // Equivalent to; num = num & (~(1 << n));

Как это работает?

       0000 0001 (1 in decimal)
    <<         4 (left shift 4 times)
-----------------
     ~ 0001 0000
-----------------
       1110 1111
     & 0011 0111 (55 in decimal)
-----------------
    => 0010 0111 (final result)

Как немного переключиться?

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

Что означает переключение немного, нам нужно выполнить операцию XOR с битом, который вы хотите переключить, и 1.

num ^= (1 << n);    // Equivalent to; num = num ^ (1 << n);

Как это работает?

  • Если бит для переключения равен 0, то 0 ^ 1 => 1 .
  • Если бит переключения 1 , то, 1 ^ 1 => 0.
       0000 0001 (1 in decimal)
    <<         4 (left shift 4 times)
-----------------
       0001 0000
     ^ 0011 0111 (55 in decimal)
-----------------
    => 0010 0111 (final result)

Рекомендуемое чтение - Битовые упражнения оператора


Спасибо за подробное объяснение. Вот ссылка на практическую задачу для BIT Magic link
Чандра Шекхар

4

Как вы устанавливаете, очищаете и переключаете один бит?

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

Какие проблемы случаются, когда numberэто более широкий тип, чем 1?
xможет быть слишком велико для сдвига, 1 << xведущего к неопределенному поведению (UB). Даже если xон не слишком велик, он ~может не перевернуть достаточно значащих бит.

// assume 32 bit int/unsigned
unsigned long long number = foo();

unsigned x = 40; 
number |= (1 << x);  // UB
number ^= (1 << x);  // UB
number &= ~(1 << x); // UB

x = 10;
number &= ~(1 << x); // Wrong mask, not wide enough

Чтобы застраховать 1 достаточно широко:

Код можно использовать 1ullили педантично (uintmax_t)1и позволить компилятору оптимизировать.

number |= (1ull << x);
number |= ((uintmax_t)1 << x);

Или приведение - что делает для кодирования / проверки / обслуживания вопросы поддержания правильного и актуального преобразования.

number |= (type_of_number)1 << x;

Или осторожно продвигайте 1, заставляя математическую операцию, которая является столь же широкой как тип number.

number |= (number*0 + 1) << x;

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


Интересный взгляд на старый вопрос! Ни, number |= (type_of_number)1 << x;ни number |= (number*0 + 1) << x;уместно устанавливать бит знака со знаком типа ... На самом деле, это не так number |= (1ull << x);. Есть ли портативный способ сделать это по позиции?
Chqrlie

1
@chqrlie IMO, лучший способ избежать установки знакового бита и риска UB или IDB со сдвигами - это использовать неподписанные типы. Очень переносимый код со сдвигом и подписью слишком сложен, чтобы быть приемлемым.
chux - Восстановить Монику

3

Шаблонная версия C ++ 11 (помещенная в заголовок):

namespace bit {
    template <typename T1, typename T2> inline void set  (T1 &variable, T2 bit) {variable |=  ((T1)1 << bit);}
    template <typename T1, typename T2> inline void clear(T1 &variable, T2 bit) {variable &= ~((T1)1 << bit);}
    template <typename T1, typename T2> inline void flip (T1 &variable, T2 bit) {variable ^=  ((T1)1 << bit);}
    template <typename T1, typename T2> inline bool test (T1 &variable, T2 bit) {return variable & ((T1)1 << bit);}
}

namespace bitmask {
    template <typename T1, typename T2> inline void set  (T1 &variable, T2 bits) {variable |= bits;}
    template <typename T1, typename T2> inline void clear(T1 &variable, T2 bits) {variable &= ~bits;}
    template <typename T1, typename T2> inline void flip (T1 &variable, T2 bits) {variable ^= bits;}
    template <typename T1, typename T2> inline bool test_all(T1 &variable, T2 bits) {return ((variable & bits) == bits);}
    template <typename T1, typename T2> inline bool test_any(T1 &variable, T2 bits) {return variable & bits;}
}

Этот код не работает. (Кроме того, почему у вас есть ;после определения вашей функции?)
melpomene

@ melpomene Код не сломан, я его проверил. Ты имеешь в виду, что он не скомпилируется или результат неверный? О лишних ';' Я не помню, они действительно могут быть удалены.
Йоаким Л. Кристиансен

(variable & bits == bits)?
Мельпомена

Спасибо, что заметили, это должно было быть((variable & bits) == bits)
Joakim L. Christiansen

использование std::bitsetв c ++ 11
pqnet

0

Эта программа основана на вышеуказанном решении @ Jeremy. Если кто-то хочет быстро поиграть.

public class BitwiseOperations {

    public static void main(String args[]) {

        setABit(0, 4); // set the 4th bit, 0000 -> 1000 [8]
        clearABit(16, 5); // clear the 5th bit, 10000 -> 00000 [0]
        toggleABit(8, 4); // toggle the 4th bit, 1000 -> 0000 [0]
        checkABit(8,4); // check the 4th bit 1000 -> true 
    }

    public static void setABit(int input, int n) {
        input = input | ( 1 << n-1);
        System.out.println(input);
    }


    public static void clearABit(int input, int n) {
        input = input & ~(1 << n-1);
        System.out.println(input);
    }

    public static void toggleABit(int input, int n) {
        input = input ^ (1 << n-1);
        System.out.println(input);
    }

    public static void checkABit(int input, int n) {
        boolean isSet = ((input >> n-1) & 1) == 1; 
        System.out.println(isSet);
    }
}


Output :
8
0
0
true

-2

Попробуйте одну из этих функций на языке C, чтобы изменить n бит:

char bitfield;

// Start at 0th position

void chang_n_bit(int n, int value)
{
    bitfield = (bitfield | (1 << n)) & (~( (1 << n) ^ (value << n) ));
}

Или

void chang_n_bit(int n, int value)
{
    bitfield = (bitfield | (1 << n)) & ((value << n) | ((~0) ^ (1 << n)));
}

Или

void chang_n_bit(int n, int value)
{
    if(value)
        bitfield |= 1 << n;
    else
        bitfield &= ~0 ^ (1 << n);
}

char get_n_bit(int n)
{
    return (bitfield & (1 << n)) ? 1 : 0;
}

value << nможет вызвать неопределенное поведение
ММ
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.