Что значит инвертируем все биты числа
Перейти к содержимому

Что значит инвертируем все биты числа

Побитовые операции

Побитовые операции (англ. bitwise operations) — операции, производимые над цепочками битов. Выделяют два типа побитовых операций: логические операции и побитовые сдвиги.

Содержание

Принцип работы [ править ]

Логические побитовые операции [ править ]

Битовые операторы И [math](AND,\ \&)[/math] , ИЛИ [math](OR,\ \mid)[/math] , НЕ [math](NOT,\ \sim)[/math] и исключающее ИЛИ [math](XOR,\ $\textasciicircum$,\ \oplus)[/math] используют те же таблицы истинности, что и их логические эквиваленты.

Побитовое И [ править ]

Побитовое И используется для выключения битов. Любой бит, установленный в [math]0[/math] , вызывает установку соответствующего бита результата также в [math]0[/math] .

&
11001010
11100010
11000010
Побитовое ИЛИ [ править ]

Побитовое ИЛИ используется для включения битов. Любой бит, установленный в [math]1[/math] , вызывает установку соответствующего бита результата также в [math]1[/math] .

|
11001010
11100010
11101010
Побитовое НЕ [ править ]

Побитовое НЕ инвертирует состояние каждого бита исходной переменной.

Побитовое исключающее ИЛИ [ править ]

Исключающее ИЛИ устанавливает значение бита результата в [math]1[/math] , если значения в соответствующих битах исходных переменных различны.

^
11001010
11100010
00101000

Побитовые сдвиги [ править ]

Операторы сдвига [math]\lt \lt [/math] и [math]<\gt \gt >[/math] сдвигают биты в переменной влево или вправо на указанное число. При этом на освободившиеся позиции устанавливаются нули (кроме сдвига вправо отрицательного числа, в этом случае на свободные позиции устанавливаются единицы, так как числа представляются в двоичном дополнительном коде и необходимо поддерживать знаковый бит).

Сдвиг влево может применяться для умножения числа на два, сдвиг вправо — для деления.

В языке программирования Java существует также оператор беззнакового битового сдвига вправо [math]\gt \gt \gt [/math] . При использовании этого оператора на освободившиеся позиции всегда устанавливаются нули.

Применение [ править ]

Сложные операции [ править ]

Определение знака числа [ править ]

Пусть дано число [math]x[/math] . Поскольку при сдвиге вправо на освобождающиеся позиции устанавливается бит знака, знак числа [math]x[/math] можно определить, выполнив сдвиг вправо на всю длину переменной:

Используя побитовые операции можно также узнать, различны ли знаки двух переменных [math]x[/math] и [math]y[/math] . Если числа имеют различный знак, то результат операции XOR, произведенной над их знаковыми битами, будет единицей. Поэтому неравенство [math](x \oplus y) \lt 0[/math] будет верно в том случае, если числа [math]x[/math] и [math]y[/math] разного знака.

Вычисление модуля числа без использования условного оператора [ править ]

Пусть дано число [math]x[/math] . Если [math]x[/math] положительно, то [math]mask = 0[/math] , и [math](x + mask) \oplus mask = x[/math] . В случае, если [math]x[/math] отрицательно, [math]mask = -1[/math] . Тогда получается, что мы работаем с числом [math]x[/math] так, как будто оно представлено в коде со сдвигом с тем отличием, что у нас знаковый бит принимает значение [math]1[/math] для отрицательных чисел, а [math]0[/math] — для положительных.

Нахождение минимума и максимума из двух чисел без использования условного оператора [ править ]

Этот способ корректен только если можно утверждать, что величина [math](x — y)[/math] лежит между граничными значениями типа int.

Пусть даны числа [math]x[/math] и [math]y[/math] разрядности [math]n[/math] . Тогда если [math]x \lt y[/math] , то [math]((x — y) \gt \gt (n — 1)) = -1[/math] , а если [math]x \geqslant y[/math] , то [math]((x — y) \gt \gt (n — 1)) = 0[/math] . Выражение [math]((x — y) \& ((x — y) \gt \gt (n — 1))[/math] принимает значение [math]0[/math] , если [math]x \geqslant y[/math] , и [math](x — y)[/math] , если [math]x \lt y[/math] .

Проверка на то, является ли число степенью двойки [ править ]

Пусть дано число [math]x[/math] . Тогда, если результатом выражения [math](x\ \&\&\ !(x\ \&\ (x — 1)))[/math] является единица, то число [math]x[/math] — степень двойки.

Правая часть выражения [math](!(x\ \&\ (x — 1)))[/math] будет равна единице, только если число [math]x[/math] равно [math]0[/math] или является степенью двойки. Если число [math]x[/math] является степенью двойки, то в двоичной системе счисления оно представляется следующим образом: [math]1\underbrace<0\dots0>_[/math] , где [math]n[/math] — показатель степени. Соответственно, выражение [math](x — 1)[/math] будет иметь вид [math]\underbrace<1\dots1>_[/math] , и [math]x\ \&\ (x — 1)[/math] равно [math]0[/math] .

Операция логического И в данном выражении отсекает тот случай, когда [math](x = 0)[/math] и не является степенью двойки, но при этом правая часть [math](!(x\ \&\ (x — 1)))[/math] равна единице.

Нахождение младшего единичного бита [ править ]

Пусть дано число [math]x[/math] и необходимо узнать его младший единичный бит.

Применим к числу [math]x[/math] побитовое отрицание, чтобы инвертировать значения всех его бит, а затем прибавим к полученному числу единицу. У результата первая часть (до младшего единичного бита) не совпадает с исходным числом [math]x[/math] , а вторая часть совпадает. Применив побитовое И к этим двум числам, получим степень двойки, соответствующую младшему единичному биту исходного числа [math](x\ \&\ (\sim x + 1))[/math] .

К такому же результату можно прийти, если сначала отнять от числа [math]x[/math] единицу, чтобы обнулить его младший единичный бит, а все последующие разряды обратить в [math]1[/math] , затем инвертировать результат и применить побитовое И с исходным числом [math](x\ \&\ \sim (x — 1))[/math] .

Нахождение старшего единичного бита [ править ]

Пусть дано число [math]x[/math] и необходимо узнать его старший единичный бит.

Рассмотрим некоторое число, представим его как [math]0\dots01b \dots b[/math] , где [math]b[/math] — любое значение бита. Тогда, если совершить битовый сдвиг этого числа вправо на [math]1[/math] и произвести побитовое ИЛИ результата сдвига и исходного числа, мы получим результат [math]0\dots011b \dots b[/math] . Если мы повторим эту последовательность действий над полученным числом, но устроим сдвиг на [math]2[/math] , то получим [math]0\dots01111b \dots b[/math] . При каждой следующей операции будем увеличивать модуль сдвига до следующей степени двойки. После некоторого количества таких операций (зависит от разрядности числа) мы получим число вида [math]0\dots01\dots1[/math] . Тогда результатом выполнения действий [math]x — (x \texttt< \gt \gt >1)[/math] будет число, состоящее только из старшего бита исходного числа.

Циклический сдвиг [ править ]

Пусть дано число [math]x[/math] и надо совершить циклический сдвиг его битов на величину [math]d[/math] . Желаемый результат можно получить, если объединить числа, полученные при выполнении обычного битового сдвига в желаемую сторону на [math]d[/math] и в противоположном направлении на разность между разрядностью числа и величиной сдвига. Таким образом, мы сможем поменять местами начальную и конечную части числа.

Подсчет количества единичных битов [ править ]

Для подсчета количества единичных битов в числе [math]x[/math] можно воспользоваться следующим алгоритмом:

Поскольку [math]5555_<16>[/math] равно [math]01010101 01010101_<2>[/math] , результатом операции [math]x\ \&\ 5555_<16>[/math] является число, в котором все нечетные биты соответствуют нечетным битам числа [math]x[/math] . Аналогично, результатом операции [math](x\ \texttt<\gt \gt \gt >\ 1)\ \&\ 5555_<16>[/math] является число, в котором все нечетные биты соответствуют четным битам [math]x[/math] . Четные биты результата в обоих случаях равны нулю.

Мысленно разобьем двоичную запись нашего числа [math]x[/math] на группы по [math]2[/math] бита. Результатом операции [math]x\ \&\ 5555_ <16>+ (x\ \texttt<\gt \gt \gt >\ 1)\ \&\ 5555_<16>[/math] будет такое число, что если разбить его двоичную запись на группы по два бита, значение каждой группы соответствует количеству единичных битов в соответствующей паре битов числа [math]x[/math] .

Аналогично, число [math]3333_<16>[/math] равно [math]00110011 00110011_<2>[/math] и операция [math]x = (x\ \&\ 3333_<16>) + (x\ \texttt<\gt \gt \gt >\ 2\ \&\ 3333_<16>)[/math] , примененная к результату, полученному на первом этапе, выполняет подсчет количества единичных битов в блоках по [math]4[/math] . В свою очередь, число [math]\texttt<0F0F>_<16>[/math] равно [math]00001111 00001111_<2>[/math] и операция [math]x = (x\ \&\ \texttt<0F0F>_<16>) + (x\ \texttt<\gt \gt \gt >\ 4\ \&\ \texttt<0F0F>_<16>)[/math] позволяет подсчитать число единичных бит в блоках по [math]8[/math] .

Теперь необходимо просуммировать числа, записанные в блоках по [math]8[/math] битов, чтобы получить искомую величину. Это можно сделать, домножив результат на [math]0101_<16>[/math] [math](1 00000001_<2>)[/math] . Ответ на задачу будет находиться в первых восьми битах произведения. Выполнив сдвиг вправо на [math]8[/math] (для шестнадцатибитных чисел), мы получим долгожданный ответ.

Заметим, что операция [math]x\ \&\ 55_ <16>+ (x\ \texttt<\gt \gt \gt >\ 1)\ \&\ 55_<16>[/math] равносильна операции [math]x — (x\ \texttt<\gt \gt \gt >\ 1)\ \&\ 55_<16>[/math] , в чем легко убедиться, рассмотрев все числа из двух бит.

В свою очередь, операцию [math](x\ \&\ \texttt<0F0F>_<16>) + ((x\ \texttt<\gt \gt \gt >\ 4)\ \&\ \texttt<0F0F>_<16>)[/math] можно заменить на [math](x + (x\ \texttt<\gt \gt \gt >\ 4))\ \&\ \texttt<0F0F>_<16>[/math] . Эта замена не повлияет на результат, так как максимальное значение в любой группе из четырех битов данного числа равно четырем, то есть требует только трех битов для записи, и выполнение суммирования не повлечет за собой переполнения и выхода за пределы четверок.

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

Разворот битов [ править ]

Чтобы получить биты числа [math]x[/math] , записанные в обратном порядке, применим следующий алгоритм.

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

Применение для решения задач [ править ]

Работа с битовыми масками [ править ]

Для работы с подмножествами удобно использовать битовые маски. Применяя побитовые операции легко сделать следующее: найти дополнение [math](\sim mask)[/math] , пересечение [math](mask_1\ \&\ mask_2)[/math] , объединение [math](mask_1 \mid mask_2)[/math] множеств, установить бит по номеру [math](mask \mid (1\ \texttt<\lt \lt >\ x))[/math] , снять бит по номеру [math](mask\ \&\ \sim(1\ \texttt<\lt \lt >\ x))[/math] .

Битовые маски используются, например, при решении некоторых задач [1] динамического программирования.

Алгоритм Флойда [ править ]

Алгоритм Флойда–Уоршелла (англ. the Floyd–Warshall algorithm) — алгоритм для нахождения длин кратчайших путей между всеми парами вершин во взвешенном ориентированном графе. Работает корректно, если в графе нет циклов отрицательной величины, а если же такой цикл есть, позволяет найти хотя бы один такой цикл. Асимптотическая сложность алгоритма [math] \Theta(n^3) [/math] , также требует [math] \Theta(n^2) [/math] памяти.

Дерево Фенвика [ править ]

Дерево Фенвика (англ. Binary indexed tree) — структура данных, которая может выполнять следующие операции:

  • изменять значение любого элемента в массиве,
  • выполнять некоторую ассоциативную, коммутативную, обратимую операцию [math] \circ [/math] на отрезке [math] [i, j] [/math] .

Данная структура требует [math] O(n) [/math] памяти, а выполнение каждой операции происходит за [math] O(\log n) [/math] .

Функция, позволяющая делать операции вставки и изменения элемента за [math] O(\log n) [/math] , задается следующей формулой [math] F(i) = (i \And (i + 1)) [/math] . Пусть дан массив [math] A = [a_0, a_1, \ldots, a_][/math] . Деревом Фенвика называется массив [math] T [/math] из [math] n [/math] элементов: [math] T_i = \sum\limits_^ a_k[/math] , где [math] i = 0\ldots n — 1 [/math] и [math] F(i) [/math] — функция, которую мы определили ранее.

Битовое представление чисел

Так как на уровне схем почти вся логика бинарная, ровно такое представление и используется для хранения чисел в компьютерах: каждая целочисленная переменная указывает на какую-то ячейку из 8 ( char ), 16 ( short ), 32 ( int ) или 64 ( long long ) бит.

#Эндианность

Единственная неоднозначность в таком формате возникает с порядком хранения битов — также называемый эндианностью. Зависимости от архитектуры он может быть разным:

  • При схеме little-endian сначала идут младшие биты. Например, число $42_<10>$ будет храниться так: $010101$.
  • При записи в формате big-endian сначала идут старшие биты. Все примеры из начала статьи даны в big-endian формате.

Хотя big-endian более естественный для понимания — на бумаге мы ровно так обычно и записываем бинарные числа — по разным причинам на большинстве современных процессоров по умолчанию используется little endian.

Иными словами, «$i$-тый бит» означает «$i$-тый младший» или «$i$-тый справа», но на бумаге мы ничего не инвертируем и записываем двоичные числа стандартным образом.

#Битовые операции

Помимо арифметических операций, с числами можно делать и битовые, которые интерпретируют их просто как последовательность битов.

#Сдвиги

Битовую запись числа можно «сдвигать» влево ( x << y ) или вправо ( x >> y ), что эквивалентно умножению или делению на степень двойки с округлением вниз.

Обычное умножение и деление — не самые быстрые операции, однако все битовые сдвиги всегда работают ровно за один такт. Как следствие, умножение и деление на какую-то фиксированную степень двойки всегда работает быстро — даже если вы не используете сдвиги явно, компилятор скорее всего будет проводить подобную оптимизацию.

#Побитовые операции

Помимо && , || и ! , существуют их побитовые версии, которые применяют соответствующую логическую операцию к целым последовательностям битов: & , | ,

Также помимо них есть ещё операция исключающего «или» (XOR), которая записывается как ^ .

  • $13$ & $7$ = $1101_2$ & $0111_2$ = $0101_2$ = $5$
  • $17$ | $10$ = $10001_2$ | $01010_2$ = $11011_2$ = $27$
  • $17$ ^ $9$ = $10001_2$ ^ $01001_2$ = $11000_2$ = $24$

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

#Маски

Бинарные последовательности можно поставить в соответствие подмножествам какого-то фиксированного множества: если на $i$-той позиции стоит единица, то значит $i$-тый элемент входит множество, а иначе не входит.

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

#Выделить i-й бит числа

(x >> i) & 1 или x & (1 << i) . Во втором случае результат будет либо 0, либо $2^i$.

Это часто используется для проверки, принадлежит ли $i$-тый элемент множеству:

Напомним, что нумерация идет с младших бит и начинается с нуля.

#Получить число, состоящее из k единиц

(1 << k) — 1 , также известное как $(2^k-1)$ и соответствующее маске полного множества.

#Инвертировать все биты числа

#Добавить i-й элемент в множество

#Удалить $i$-й элемент из множества

#Удалить i-й элемент из множества, если он есть

Также добавляет этот элемент, если его нет.

#Знаковые числа

Целочисленные переменные делятся на два типа — знаковые (signed) и беззнаковые (unsigned).

Если сложить две unsigned int переменные, сумма которых превосходит $2^<32>$, произойдет переполнение: сумму нельзя будет представить точно, и поэтому вместо неё результатом будут только нижние 32 бит. Все операции с беззнаковыми числами как бы проходят по модулю какой-то степени двойки.

Знаковые же типы нужны для хранения значений, которые могут быть и отрицательными. Для этого нужно выделить один бит для хранение знака — отрицательное ли число или нет — немного пожертвовав верхней границей представимых чисел: теперь самое большое представимое число это $2^<31>-1$, а не $2^<32>-1$.

Инженеры, которые работают над процессорами, ещё более ленивые, чем программисты — это мотивировано не только стремлением к упрощению, но и экономией транзисторов. Поэтому когда в signed типах происходит переполнение, результат в битовом представлении считается так же, как и в случае с unsigned числами. Если мы хотим ничего не менять в плане того, как работают unsigned числа, представление отрицательных чисел должно быть таким, что число $-x$ как бы вычитается из большой степени двойки:

  • Все неотрицательные числа записываются в точности как раньше.
  • У всех отрицательных чисел самый большой бит будет единичным.
  • Если прибавить к $2^<31>-1$ единицу, то результатом будет $-2^<31>$, представляемое как 10000000 (в целях изложения мы будем записывать 8 бит, хотя в int их 32).
  • Зная двоичную запись положительного числа x , запись -x можно получить как

Упражнение. Каких чисел больше: положительных или отрицательных?

Осторожно. В стандарте C/C++ прописано, что переполнение знаковых переменных приводит к undefined behavior, поэтому полагаться на описанную логику переполнения нельзя, хотя равно это скорее всего и произойдет.

#128-битные числа

Общих регистров размера больше 64 в процессорах нет, однако умножение и несколько смежных инструкций могут использовать два последовательных регистра как один большой. Это позволяет быстро перемножать два 64-битных числа и получать 128-битный результат, разделенный на нижние биты и верхние.

Это весьма специфичная операция, и поэтому в языках программирования нет полноценной поддержки 128-битных переменных. В C++ однако есть «костыль» — тип __int128_t — который фактически просто оборачивает пару из двух 64-битных регистров и поддерживает арифметические операции с ними.

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

_______________ сдвиг влево << _______________

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

tmp = 7; //0b00000111
tmp <<= 2; //сокращенный вариант записи
//теперь в переменной tmp число 28 или 0b00011100

Операция сдвига влево на n разрядов эквивалентна умножению переменной на 2 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 разрядов эквивалентна делению на 2 n . При этом есть некоторые нюансы. Если потерянные младшие разряды содержали единицы, то результат подобного “деления” получается грубоватым.

Например 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

_______________поразрядная инверсия

tmp;
//теперь в переменной tmp число 161 или 0b10100001

tmp;
//теперь в tmp снова число 94 или 0b01011110

_______________ поразрядное ИЛИ | ______________

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

tmp = 155
tmp = tmp | 4; //устанавливаем в единицу второй бит переменной tmp

155 0b10011 0 11
|
4 0b00000 1 00
159 0b10011 1 11

tmp = tmp | (1<<4); //устанавливаем в единицу четвертый бит переменной tmp

Установить несколько битов в единицу можно так

tmp = tmp | (1<<7)|(1<<5)|(1<<0);
//устанавливаем в единицу седьмой, пятый и нулевой биты переменной tmp

С помощью составного оператора присваивания |= можно сделать запись компактней.

_______________ побитовое И & _______________

Оператор & обычно применяют, чтобы обнулить один или несколько битов.

tmp = 155;
tmp = tmp & 247; //обнуляем третий бит переменной tmp

155 0b1001 1 011
&
247 0b1111 0 111
147 0b1001 0 011

Видите, третий бит стал равен 0, а остальные биты не изменились.

Обнулять биты, используя десятичные цифры, неудобно. Но можно облегчить себе жизнь, воспользовавшись операторами << и

tmp = 155;
tmp = tmp & (

(1<<3)); //обнуляем третий бит

(1<<3)) 0b1001 1 011 & 0b1111 0 111
результат 0b1001 0 011

Обнулить несколько битов можно так

((1<<3)|(1<<5)|(1<<6))); //обнуляем третий, пятый и шестой биты

Используя составной оператор присваивания &= ,можно записать выражение более компактно

((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

155 0b1001 1 011
^
8 0b0000 1 000
147 0b1001 0 011

Четвертый бит изменил свое значение на противоположное, а остальные биты остались без изменений.

tmp = tmp ^ 8; // опять инвертируем четвертый бит переменой tmp

147 0b1001 0 011
^
8 0b000 0 1 000
155 0b1001 1 011

Видите, четвертый бит снова изменил свое значение на противоположное.

Так записывать выражение намного удобнее

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;

Чистая магия, хотя, честно говоря, я ни разу не пользовался таким приемом.

________________Директива #define__________________

Пример:

//порт, к которому подключены кнопки
#define PORT_BUTTON PORTA
#define PIN_BUTTON PINA
#define DDRX_BUTTON DDRA

//выводы, к которым подключены кнопки
#define DOWN 3
#define CANCEL 4
#define UP 5
#define ENTER 6

int main()
<
//конфигурируем порт на вход,
//и включаем подтягивающие резисторы
DDRX_BUTTON = 0;
PORT_BUTTON = 0xff;

При задании символического имени можно использовать и выражения

пример использования:
tmp = PORTB & MASK_BUTTONS;

Используя #define не жалейте скобок чтобы четко задать последовательность вычисления выражений!

Некоторые выражения можно замаскировать под «функции».

#define ADC_OFF() ADCSRA = 0

Можно использовать многострочные определения, используя в конце каждой строки символ \

#define INIT_Timer() TIMSK = (1<<OCIE0);\
TCCR0 = (1<<WGM01)|(0<<WGM00)|(1<<CS02);\
TCNT0 = 0;\
OCR0 = 0x7d

Ну и самое мощное применение директивы #define – это задание макроопределений (или просто макросов). Вот как с помощью #define можно задать макросы для рассмотренных ранее операций с битами

#define SetBit(reg, bit) reg |= (1<<bit)
#define ClearBit(reg, bit) reg &= (

(1<<bit))
#define InvBit(reg, bit) reg ^= (1<<bit)
#define BitIsSet(reg, bit) ((reg & (1<<bit)) != 0)
#define BitIsClear(reg, bit) ((reg & (1<<bit)) == 0)

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

SetBit(PORTB, 0); //установить нулевой бит порта B
InvBit(tmp,6); //инвертировать шестой бит переменной tmp

Определим макрос, вычисляющий квадрат числа:

#define SQUARE(x) x*x

выражение
tmp = SQUARE(my_var);
даст корректный результат.

А что будет если в качестве аргумента макроопределения использовать выражение my_var+1

tmp = SQUARE(my_var +1);

Препроцессор заменит эту строчку на

tmp = my_var + 1 * my_var +1;

а это вовсе не тот результат, который мы ожидаем.

Чтобы избежать таких ошибок не скупитесь на скобки при объявлении макросов!

Если объявить макрос так

#define SQUARE(x) ((x)*(x))

выражение
tmp = SQUARE(my_var +1);
даст корректный результат, потому что препроцессор заменит эту строчку на
tmp = ((my_var + 1) * (my_var +1));

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *