Як встановити, очистити та переключити один біт?


2567

Як встановити, очистити та змінити трохи?


64
читайте це: graphics.stanford.edu/~seander/bithacks.html і, коли ви це освоїте, прочитайте це: realtimecollisiondetection.net/blog/?p=78
ugasoft

9
Ви також можете бути зацікавлені в перевірці з битового Twiddler , з битами Хакі і агреговані Чарівні алгоритмами .

Відповіді:


3596

Постановка трохи

Використовуйте побітовий оператор АБО ( |), щоб встановити трохи.

number |= 1UL << n;

Це встановить цей nбіт number. nмає бути нульовим, якщо ви хочете встановити 1st біт і так далі upto n-1, якщо ви хочете встановитиn th біт.

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

Трохи очищаючи

Використовуйте побітне оператор AND ( &), щоб трохи очистити.

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

Це очистить цей nшматочок number. Ви повинні перевернути бітову рядок за допомогою оператора NOT ( ~), а потім AND.

Трохи пересуватися

Оператор XOR ( ^) може використовуватися для перемикання.

number ^= 1UL << n;

Це переключить цей nбіт number.

Перевірка трохи

Ви цього не просили, але я можу також додати його.

Щоб трохи перевірити, змістіть число n вправо, потім порозрядно І це:

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

Це додасть значення nth біта numberв зміннуbit .

Зміна n- го біта на x

Встановлення nth-біту або в будь-який, 1або за його 0допомогою можна досягти за допомогою наступного в додатку 2-го додатка C ++:

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

Біт nбуде встановлений, якщо xє 1, і очищений, якщо xє 0. Якщо xмає якесь інше значення, ви отримуєте сміття. x = !!xБуолеанізує його до 0 або 1.

Щоб зробити це незалежним від поведінки заперечення комплементу 2 (де -1встановлено всі біти, на відміну від реалізації доповнення 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 .

Загалом, це гарна ідея взагалі не копіювати / вставляти код, і тому багато людей використовують макроси попереднього процесу (наприклад, відповідь на вікі спільноти далі ) або якусь інкапсуляцію.


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

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

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: вектор <bool> - це (на жаль) спеціалізація, яка зберігає значення як біти. Дивіться gotw.ca/publications/mill09.htm для отримання додаткової інформації ...
Niklas

71
Можливо, ніхто не згадав про це, оскільки це було позначено тегами. У більшості вбудованих систем ви уникаєте STL, як чума. Підтримка підтримки, ймовірно, дуже рідкісна птах, яку можна помітити серед більшості вбудованих компіляторів.
Лундін

17
@Martin Це дуже правда. Окрім конкретних вбивць продуктивності, таких як STL та шаблони, багато вбудованих систем навіть уникають цілих стандартних бібліотек, тому що вони так сильно перевіряють. Більшість вбудованих гілок застосовують стандарти, такі як MISRA, для чого потрібні інструменти аналізу статичного коду (будь-які фахівці з програмного забезпечення повинні використовувати такі інструменти btw, а не просто вбудовані люди). Зазвичай людям краще робити, ніж проводити статичний аналіз через усю стандартну бібліотеку - якщо її вихідний код навіть доступний у конкретному компіляторі.
Лундін

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 ЗАСТАНІТЬ ДОПОМОГУ ДВІ

34
Польові біти погані в багатьох варіантах, я майже міг написати про це книгу. Насправді я мало не мав цього зробити для польової програми, яка потребувала відповідності MISRA-C. MISRA-C примушує зафіксувати всю визначену реалізацією поведінку, тому я закінчив писати есе про все, що може піти не так у бітових полях. Порядок розрядів, ендіатизм, біт підкладки, байти, що містять вкладені файли, різні інші проблеми вирівнювання, неявні та явні перетворення типу в бітове поле та з нього, UB, якщо int не використовується тощо. Натомість використовуйте побітові оператори для меншої кількості помилок та портативного коду. Поля бітів абсолютно зайві.
Лундін

44
Як і більшість мовних функцій, бітові поля можна правильно використовувати або ними можна зловживати. Якщо вам потрібно спакувати кілька малих значень в один цілий int, бітові поля можуть бути дуже корисними. З іншого боку, якщо ви почнете робити припущення про те, як бітові поля відображаються у фактичному, що містить int, ви просто запитуєте проблеми.
Ferruccio

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

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

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чи "" більшого розміру
MM

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()функцію замість &= ~. Чому для цього ви використовуєте енту? Я думав, що вони створюють купу унікальних змінних із прихованим довільним значенням, але ви присвоюєте певне значення кожному. То яка користь проти просто визначення їх як змінних?
ендоліт

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 { ... };
Ейкен Барабан

47

З bitops.h з snip-c.zip:

/*
**  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 тим самим виразом, що позначається оператором tilde (~).

Як тільки маска буде створена, вона застосовується до аргументу так само, як ви пропонуєте, використовуючи побітові та (&), або (|) та xor (^) оператори. Оскільки маска має тип довгих, макроси будуть працювати так само добре, як у char, short, int або long's.

Суть полягає в тому, що це загальне рішення цілого класу проблем. Звичайно, можна і навіть доречно переписувати еквівалент будь-якого з цих макросів із явними значеннями маски кожного разу, коли вам потрібен, але навіщо це робити? Пам'ятайте, що макрозаміщення відбувається в препроцесорі, і тому сформований код відображатиме той факт, що значення компілятором вважаються постійними, тобто використовувати так само настільки ефективно, як використовувати узагальнені макроси, щоб "винаходити колесо" кожного разу, коли потрібно робити бітові маніпуляції.

Непереконаний? Ось кілька тестових кодів - я використовував 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

---- [фініс] ------------------------------------------- ----------------------


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

19
-1 Це просто дивне затуманення. Ніколи не вигадуйте мову C, ховаючи синтаксис мови за макросами, це дуже погана практика. Тоді деякі дивацтва: спочатку підписується 1L, тобто всі бітові операції виконуватимуться на підписаному типі. Все, що передається цим макросам, повернеться як довго підписано. Не добре. По-друге, це буде працювати неефективно на менших процесорах, оскільки воно буде застосовуватись довго, коли операції могли бути на рівні int. По-третє, макроси, подібні до функцій, - корінь усього зла: у вас немає ніякої безпеки безпеки. Крім того, попередній коментар про відсутність призначення є дуже дійсним.
Лундін

2
Це не вдасться, якщо argє long long. 1Lмає бути якомога ширшим типом, тому (uintmax_t)1. (Ви можете піти з цього місця 1ull)
MM

Ви оптимізували розмір коду? На основних процесорах 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
Петро L

Використовуйте НЕ, щоб перевернути маску, щоб зрозуміти:foo = foo & ~MY_MASK
Петро L

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++, |=);в циклі, швидше за все, не зробить те, що хоче абонент.
foraidt

Справді. =) Один з варіантів, який ви можете віддати перевагу - це розділити його на 2 макроси, 1 для адреси елемента масиву, а інший для переміщення біта на місце, ала BITCELL(a,b) |= BITMASK(a,b);(обидва беруть aза аргумент для визначення розміру, але останній ніколи не оцінить, aоскільки вона з’являється лише в sizeof).
R .. GitHub СТОП ДОПОМОГАЙТЕ

1
@R .. Ця відповідь справді стара, але я, мабуть, вважаю за краще функцію перед макросом.
PC Luddite

Minor 3 - ї (size_t)кидок , здається, є тільки забезпечити деяку беззнакову математику з %. Може (unsigned)там.
chux

(size_t)(b)/(8*sizeof *(a))Непотрібно може бути звужена bдо поділу. Тільки проблема з дуже великими масивами бітів. Ще цікавий макрос.
chux

25

Оскільки це позначено "вбудованим", я вважаю, що ви використовуєте мікроконтролер. Усі вищезазначені пропозиції є дійсними та працюють (читати-змінювати-писати, спілки, структури тощо).

Однак під час налагодження налагодження на основі осцилоскопа я був здивований, виявивши, що ці методи мають значні накладні витрати в циклах процесора порівняно з записом значення безпосередньо в регістри PORTnSET / PORTnCLEAR мікро, що робить реальну різницю там, де є тісні петлі / високі -часті шпильки ISR.

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


Мікро був Coldfire MCF52259, використовуючи C в Codewarrior. Перегляд розбиральника / ASM - корисна вправа, оскільки він показує всі кроки, які повинен пройти процесор, щоб зробити навіть найпростішу операцію. <br> Ми також помітили інші вказівки щодо вивільнення процесора в критичних циклах часу - обмеження змінної, виконуючи var% = max_val, щоразу купує купу циклів процесора, виконуючи, якщо (var> max_val) var- = max_val використовує лише пара інструкцій. <br> Хороший посібник щодо ще декількох хитрощів можна знайти тут: codeproject.com/Articles/6154/…
Джон 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(а насправді ви робите свій код ще гіршим)
MM

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);

Вам належить переконатися, що число бітів знаходиться в межах діапазону бітової карти, яку ви передаєте. Зауважте, що для маленьких ендіанських процесорів, які байти, слова, dwords, qwords тощо, правильно відображати один одного в пам’яті (головна причина, що маленькі ендіанські процесори «кращі», ніж процесори з великим ендіаном, ах, я відчуваю, що наближається полум'яна війна на ...).


2
Не використовуйте таблицю для функції, яку можна реалізувати з одним оператором. TQuickByteMask [n] еквівалентно (1 << n). Крім того, зробити аргументи короткими - дуже погана ідея. / І% насправді буде поділом, а не бітовим зміною / побіт, і тому, що підписаний поділ потужністю 2 не може бути реалізований порозрядно. Вам слід зробити тип аргументу без знаку int!
R .. GitHub СТОП ДОПОМОГАЙТЕ

Який сенс у цьому? Це лише робить код повільніше і важче читати? Я не бачу жодної переваги в цьому. 1u << n легше читати для програмістів на C, і, сподіваємось, можна перевести його в одну інструкцію процесора, що позначає один годинник. З іншого боку, ваш підрозділ буде переведено на приблизно 10 кліщів, або навіть так погано, як на 100 кліщів, залежно від того, наскільки погано обробляється поділ. Що стосується функції растрових зображень, то було б більше сенсу мати таблицю пошуку, що переводить кожен бітовий індекс в байт-індекс, щоб оптимізувати швидкість.
Лундін

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

2
@R .. Таблиця може бути корисною, якщо ваша платформа не може ефективно зміщуватися, як у старих мікросхемах mcu, але, звичайно, тоді поділ у вибірці абсолютно неефективний
jeb

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 Завдання компілятора - вивести найефективніший двійковий файл, завдання програміста - написати чіткий код
MM

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, і, можливо, багато інших компіляторів, мають пряму підтримку вбудованих операцій boolean. Трохи є два можливі значення, як булеві, тому ми можемо використовувати booleans замість цього, навіть якщо вони займають більше місця, ніж один біт у пам'ять у цьому поданні. Це працює, навіть 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полегшити налаштування та очищення булінів.

Щоб знайти недруковані символи:

//  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 значення.

Я колись використовував такий підхід для пошуку дублікатів записів позики, де number_number був ключем ISAM, використовуючи 6-значний номер позики як індекс у бітовому масиві. Швидко, і через 8 місяців довів, що система мейнфреймів, з якої ми отримували дані, насправді несправна. Простота бітових масивів робить дуже впевненим у їх коректності - наприклад, підходом до пошуку.


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

@galinette, Погоджено. Файл заголовка #include <bitset> - хороший ресурс у цьому плані. Також спеціальний вектор класу <bool> для того, коли вам потрібно змінити розмір вектора. C ++ STL, 2-е видання, Nicolai M. Josuttis вичерпно висвітлює їх на сторінках 650 та 281 відповідно. C ++ 11 додає декілька нових можливостей std :: bitset, особливий інтерес для мене - хеш-функція в не упорядкованих контейнерах. Дякую за голову вгору! Я збираюся видалити коментар із судом мозку. Вже достатньо сміття в Інтернеті. Я не хочу до цього додавати.

3
Тут використовується щонайменше цілий байт сховища для кожного bool. Можливо, навіть 4 байти для налаштувань C89, які використовуються intдля реалізаціїbool
MM

@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 -
Позиція даних - положення біта, яке нам цікаво встановити, очистити або переключити.

Встановіть трохи:

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 виконує побітові операції (встановити, дістати, очистити, переключити).
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)

Рекомендоване читання - бітові вправи оператора


Дякую за детальне пояснення. Ось посилання на проблему практики для посилання
Чандра Шехар

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)
Йоаким Л. Крістіансен,

використовувати std::bitsetв c ++ 11
pqnet

0

Ця програма заснована на вищезгаданому рішенні @ Джеремі. Якщо хтось хоче швидко пограти.

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.