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

avatar
JeffV
7 сентября 2008 в 00:42
1350841
27
2815

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

Источник
ugasoft
18 сентября 2008 в 16:01
77

прочтите это: graphics.stanford.edu/~seander/bithacks.html и, когда вы освоите это, прочтите это: realtimecollisiondetection.net/blog/?p=78

anon
5 января 2009 в 11:13
17

Вам также может быть интересно ознакомиться с Bit Twiddler, Bit Twiddling Hacks и Агрегатными магическими алгоритмами.

Ответы (27)

avatar
Paige Ruten
7 сентября 2008 в 00:50
3939

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

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

number |= 1UL << n;

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

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

Очистка бита

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

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

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

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

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

number ^= 1UL << n;

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

Проверка бита

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

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

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

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

Изменение n th бита на 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.

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

Aaron
17 сентября 2008 в 17:13
142

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

Chris Young
16 ноября 2008 в 07:49
54

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

John Zwinck
3 января 2009 в 22:55
3

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

Happy Green Kid Naps
9 мая 2013 в 14:27
6

Это только я, но я бы предпочел заключить в скобки выражения битового сдвига (аналогично тому, что сделал @Aaron выше).

anatolyg
9 мая 2013 в 15:58
13

Кстати, бит-игра здесь будет тихо терпеть неудачу, если number шире, чем int

aaronman
26 июня 2013 в 18:47
24

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

phuclv
1 августа 2013 в 02:04
1

@Aaron: x86 имеет собственный битовый тест, а также инструкции по установке / очистке / дополнению

user2568508
13 сентября 2013 в 21:40
2

Думаю, проблема в обозначениях. Скажем, у меня есть 0011 (десятичное число 3), и я хочу проверить, установлен ли второй бит , например, тот, который выделен жирным шрифтом: 0 0 1 1. Как вы относитесь к x? Будет ли это 2-й или 1-й бит в соответствии с вашей записью, потому что, если это 2-й бит, я думаю, ваше предложение не сработает, например, поскольку вы сдвинете 1 два раза и получите 100 - что не даст 2-й бит как Я определил выше. Не так ли?

Siyuan Ren
10 декабря 2013 в 08:53
46

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

JonS
7 июля 2014 в 22:46
9

Чтобы прояснить комментарий анатолика, константа «1» без модификаторов определяется как int со знаком. Чтобы это работало правильно для всех переменных, используйте вместо этого «1ULL».

M.M
6 февраля 2015 в 23:48
5

@JonS для всех переменных размером до unsigned long long в любом случае ... могут быть расширения, определяемые реализацией, такие как __int128. Чтобы быть сверхбезопасным (uintmax_t)1 << x

jiasli
24 марта 2015 в 00:38
61

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

chqrlie
24 марта 2015 в 08:48
2

@Jiashuo Li: этот оператор завершится ошибкой, если number больше, чем int, а n больше или равно количеству бит в int. В этом случае он даже вызывает неопределенное поведение. number = number & ~((uintmax_t)1 << n) | ((uintmax_t)x << n); - это общее выражение, которое должно работать для всех размеров number, но может генерировать уродливый и неэффективный код для меньших размеров.

Anonymous
8 апреля 2015 в 11:28
2

Немного по поводу проверки: почему бы просто не использовать "number & 0x01" для проверки первого бита, "number & 0x08" для четвертого и т. Д. Imho более красивое.

Kevin
18 января 2016 в 18:04
5

Использование таких операций, как -x, небезопасно, поскольку стандарт C позволяет целым числам со знаком быть (например) величиной знака, дополнением единиц или любой другой системой, которая может выражать необходимый диапазон, что означает, что -x равно не должен совпадать с (~x) + 1. Это не так уж важно для современных архитектур, но вы никогда не знаете, что достаточно умный оптимизирующий компилятор сделает с вашим кодом.

71GA
30 января 2017 в 07:12
2

Почему мы используем ~ вместо !?

mckenzm
9 марта 2017 в 02:19
1

@ Анонимный. Мы все равно используем маски, так что если нам когда-нибудь понадобится сделать более одного расширения, очевидно? Примечание: верхний / нижний регистр в EBCDIC составляет 1 бит.

Patrick Roberts
14 июня 2017 в 21:46
1

Разве последний пример не предполагает представление с дополнением до двух? Разве это не плохо / непереносимо, как бы вы это ни называли?

Adrian McCarthy
5 сентября 2017 в 20:34
3

@ 71GA: Побитовое отрицание (~) инвертирует все биты, поэтому ~0xFFF0FFFF равно 0x000F0000. Логическое значение not (!) дает 0, если значение не равно нулю, или 1, если значение равно нулю, поэтому !0xFFF0FFFF равно 0x00000000.

Peter Cordes
10 ноября 2017 в 19:55
1

@PatrickRoberts: Да, это требует дополнения до двух. В дополнении -1 равно 0b1111..1110. (Все единицы: -0). C ++ также допускает целочисленные представления знака / величины, где -x просто переворачивает бит знака. Я обновил этот ответ, чтобы указать на это. Совершенно нормально, если вы ориентируетесь только на две дополнительные реализации C ++. Это не UB, это просто определяется реализацией, поэтому для правильной работы требуется определить целые числа со знаком как дополнение до 2. Я также изменил константы на 1UL, отметив, что может потребоваться 1ULL.

Peter Cordes
10 ноября 2017 в 19:57
2

@Kevin: Это гарантированно безопасно для реализации C ++, в которой используется дополнение 2. Это «определяется реализацией», а не UB. Следует беспокоиться о переносимости до дополнения или знака / величины, а не об оптимизации компиляторов.

Kevin
10 ноября 2017 в 20:55
1

@Peter -x - UB, если x - INT_MIN.

Peter Cordes
10 ноября 2017 в 21:05
2

@Kevin: да, должно быть -(unsigned long)x, что также обойдёт представление целого числа со знаком. (беззнаковое основание 2 соответствует семантике дополнения до 2). Но оно работает правильно, только если x равно 0 или 1. Помните, что мы устанавливаем бит n на x.

Kevin
10 ноября 2017 в 21:18
1

@PeterCordes: Это вполне разумно. Но когда разработчик говорит мне, что «этот инвариант будет всегда сохраняться!» Я очень нервничаю.

Peter Cordes
10 ноября 2017 в 21:21
1

@Kevin: Я решил опустить любое упоминание UB с подписанным переполнением в моем последнем редактировании, потому что вы все равно хотите использовать unsigned для переносимых 0U - 1U -> все. Как это выглядит сейчас? Я постарался, чтобы ответ был простым. Я уже упоминал, что вам может потребоваться логическое преобразование с помощью !!x. Если бы это был мой собственный ответ, я мог бы вставить больше текста о том, что всегда использовать unsigned, но я просто сохраняю этот старый канонический ответ. (Джереми, надеюсь, вам понравятся изменения, возможно, вы захотите отредактировать, чтобы изложить мои изменения своими словами или что-то еще, что вы хотите сказать почти 9 лет спустя.)

Peter Cordes
10 ноября 2017 в 21:26
0

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

avivgood2
22 апреля 2020 в 08:37
0

Является ли ваш метод изменения n-го бита на x действительным и для C90? И справедливо ли это для чисел без знака?

sherrellbc
15 января 2021 в 13:46
0

Я сталкивался с этим довольно редко и всегда думал, что мне нужно придумать одно выражение для выполнения операции set bit to value. Обычно я делаю save state + unconditionally set/clear + restore state аппаратно. Во всяком случае, я случайно наткнулся на это и обязательно украду выражение выше.

Shogan Aversa-Druesne
18 января 2021 в 04:02
0

number = (number | (1UL << n)) ^ (!x << n) Упрощено для удаления логического «нет» и побитового «нет».

Shogan Aversa-Druesne
18 января 2021 в 04:09
0

Вышеупомянутое выражение ^^^^^ позволяет x быть неограниченным, x может быть 0 или любым положительным числом для истины и не привязан к 0 и 1

Smartskaft2
2 мая 2021 в 19:08
0

Иногда мне интересно, не было бы более эффективным использование простого оператора if / else при изменении n th бита на x. Не будет number = (number & ~(1UL << n)) | (x << n); генерировать два битовых сдвига, одно обращение байта, одно И, одно ИЛИ и, наконец, одно присвоение. В то время как if (x) { number |= (1 << n); } else { number &= ~(1 << n); } генерирует одно «сравнение», сдвиг на один бит, либо , либо ИЛИ , либо И с переворотом байта, а также присваивание. 6 против 5 операций в худшем случае (сброс бита). 6 против 4 при установке бита. Намного более читабельным. Но, может быть, некоторые операции обходятся дороже?

Angra Mainyu
3 мая 2021 в 11:58
1

@ Smartskaft2 проблема в том, что ветвление с помощью if / else может быть намного дороже. Найдите «Ошибка предсказания ветвления» или посмотрите этот вопрос: coderhelper.com/q/11227809

JulianH
26 мая 2021 в 15:24
0

@AngraMainyu Ошибки предсказания ветки пренебрежимо малы: godbolt.org/z/vascq9Khc if решение даже в 1,2 раза быстрее.

Angra Mainyu
27 мая 2021 в 07:46
0

@JulianH Спасибо за тестирование. Но в этом тесте у нас есть фиксированный шаблон чередования истинно / ложно в условии перехода, который предсказатель перехода, вероятно, точно предскажет после первых нескольких итераций. Таким образом, тест на самом деле не измеряет ошибку предсказания ветвления.

JulianH
28 мая 2021 в 10:31
0

@AngraMainyu ЦП не предсказывает идеально, как вы сказали, поскольку существует разница в ускорении между чередующимся и не чередующимся x, однако при рандомизации значения x не- if -решение примерно в два раза быстрее, чем if -решение: godbolt.org/z/5aEbKchzf Итак, да, вы правы, предсказание ветвления убивает производительность.

avatar
Steve Karg
5 августа 2021 в 09:02
207

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

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

#define BITMASK_SET(x, mask) ((x) |= (mask))
#define BITMASK_CLEAR(x, mask) ((x) &= (~(mask)))
#define BITMASK_FLIP(x, mask) ((x) ^= (mask))
#define BITMASK_CHECK_ALL(x, mask) (!(~(x) & (mask)))
#define BITMASK_CHECK_ANY(x, mask) ((x) & (mask))
Robert Kelly
2 октября 2013 в 14:53
19

Я понимаю, что это сообщение пятилетней давности, но ни в одном из этих макросов нет дублирования аргументов, Дэн

brigadir
11 декабря 2014 в 12:00
12

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

M.M
6 февраля 2015 в 23:50
8

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

Peter Cordes
10 ноября 2017 в 21:27
0

Или 1ULL работает так же хорошо, как (uintmax_t) в большинстве реализаций.

Peter Cordes
10 ноября 2017 в 21:31
0

@brigadir: зависит от того, хотите ли вы проверить наличие каких-либо установленных битов или всех установленных битов. Я обновил ответ, включив оба с описательными именами.

Handy999
20 ноября 2018 в 09:24
3

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

Tavian Barnes
13 августа 2019 в 20:14
4

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

William Martens
16 февраля 2021 в 16:12
0

Это было настолько полезно, что я удивляюсь, почему я не пробовал это раньше, особенно #define BIT_CHECK(a,b) (!!((a) & (1ULL<<(b)))) // '!!' to make sure this returns 0 or 1

avatar
lckid2004
17 марта 2021 в 05:14
0

Вот процедура на C для выполнения основных побитовых операций:

#define INT_BIT (unsigned int) (sizeof(unsigned int) * 8U) //number of bits in unsigned int

int main(void)
{
    
    unsigned int k = 5; //k is the bit position; here it is the 5th bit from the LSb (0th bit)
    
    unsigned int regA = 0x00007C7C; //we perform bitwise operations on regA
    
    regA |= (1U << k);    //Set kth bit
    
    regA &= ~(1U << k);   //Clear kth bit
    
    regA ^= (1U << k);    //Toggle kth bit
    
    regA = (regA << k) | regA >> (INT_BIT - k); //Rotate left by k bits
    
    regA = (regA >> k) | regA << (INT_BIT - k); //Rotate right by k bits

    return 0;   
}

avatar
Dominic van der Zypen
6 февраля 2021 в 09:43
0

Установка n-го бита на x (значение бита) без использования -1

Иногда, когда вы не уверены, к чему приведет -1 или что-то подобное, вы можете захотеть установить n-й бит без использования -1:

number = (((number | (1 << n)) ^ (1 << n))) | (x << n);

Пояснение: ((number | (1 << n) устанавливает n-й бит в 1 (где | обозначает побитовое ИЛИ), затем с помощью (...) ^ (1 << n) мы устанавливаем n-й бит в 0 и, наконец, с помощью (...) | x << n) мы устанавливаем n-й бит, который был 0, на (значение бита) x.

Это также работает в golang.

Will Eccles
7 мая 2021 в 21:16
1

Это могло бы быть намного более кратко (и, вероятно, более эффективно, если компилятор не оптимизирует ваше решение) как (number & ~(1 << n)) | (!!x << n).

avatar
Balaji Boggaram Ramanarayan
22 апреля 2020 в 20:30
4

Эта программа основана на вышеупомянутом решении @ 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
avatar
Pankaj Prakash
10 июня 2019 в 05:52
11

Предположим сначала несколько вещей
num = 55 Целое число для выполнения побитовых операций (установка, получение, очистка, переключение).
<1919445 битовое положение для выполнения 668357> 0

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

  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. Наконец, выполните побитовую операцию AND & с результатом выше и num. Вышеупомянутые три шага вместе можно записать как num & (~ (1 << n));

Steps to clear a bit

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)

Рекомендуемая литература - Упражнения с побитовым оператором

Chandra Shekhar
28 декабря 2019 в 18:37
0

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

avatar
Sazzad Hissain Khan
21 февраля 2018 в 12:35
7
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);
}
Xeverous
5 мая 2020 в 16:07
1

Тип возврата check_nth_bit может быть bool.

Sazzad Hissain Khan
5 мая 2020 в 18:09
0

@Xeverous да, это зависит от намерения вызывающего абонента

avatar
Joakim L. Christiansen
10 февраля 2018 в 20:07
3

Шаблонная версия (помещенная в файл заголовка) с поддержкой изменения нескольких бит (работает на микроконтроллерах AVR, кстати):

namespace bit {
  template <typename T1, typename T2>
  constexpr inline T1 bitmask(T2 bit) 
  {return (T1)1 << bit;}
  template <typename T1, typename T3, typename ...T2>
  constexpr inline T1 bitmask(T3 bit, T2 ...bits) 
  {return ((T1)1 << bit) | bitmask<T1>(bits...);}

  /** Set these bits (others retain their state) */
  template <typename T1, typename ...T2>
  constexpr inline void set (T1 &variable, T2 ...bits) 
  {variable |= bitmask<T1>(bits...);}
  /** Set only these bits (others will be cleared) */
  template <typename T1, typename ...T2>
  constexpr inline void setOnly (T1 &variable, T2 ...bits) 
  {variable = bitmask<T1>(bits...);}
  /** Clear these bits (others retain their state) */
  template <typename T1, typename ...T2>
  constexpr inline void clear (T1 &variable, T2 ...bits) 
  {variable &= ~bitmask<T1>(bits...);}
  /** Flip these bits (others retain their state) */
  template <typename T1, typename ...T2>
  constexpr inline void flip (T1 &variable, T2 ...bits) 
  {variable ^= bitmask<T1>(bits...);}
  /** Check if any of these bits are set */
  template <typename T1, typename ...T2>
  constexpr inline bool isAnySet(const T1 &variable, T2 ...bits) 
  {return variable & bitmask<T1>(bits...);}
  /** Check if all these bits are set */
  template <typename T1, typename ...T2>
  constexpr inline bool isSet (const T1 &variable, T2 ...bits) 
  {return ((variable & bitmask<T1>(bits...)) == bitmask<T1>(bits...));}
  /** Check if all these bits are not set */
  template <typename T1, typename ...T2>
  constexpr inline bool isNotSet (const T1 &variable, T2 ...bits) 
  {return ((variable & bitmask<T1>(bits...)) != bitmask<T1>(bits...));}
}

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

#include <iostream>
#include <bitset> // for console output of binary values

// and include the code above of course

using namespace std;

int main() {
  uint8_t v = 0b1111'1100;
  bit::set(v, 0);
  cout << bitset<8>(v) << endl;

  bit::clear(v, 0,1);
  cout << bitset<8>(v) << endl;

  bit::flip(v, 0,1);
  cout << bitset<8>(v) << endl;

  bit::clear(v, 0,1,2,3,4,5,6,7);
  cout << bitset<8>(v) << endl;

  bit::flip(v, 0,7);
  cout << bitset<8>(v) << endl;
}

Кстати: оказывается, что constexpr и inline не используются, если не отправлять аргумент оптимизатора (например: -O3) компилятору. Не стесняйтесь попробовать код на https://godbolt.org/ и посмотрите на вывод ASM.

melpomene
10 февраля 2018 в 20:11
0

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

Joakim L. Christiansen
25 февраля 2018 в 15:51
0

@melpomene Код не битый, тестировал. Вы имеете в виду, что он не будет компилироваться или результат неправильный? О дополнительном ';' Не помню, их действительно можно убрать.

melpomene
25 февраля 2018 в 18:25
0

(variable & bits == bits)?

Joakim L. Christiansen
27 февраля 2018 в 20:56
0

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

pqnet
25 октября 2019 в 20:27
1

используйте std::bitset в С ++ 11

avatar
chux - Reinstate Monica
27 сентября 2017 в 18:18
4

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

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

Какие проблемы возникают, когда number является более широким типом, чем 1?
x, может быть слишком большим для сдвига 1 << x, ведущего к (неопределенное поведение) <5158> Даже если 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;

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

chqrlie
27 сентября 2017 в 22:27
0

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

chux - Reinstate Monica
27 сентября 2017 в 22:33
1

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

avatar
Jeegar Patel
27 мая 2016 в 16:41
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 и будут полезны для обеспечения согласованности между процессорами.

avatar
sam msft
6 февраля 2015 в 23:11
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)
avatar
Vincet
27 мая 2014 в 11:46
-1

Попробуйте одну из этих функций на языке 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;
}
M.M
6 февраля 2015 в 23:57
0

value << n может вызвать неопределенное поведение

KPCT
18 апреля 2021 в 15:58
0

измените 1 на 0x1 или 1UL, чтобы избежать разговоров UB @ M.M

avatar
kendotwill
8 мая 2014 в 04:33
11

Раскрываем ответ 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;
}
avatar
user1899861
30 декабря 2012 в 02:31
11

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 упростят настройку и очистку bool.

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

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

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

galinette
17 ноября 2014 в 20:03
0

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

user1899861
17 ноября 2014 в 21:08
2

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

M.M
6 февраля 2015 в 23:55
4

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

user1899861
12 февраля 2015 в 07:23
0

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

Ben Voigt
22 февраля 2015 в 02:20
2

@RocketRoy: Тогда, наверное, стоит изменить предложение, в котором говорится, что это пример "битовых операций".

avatar
John U
14 июня 2012 в 15:23
27

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

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

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

John U
19 июня 2012 в 17:33
0

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

Ben Voigt
22 февраля 2015 в 02:16
1

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

Lundin
14 декабря 2015 в 09:42
2

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

avatar
kapilddit
5 июня 2012 в 14:18
36

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

Пример:

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)
avatar
Gokul Naathan
28 февраля 2012 в 19:27
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);
        }
    }
}
avatar
R.. GitHub STOP HELPING ICE
13 июля 2010 в 06:53
27

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

и т. Д.

foraidt
13 июля 2010 в 08:27
5

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

R.. GitHub STOP HELPING ICE
13 июля 2010 в 09:19
0

Верно. =) Один из вариантов, который вы могли бы предпочесть, - разделить его на 2 макроса, один для адресации элемента массива, а другой для смещения бита на место, ala BITCELL(a,b) |= BITMASK(a,b); (оба принимают a в качестве аргумента для определения размера, но последний никогда не будет оценивать a, поскольку он появляется только в sizeof).

PC Luddite
23 октября 2015 в 17:08
1

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

chux - Reinstate Monica
27 сентября 2017 в 17:58
0

Незначительное: третье приведение (size_t), похоже, существует только для того, чтобы застраховать некоторую беззнаковую математику с помощью %. Может там (unsigned).

chux - Reinstate Monica
27 сентября 2017 в 18:00
0

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

avatar
bill
13 июня 2009 в 21:27
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)))
M.M
6 февраля 2015 в 23:52
3

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

avatar
thangavel
11 апреля 2009 в 23:05
12

Используйте это:

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

    return num;
}
asdf
2 июля 2011 в 19:33
6

Что ж, он использует неэффективное ветвление.

M.M
6 февраля 2015 в 23:53
3

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

Ben Voigt
22 февраля 2015 в 02:18
3

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

avatar
John Zwinck
3 января 2009 в 23:44
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. Можно сделать аналогичные макросы для установки и сброса битов. Ключевое отличие этого решения от многих других здесь заключается в том, что оно работает для любого местоположения практически с любым типом переменных.

avatar
Roddy
6 ноября 2008 в 11:30
25

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

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, но это может зависеть от реализации. Также проверьте, как ваш компилятор обрабатывает поля, пересекающие границы байтов.

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

Lundin
18 августа 2011 в 19:50
3

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

Roddy
19 августа 2011 в 20:13
1

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

Lundin
20 августа 2011 в 09:35
1

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

user1899861
15 февраля 2013 в 22:26
0

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

avatar
Martin York
18 сентября 2008 в 00:34
496

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

paercebal
19 сентября 2008 в 18:16
37

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

xtofl
27 сентября 2008 в 19:21
1

Отлично, Мартин! Вы даже можете использовать перечисление для «индексации» битов: enum {cEngineOn, cDoorsOpen, cAircoOn}; std :: bitset <cNBBITS> mybits; mybits [cEngineOn] .set (); const bool cbDoorOpen = mybits [cDoorsOpen]; ...

moogs
15 октября 2008 в 11:43
0

среди ответов здесь я думаю, что это лучший способ управлять битами ... но я думаю, что суть вопроса заключалась в том, как манипулировать битами вручную. еще, + голосование :)

andrewdotnich
13 ноября 2008 в 06:10
0

@paercebal: vector <bool> не очень эффективен, так как bool в C ++ занимает полный байт пространства вместо 1 бита ...

Niklas
12 декабря 2008 в 20:40
26

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

Lundin
18 августа 2011 в 19:47
76

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

Martin York
18 августа 2011 в 19:51
9

@Lundin: первый пункт неверен (некоторые вещи в STL избегаются, но такой общий оператор является общим, std :: bitset прекрасен и ничего не стоит использовать). Пункт второй Boost :: dynamic_bitset ни от чего не зависит и может быть легко использован.

Lundin
19 августа 2011 в 06:26
19

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

Martin York
19 августа 2011 в 06:41
42

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

Martin York
23 апреля 2013 в 21:13
0

@ jons34yp: Документация SGI более коническая и обычно содержит меньше ошибок, чем cppreference.

mloskot
10 сентября 2013 в 12:24
1

В теме четко указан язык C, а не C ++. Ясно, что это неправильный ответ, так как же он получил 120+ голосов ?!

Martin York
10 сентября 2013 в 21:42
11

@mloskot: вопрос четко указывает на C ++. Его в теге под вопросом, какие языки допустимы. Он получил более 120 голосов, потому что люди, которые умеют читать, читают вопрос и правильно оценивают его в контексте тегов. :-) Ох. И это просто.

mloskot
11 сентября 2013 в 09:14
0

@LokiAstari Осмелюсь заявить, что либо тег неверен, либо тело темы и неточно. Это важный вопрос, так как он приводит к тому, что ТАК вопросы становятся расплывчатыми, а значит, на самом деле бесполезными.

sudo
13 августа 2014 в 07:14
0

Все, что я знаю, это то, что каждый раз, когда я пытаюсь скомпилировать что-то, сделанное на C ++, Boost становится моим новым злейшим врагом. Все равно проголосовали за.

Martin York
13 августа 2014 в 09:09
2

@ 9000: Почему? Вы не должны ничего делать с наддувом. Это библиотека только для заголовков, и установка ускорения должна быть связана с управлением пакетами sudo apt-get install boost-devel

Luis Colorado
30 сентября 2014 в 10:53
0

Вопрос задан для C / C ++, поэтому, чтобы быть совместимым с обоими мирами, я думаю, что STL здесь неприменим.

Martin York
30 сентября 2014 в 14:47
1

@LuisColorado: Но если не говорить об этом, значит, информация будет недоступна для пользователей. Дело не в том, чтобы ограничить ваш ответ, а в том, чтобы предложить решение. ОП решит, актуально ли это, поставив галочку рядом с лучшим ответом. Вы голосуете «за», если считаете, что это полезный для сообщества ответ, или «против», если он не имеет ценности для сообщества.

Martin York
2 октября 2014 в 17:55
0

@LuisColorado: Я не согласен. Это решило проблему способом, о котором не упоминали другие. По общему признанию, это было для части людей, которые использовали C ++, но вопрос был помечен как C ++ и, следовательно, правильный ответ.

Luis Colorado
5 октября 2014 в 10:18
0

@LokiAstari, и по этой причине я только что оставил комментарий, без отрицательного голоса, ничего больше ... просто комментарий.

Peter - Reinstate Monica
22 января 2020 в 17:15
1

@MartinYork Мир встроенных систем огромен; но если избегать STL и вместо этого некоторые контейнеры и т. д. написаны самостоятельно, код почти наверняка будет менее производительным и будет содержать больше ошибок. (Я работал над одной такой системой в начале 2000-х. Она была хорошо встроена, информационно-развлекательная, но 5 лет назад у нее были ресурсы ПК.)

Martin York
22 января 2020 в 19:04
0

@ Peter-ReinstateMonica, мы прямо говорим о std::bitset только здесь.

Peter - Reinstate Monica
22 января 2020 в 20:13
0

@MartinYork Я пытался поддержать ваше заявление; "STL / Boost, который не используется во встроенных системах, тоже неверен" - это правильно. Это используется. (А иногда, когда система достаточно функциональна, а программное обеспечение достаточно сложное, на самом деле ошибкой не использовать STL, потому что альтернативы - без дженериков или самодельные - хуже.)

avatar
Tim Ring
17 сентября 2008 в 14:10
12

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

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

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

R.. GitHub STOP HELPING ICE
28 июня 2010 в 06:24
2

Не используйте таблицу для функции, которая может быть реализована с помощью одного оператора. TQuickByteMask [n] эквивалентен (1 << n). Кроме того, очень плохая идея - делать ваши аргументы краткими. На самом деле / ​​и% будут делением, а не побитовым сдвигом / побитовым и, поскольку знаковое деление на степень 2 не может быть реализовано побитовым. Вы должны сделать тип аргумента беззнаковым int!

Lundin
18 августа 2011 в 19:32
0

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

Lundin
18 августа 2011 в 19:42
2

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

jeb
18 ноября 2011 в 11:28
2

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

avatar
yogeesh
17 сентября 2008 в 02:04
52

Из файла 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, то это выражение будет оценить до:

0000 0000 0000 0000 0000 0000 0000 0001 binary.

Если posn == 8, он будет оцениваться как

0000 0000 0000 0000 0000 0001 0000 0000 binary.

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

После создания маски она применяется к аргументу так, как вы предлагаете, с помощью побитовых операторов и (&), или (|), и 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] ---------------------------------------- -------------------------

Dan
18 октября 2008 в 01:51
4

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

Lundin
18 августа 2011 в 19:14
21

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

M.M
6 февраля 2015 в 23:51
3

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

Peter Cordes
10 ноября 2017 в 21:38
1

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

avatar
Ferruccio
11 сентября 2008 в 00:56
264

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

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

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

R.. GitHub STOP HELPING ICE
28 июня 2010 в 06:17
76

Я всегда считал, что использование битовых полей - плохая идея. У вас нет контроля над порядком, в котором распределяются биты (сверху или снизу), что делает невозможным сериализацию значения стабильным / переносимым способом, кроме побитового. Также невозможно смешивать битовую арифметику DIY с битовыми полями, например, создать маску, которая проверяет сразу несколько битов. Конечно, вы можете использовать && и надеяться, что компилятор правильно его оптимизирует ...

Lundin
18 августа 2011 в 19:19
40

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

Ferruccio
18 августа 2011 в 19:35
46

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

endolith
8 марта 2012 в 19:47
0

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

Ferruccio
8 марта 2012 в 21:02
5

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

iGbanam
28 ноября 2012 в 22:39
0

Это круто. Однако мой вопрос: после проверки sizeof(mybits) я получаю 12 (т.е. размер трех ints). Это место, выделенное в памяти, или какая-то ошибка в функции sizeof?

Ferruccio
29 ноября 2012 в 00:36
0

@Yasky - какой компилятор вы используете? Я получаю 4 с VC ++ 11 и Clang 3.1.

iGbanam
29 ноября 2012 в 01:02
0

@Ferruccio Я использую gcc версию 4.4.5

Shade
17 мая 2014 в 04:59
1

@Р. Можно использовать оба, struct можно поместить внутри (обычно анонимного) union с целым числом и т. Д. Это работает. (Я понимаю, что это старый тред, кстати)

Lundin
14 декабря 2015 в 09:30
0

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

Kelly S. French
8 декабря 2016 в 16:11
4

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

user13709754
28 июня 2020 в 00:57
0

вы можете захотеть __attribute__((packed))

avatar
dmckee --- ex-moderator kitten
8 сентября 2008 в 21:07
128

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

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

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

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

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

В остальном я поддерживаю решение Джереми.

endolith
20 декабря 2011 в 15:09
2

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

dmckee --- ex-moderator kitten
22 декабря 2011 в 01:15
5

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

Luis Colorado
30 сентября 2014 в 10:55
1

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

Lundin
14 декабря 2015 в 09:25
7

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

Aiken Drum
15 марта 2016 в 15:01
3

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