Работа с битами в языке C
При программировании микроконтроллеров постоянно приходится работать с битами. Устанавливать, сбрасывать, проверять их наличие в том или ином регистре и т.д. В компиляторах для разных платформ могут различаться команды для установки отдельных битов в регистрах, поэтому полезно знать независимые от устройства команды для работы с битами на языке Си.
В Си существуют 6 операторов для манипулирования битами:
<< — сдвиг влево
>> — сдвиг вправо
~ — поразрядная инверсия
| — поразрядное ИЛИ
& — поразрядное И
^ — поразрядное исключающее ИЛИ
Их можно применять к любым целочисленным знаковым или беззнаковым типам переменных.
Разберём подробнее каждую операцию.
Пример для беззнаковой переменной:
9/4 = 2.5, но 9>>2 (1001>>2) = 2
11/4 = 2.75, но 11>>2 (1011>>2) = 2
28/4 = 7, а тут всё ровно 28>>2 (11100>>2) = 7
Во втором случае ошибка больше, потому что оба младших разряда единицы. В третьем случае ошибки нет, потому что потерянные разряды нулевые.
Использовать десятичные числа для установки битов довольно неудобно, поскольку приходится самостоятельно переводить их в двоичные. Гораздо наглядней это делать с помощью операции сдвига влево <<.
Установить несколько битов в единицу можно так:
Обнулять биты, используя десятичные цифры, неудобно. Но можно облегчить себе жизнь, воспользовавшись операторами << и ~
1<<3 — сдвинуть единицу на три разряда влево (= 0b00001000),
~(1<<3) — выполнить инверсию полученного числа (= 0b11110111),
tmp & (~(1<<3)) — выполнить операцию & между значением переменной tmp и инвертированным числом (= 0b10011011 & 0b11110111), а результат присвоить переменной tmp.
В результате этих операций tmp = 0b10010011.
Обнулить несколько битов можно так:
Используя составной оператор присваивания &= ,можно записать выражение более компактно:
Четвертый бит изменил свое значение на противоположное, а остальные биты остались без изменений.
Гораздо нагляднее записывать подобные выражения по другому:
У поразрядного исключающего ИЛИ есть еще одно интересное свойство. Его можно использовать, для того чтобы поменять значения двух переменных местами. Обычно для этого требуется третья переменная:
В Си существуют 6 операторов для манипулирования битами:
<< — сдвиг влево
>> — сдвиг вправо
~ — поразрядная инверсия
| — поразрядное ИЛИ
& — поразрядное И
^ — поразрядное исключающее ИЛИ
Их можно применять к любым целочисленным знаковым или беззнаковым типам переменных.
Разберём подробнее каждую операцию.
Cдвиг влево <<
Сдвигает число на N разрядов влево. Старшие N разрядов при этом исчезают, а младшие N разрядов заполняются нулями.
unsigned char tmp = 3; // 0b00000011
tmp = tmp << 1; // теперь в переменной tmp число 6 или 0b00000110
tmp = tmp << 3; // теперь в переменной tmp число 48 или 0b00110000
Выражения, в которых над переменной производится какая-либо операция, а потом результат операции присваивается этой же переменной, можно записывать короче, используя составные операторы.
tmp = 7; // 0b00000111
tmp <<= 2; // сокращенный вариант записи, в переменной tmp теперь 0b00011100 (28)
Операция сдвига влево на N разрядов эквивалентна умножению переменной на 2N.
Сдвиг вправо >>
Сдвигает число на N разрядов вправо. Младшие N разрядов при этом теряются. Заполнение старших N разрядов зависит от типа переменной и ее значения. Старшие N разрядов заполняются нулями в двух случаях — если переменная беззнакового типа или если переменная знаковая и ее текущее значение положительное. Когда переменная знаковая и ее значение отрицательное — старшие разряды заполняются единицами.Пример для беззнаковой переменной:
unsigned char tmp = 255; // 0b11111111
tmp = tmp >> 1; // теперь в переменной tmp число 127 или 0b01111111
tmp >>= 3; // сокращенный вариант записи, теперь в переменной tmp число 15 или 0b00001111
Пример для переменной знакового типа:
int tmp = 3400; // 0b0000110101001000
tmp >>= 2; // теперь в переменной число 850 или 0b0000001101010010
tmp = -1200; // 0b1111101101010000
tmp >>= 2; // теперь в tmp число -300 или 0b1111111011010100
// два старших разряда заполнились единицами
Операция сдвига вправо на N разрядов эквивалентна делению на 2N. При этом есть некоторые нюансы. Если потерянные младшие разряды содержали единицы, то результат подобного «деления» получается грубоватым. Например:9/4 = 2.5, но 9>>2 (1001>>2) = 2
11/4 = 2.75, но 11>>2 (1011>>2) = 2
28/4 = 7, а тут всё ровно 28>>2 (11100>>2) = 7
Во втором случае ошибка больше, потому что оба младших разряда единицы. В третьем случае ошибки нет, потому что потерянные разряды нулевые.
Поразрядная инверсия ~
Поразрядная инверсия, как следует из названия, поразрядно (в двоичном представлении) инвертирует число. Разряды, в которых были нули – заполняются единицами. Разряды, в которых были единицы – заполняются нулями. Оператор поразрядной инверсии является унарным оператором, то есть используется с одним операндом.
unsigned char tmp = 94; // 0b01011110
tmp = ~tmp; // теперь в переменной tmp число 161 или 0b10100001
tmp = ~tmp; // теперь в tmp снова число 94 или 0b01011110
Поразрядное ИЛИ |
Оператор | осуществляет операцию логического ИЛИ между соответствующими битами двух операндов. Результатом операции логического ИЛИ между двумя битами будет 0 только в случае, если оба бита равны 0. Во всех остальных случаях результат будет 1.
Таблица истинности оператора | иллюстрирует результат выполнения оператора над двумя битами B1 и B2.
Оператор | обычно используют для установки заданных битов переменной в единицу.
Таблица истинности оператора | иллюстрирует результат выполнения оператора над двумя битами B1 и B2.
Оператор | обычно используют для установки заданных битов переменной в единицу.
tmp = 155
tmp = tmp | 4; // устанавливаем в единицу второй бит переменной tmp
| B1 | B2 | B1|B2 |
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
| 155 | 0b10011011 |
| | | |
| 4 | 0b00000100 |
| 159 | 0b10011111 |
Использовать десятичные числа для установки битов довольно неудобно, поскольку приходится самостоятельно переводить их в двоичные. Гораздо наглядней это делать с помощью операции сдвига влево <<.
tmp = tmp | (1<<4); // устанавливаем в единицу четвертый бит переменной tmp
Читаем справа налево – сдвинуть единицу на четыре разряда влево, выполнить операцию ИЛИ между полученным числом и значением переменной tmp, результат присвоить переменной tmp.
Установить несколько битов в единицу можно так:
tmp = tmp | (1<<7)|(1<<5)|(1<<0); // устанавливаем в единицу седьмой, пятый и нулевой биты переменной tmp
С помощью составного оператора присваивания |= можно сделать запись компактней.
tmp |= (1<<4);
tmp |= (1<<7)|(1<<5)|(1<<0);
Побитовое И &
Оператор & осуществляет операцию логического И между соответствующими битами двух операндов. Результатом операции логического И между двумя битами будет 1 только в том случае, если оба бита равны 1. Во всех других случаях результат будет 0.
Это проиллюстрировано в таблице истинности.
Оператор & обычно применяют, чтобы обнулить один или несколько битов.
Третий бит стал равен 0, а остальные биты не изменились.
Это проиллюстрировано в таблице истинности.
Оператор & обычно применяют, чтобы обнулить один или несколько битов.
tmp = 155;
tmp = tmp & 247; // обнуляем третий бит переменной tmp
Третий бит стал равен 0, а остальные биты не изменились.
| B1 | B2 | B1&B2 |
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
| 155 | 0b10011011 |
| & | |
| 247 | 0b11110111 |
| 147 | 0b10010011 |
Обнулять биты, используя десятичные цифры, неудобно. Но можно облегчить себе жизнь, воспользовавшись операторами << и ~
tmp = 155;
tmp = tmp & (~(1<<3)); // обнуляем третий бит
Читаем эту запись справа налево:1<<3 — сдвинуть единицу на три разряда влево (= 0b00001000),
~(1<<3) — выполнить инверсию полученного числа (= 0b11110111),
tmp & (~(1<<3)) — выполнить операцию & между значением переменной tmp и инвертированным числом (= 0b10011011 & 0b11110111), а результат присвоить переменной tmp.
В результате этих операций tmp = 0b10010011.
Обнулить несколько битов можно так:
tmp = tmp & (~((1<<3)|(1<<5)|(1<<6))); // обнуляем третий, пятый и шестой биты
Здесь сначала выполняются операции сдвига, потом операции поразрядного ИЛИ, затем инверсия, поразрядное И, присвоение результата переменной tmp.
Используя составной оператор присваивания &= ,можно записать выражение более компактно:
tmp &= (~((1<<3)|(1<<5)|(1<<6))); // Хотя читаемость, конечно, ужасная, не делайте так
Как проверить установлен ли бит в переменной? Нужно обнулить все биты, кроме проверочного, а потом сравнить полученное значение с нулем:
if ((tmp & (1<<2)) != 0 ) {
// блок будет выполняться, только если установлен
// второй бит переменной tmp
}
if ((tmp & (1<<2)) == 0 ) {
// блок будет выполняться, только если не установлен
// второй бит переменной tmp
}
Побитовое исключающее ИЛИ ^
Оператор ^ осуществляет операцию логического исключающего ИЛИ между соответствующими битами двух операндов. Результатом операции логического исключающего ИЛИ будет 0 в случае равенства битов. Во всех остальных случаях результат будет 1. Это проиллюстрировано в таблице истинности.
Оператор ^ применяется не так часто как остальные битовые операторы, но и для него находится работенка. Например, с помощью него можно инвертировать один или несколько битов переменной.
Оператор ^ применяется не так часто как остальные битовые операторы, но и для него находится работенка. Например, с помощью него можно инвертировать один или несколько битов переменной.
tmp = 155;
tmp = tmp ^ 8; // инвертируем третий бит переменой tmp
| B1 | B2 | B1^B2 |
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
| 155 | 0b10011011 |
| ^ | |
| 8 | 0b00001000 |
| 147 | 0b10010011 |
Гораздо нагляднее записывать подобные выражения по другому:
tmp = tmp ^ (1<<3); // инвертируем третий бит переменой tmp
А так и удобно и компактно:
tmp ^= (1<<4); // инвертируем четвертый бит
У поразрядного исключающего ИЛИ есть еще одно интересное свойство. Его можно использовать, для того чтобы поменять значения двух переменных местами. Обычно для этого требуется третья переменная:
tmp = var1;
var1 = var2;
var2 = tmp;
Но используя оператор ^ переставить значения можно так:
var1 ^= var 2;
var 2 ^= var 1;
var 1 ^= var 2;