Заметки о Windows и других программных продуктах Microsoft...

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

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

Давным давно, когда компьютеры были очень большими и очень медленными, оперативной памяти в них было мало и ее старались использовать максимально эффективно. Например у переменной типа bool есть всего два возможных значения (true и false), которые теоретически могут быть представлены одним битом, а по факту занимают целый байт памяти. Это происходит из за того, что переменные используют уникальные адреса памяти, которые выделяются в байтах. В результате переменная занимает всего 1 бит, а остальные 7 тратятся впустую.

Избежать напрасной траты памяти можно с помощью побитовых операторов. Побитовые операторы, как следует из названия, позволяют оперировать непосредственно битами. С их помощью можно уместить 8 значений типа bool в переменной размером 1 байт, что значительно сэкономит потребление памяти.

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

PowerShell поддерживает все стандартные побитовые операторы включая побитовое И (-band), побитовое ИЛИ (-bor), побитовое исключающее ИЛИ (-bxor) и побитовое НЕ (-bnot). А начиная с третьей версии в PowerShell появились операторы побитового сдвига. Операций сдвига две – битовый сдвиг влево (-shl) и битовый сдвиг вправо (-shr). Начнем с побитовых операторов сравнения.

Побитовое И (bitwise AND, bAND)

Побитовые операторы используют двоичный формат значений. Например число 1 будет выглядеть как 00000001, а число 3 — как 00000011 (с учетом 1 байт — 8 бит).

Действуют побитовые операторы аналогично обычным логическим операторам сравнения (И, ИЛИ), но в отличии от них применяются к каждому биту. Так побитовый оператор И производит сравнение и возвращает 1 только в том случае, если оба входных бита равны 1. Если хотя бы один бит равен 0, то на выходе получим тоже 0.


В качестве примера возьмем числа 1 и 3 и переведем их в двоичный вид. Затем (для наглядности) разместим числа одно над другим и применим операцию побитового И к каждому столбцу с битами. В результате операции получим 1.

0000 0001 (1)
0000 0011 (3)
———————— bAND
0000 0001 (1)

В PowerShell для операции побитового И используется оператор —band. Например:

1 -band 3

оператор -bAND

Побитовое ИЛИ (bitwise OR, bOR)

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

Для примера проделаем операцию побитового ИЛИ над числами 1 и 3, в результате получим 3.

0000 0001 (1)
0000 0011 (3)
———————— bOR
0000 0011 (3)

В PowerShell для операции побитового ИЛИ используется оператор —bor. Например:

1 -bor 3

оператор -bOR

Исключающее побитовое ИЛИ (bitwise exclusive OR, bXOR)

Операция исключающего побитового ИЛИ возвращает 1 в том случае, если только один из входных битов равен 1. Если оба входных бита равняются 1 или 0, то результат равен 0. Так в результате операции исключающего побитового ИЛИ над ″многострадальными″ числами 1 и 3 получим 2.


0000 0001 (1)
0000 0011 (3)
———————— bXOR
0000 0010 (2)

В PowerShell операция исключающего побитового ИЛИ реализована с помощью оператора —bxor. Например:

1 -bxor 3

оператор -bXOR

Побитовое НЕ (bitwise NOT, bNOT)

На первый взгляд побитовое НЕ — самый простой для понимания оператор. Операция побитового НЕ просто меняет каждый бит числа на противоположный  (0 на 1 или 1 на 0).

Для примера возьмем число 00000011 (3) и применим к нему оператор bnot. В результате инверсии получим число 11111100, которое при переводе в десятичную систему даст отрицательное число -4.

0000 0011 (3)
———————— bNOT
1111 1100 (-4)

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

-bnot 3

оператор -bNOT

 

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

оператор -bNOT

 

У вас может возникнуть вполне закономерный вопрос — почему оператор -bnot меняет знак числа. Дело в том, что по умолчанию в PowerShell используется подписанное представление чисел. Для представления отрицательных чисел в двоичной системе используется так называемый дополнительный код. Дополнительный код двоичного числа получается путем применения к каждому биту числа (кроме старшего, которое считается знаковым) операции инверсии и прибавлении к полученному числу 1.

Так в случае с -4 берем десятичное число 4 и  переводим его в двоичный вид (прямой код), затем инвертируем значение (получаем обратный код), к полученной инверсии прибавляем 1 и в результате получаем десятичное число -4 в дополнительном коде:

0000 0100 (прямой код)
1111 1011 (обратный код)     

0000 0001  

———————
1111 1100 (дополнительный код)

Надеюсь с этим все более-менее понятно, идем дальше.

Побитовый сдвиг влево (shift bits left, shl)

Битовый сдвиг влево сдвигает биты влево на указанное количество, дописывая справа нули. Для примера возьмем число 3, переведем его в двоичный вид и сдвинем на один бит влево. В результате получим 6.

0000 0011 (3)
———————— shl 1
0000 0110 (6)

Сдвиг на 2 бита даст число 12

0000 0011 (3)
———————— shl 2
0000 1100 (12)

на 3 бита — уже 24.

0000 0011 (3)
———————— shl 3
0001 1000 (24)

По аналогии с математическими операциями сдвиг влево можно представить как умножение на 2 в степени N. Сдвиг на 1 бит — умножение на 21, на 2 бита — умножение на 22, на 3 бита — умножение на 23. И так далее.

В PowerShell сдвиг влево осуществляется с помощью оператора -shl, например:

3 -shl 1
3 -shl 2
3 -shl 3

оператор -shl

Побитовый сдвиг вправо (shift bits right, shr)

Битовый сдвиг вправо сдвигает биты числа вправо, дописывая слева нули. При этом вышедшие за пределы числа биты отбрасываются. Так если взять число 12 и сдвинуть на 1 бит вправо, то получится 6

00001100 (12)
————————  shr 1
00000110 (6)

сдвиг на 2 бита вправо даст 3

00001100 (12)
———————— shr 2
00000011 (3)

а сдвиг на 3 бита вернет 1.

00001100 (12)
———————— shr 3
00000001 (1)

Т.е сдвиг вправо можно сравнить с делением на 2 в степени N с отбрасыванием остатка от деления:

Сдвиг на 1 бит 12 / 21 = 6
Сдвиг на 2 бита 12 / 22 = 3
Сдвиг на 3 бита 12 / 23 = 1

В PowerShell правый сдвиг осуществляется с помощью оператора -shr. Например:

12 -shr 1
12 -shr 2
12 -shr 3

оператор -shr

Пример

В PowerShell побитовые операторы используются достаточно редко, по крайней мере у меня никогда не было необходимости применять их в своих скриптах. Но тем не менее мне удалось найти довольно интересный пример их использования. Пример этот связан с сетями.

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

[CmdletBinding()]

Param (
[Parameter (Mandatory=$true, Position=1)]
[string]$subnet,
[Parameter (Mandatory=$true, Position=2)]
[string]$ip
)

$network, [int]$subnetlen = $subnet.Split(‘/’);
$a = [uint32[]]$network.split(‘.’);
[uint32] $unetwork = ($a[0] -shl 24) + ($a[1] -shl 16) + ($a[2] -shl 8) + $a[3];
$mask = (-bnot [uint32]0) -shl (32 — $subnetlen);
$b = [uint32[]]$ip.split(‘.’);
[uint32] $uip = ($b[0] -shl 24) + ($b[1] -shl 16) + ($b[2] -shl 8) + $b[3];
$unetwork -eq ($mask -band $uip);

Работает так — вводим сеть, адрес хоста и получаем True или False.

результат работы скрипта

 

А теперь давайте более подробно рассмотрим, как это работает. Вкратце напомню, что IP-адрес представляет из себя 32-битное число, состоящее из двух частей — адреса сети и адреса хоста. В свою очередь подсеть имеет сетевой адрес и длину в битах (маску подсети), составляющую сетевую часть адреса. Например 192.168.0.0/16 означает сеть с адресом 192.168.0.0 и маской 16. Маску 16 также можно записать как 255.255.0.0. Углубляться дальше не буду, про маски, адреса и прочее очень доступно написано здесь.

Переходим к скрипту. В качестве параметров скрипт принимает адрес сети с маской и адрес хоста:

Param (
[Parameter (Mandatory=$true, Position=1)]
[string]$subnet,
[Parameter (Mandatory=$true, Position=2)]
[string]$ip
)

Подсеть разбивается на собственно адрес и маску с помощью команды:

$network, [int]$subnetlen = $subnet.Split(‘/’);

Затем адрес сети приводится к типу Uint32 (32-битное целое число без знака). Сначала адрес сети разбивается на 4 части по 8 бит (1 байт) каждая и помещается в переменную в виде массива. Затем каждый байт извлекается из массива и помещается на свое место в uint32 с помощью побитового сдвига влево. Чтобы поместить $a[3] в младший байт, просто добавляем его (нулевой сдвиг), $a[2] сдвигаем на 8 бит (1 байт), $a[1] — на 16 бит (2 байта) и $a[0] на 24 бита (3 байта):

$a = [uint32[]]$network.split(‘.’);
[uint32] $unetwork = ($a[0] -shl 24) + ($a[1] -shl 16) + ($a[2] -shl 8) + $a[3];

Для расчета маски сети берется число, обратное 0, приведенное к типу Uint32:

(-bnot [uint32]0)

Количество нулей в маске подсети равно (32 — $subnetlen). Производим сдвиг влево на это число и получаем маску подсети:

$mask = (-bnot [uint32]0) -shl (32 — $subnetlen)

С адресом хоста производим такие же манипуляции, как и с адресом сети:

$b = [uint32[]]$ip.split(‘.’)
[uint32] $uip = ($b[0] -shl 24) + ($b[1] -shl 16) + ($b[2] -shl 8) + $b[3]

В результате получаем адрес подсети и адрес хоста в виде Uint32. Вычисляем сетевую часть адреса хоста с помощью побитового И (band), результат сравниваем с адресом подсети:

$unetwork -eq ($mask -band $uip)

Если адреса одинаковые, значит IP адрес хоста попадает в указанную подсеть, разные — не попадает.

 
 
Комментарии

Пока нет комментариев.