Инструкции сдвига и вращения также манипулируют отдельными битами числа.
Для сдвига влево применяется инструкция shl, которая имеет следующий синтаксис:
shl count, dest
Первый операнд - count
указывается на сколько разрядов сдвигаем влево число из второго операнда. Второй операнд - dest представляет число, в котором сдвигаются биты. Это может быть либо регистр, либо переменная.
Например:
.globl _start .text _start: movq $5, %rdi # в RDI число 5 или 00000101 shlq $1, %rdi # сдвигаем число в RDI на 1 разряд влево = 00000101 << 1 = 00001010 = 10 movq $60, %rax syscall
Здесь в регистр RDI помещаем число 5. Поскольку регистр 64-разрядный, то его старшие 7 байт будут содержать нули. А значение из младшего байта будет равно 00000101 (число 5 в двоичной системе). С помощью инструкции shlq сдвигаем число из регистра RDI влево на 1 разряд:
shlq $1, %rdi
То есть 1 крайний левый бит числа отбрасывается, а справа добавляется нулевой бит. Таким образом, мы получим
00000101 << 1 = 00001010 = 10
С помощью символов << обычно обозначается сдвиг влево. В регистре RDI в итоге будет число 00001010, которое в десятичной системе равно 10.
Другой пример:
.globl _start .text _start: movq $69, %rdi # в RDI число 69 или 01000101 movq $2, %rcx # в RCX число 2 shlq %rcx, %rdi # сдвигаем число в RDI на 2 разряда влево = 01000101 << 2 = 00010100 = 20 movq $60, %rax syscall
Для разнообразия здесь количество сдвигаемых битов - число 2 помещаем в регистр RCX. В регистре RDI число 69, которое в двоичной системе равно 01000101.
Поскольку его сдвигаем на два разряда влево, то отбрасываем 2 крайних левых бита, а справа добавляем два нулевых бита. В итоге получим 00010100
, что
представляет число 20 в десятичной системе.
Можно заметить, что при сдвиг на n разрядов влево фактически равнозначен умножению на n. Поэтому операцию сдвига влево можно использовать вместо операции умножения.
Стоит учитывать, что при сдвиге влево последний отбрасываемый бит копируется в регистр флагов во флаг переноса CF. И при необходимости мы можем инструкциями условного перехода, которые производят переход, если флаг переноса установлен:
.globl _start .text _start: movb $69 , %al # в AL число 69 или 01000101 shlb $2, %al # сдвигаем число в AL на 2 разряда влево = 01000101 << 2 = 00010100 = 20 jc carry_set # при сдвиге отброшены 2 левых бита - 01 - флаг CF установлен movq $0, %rdi jmp exit carry_set: movq $1, %rdi exit: movq $60, %rax syscall
Здесь также сдвигаем число 69 или 01000101
влево на 2 разряда. Это ведет к тому, что левые два бита - 01 отбрасываются. Последний из этих битов копируется во флаг переноса CF.
Поскольку последний отбрасываемый бит здесь равен 1, то соответственно флаг CF будет установлен. А с помощью инструкции jc мы можем проверить установку этого флага и
выполнить переход к метке carry_set
, если флаг установлен.
Кроме флага CF инструкция сдвига также устанавливает и другие флаги состояния. Флаг нуля ZF устанавливается, если в результате сдвига получилось число 0. Флаг знака устанавливается, если старший бит результата равен 1. Флаг переполнения OF устанавливается, если при сдвиге на 1 разряд меняется старший бит (с 0 на 1 или с 1 на 0).
Для сдвига вправо предназначена инструкция shr, которая работает во многом аналогично инструкции shl
:
shr count, dest
В качестве второго операнда также передается сдвигаемое число, а в качестве первого операнда - на сколько разрядов надо сдвинуть число вправо. Например:
.globl _start .text _start: movq $69, %rdi # в RDI число 69 или 01000101 shrq $2, %rdi # сдвигаем число в RDI на 2 разряда вправо = 01000101 >> 2 = 00010001 = 17 movq $60, %rax syscall
Здесь помещаем в регистр RDI число 69 или 01000101 в двоичной системе и сдвигаем его на два разряда вправо. Сдвиг вправо на n разрядов приводит к тому что n правых разрядов числа отбрасываются, а с слева добавляются n нулевых битов. Поэтому при сдвиге числа 01000101 вправо отбрасываем справа 2 бита и слева добавляем 2 нуля:
01000101 >> 2 = 00010001 = 17
Фактически сдвиг вправо беззнаковых чисел на N разрядов эквивалентно делению числа на 2*N.
При сдвиге вправо также устанавливаются флаги состояния. Последний отрасываемый бит копируется во флаг переноса CF. Если результат сдвига равен 0, то также устанавливается флаг нуля ZF. Если при сдвиге вправо на 1 разряд меняется старший бит (с 1 на 0), то также устанавливается флаг переполнения OF. При этом флаг знака SF всегда сбрасывается, так как слева в любом случае добавляется 0.
Кроме логического сдвига вправо в ассемблере есть арифметически сдвиг вправо, для которого применяется инструкция sar:
sar count, dest
Она работает во многом аналогично за тем исключением, что сдвигаемое число слево дополняется не нулями, а значением из старшего бита. Это позволяет сохранить значение знакового бита. Например, возьмем сначала стандартный логический сдвиг вправо:
.globl _start .text _start: movq $-32, %rdi # в RDI число -8 или FFFFFFE0h shrq $4, %rdi # сдвигаем число в RDI на 4 разряда вправо = FFFFFFE0 >> 4 = 0FFFFFFE = 268435454 movq $60, %rax syscall
Здесь в регистр RDI помещается отрицательное число -32 (FFFFFFE0 в 16-чной системе). Когда логического сдвига сдивает на 4 байта вправо, в итоге получаем 0FFFFFFE или 268435454 в десятичной системе. С точки зрения логического сдвига это нормальный результат. Но например выше мы видели, что логический сдвиг вправо беззнакового числа на N разрядов вправо эквивалентен делению на 2*N. С числами со знаком такого не проходит. И в этом случае мы можем использовать арифметический сдвиг вправо:
.globl _start .text _start: movq $-32, %rdi # в RDI число -8 или FFFFFFE0h sarq $4, %rdi # сдвигаем число в RDI на 4 разряда вправо = FFFFFFE0 >> 4 = FFFFFFFE = -2 movq $60, %rax syscall
При арифметическим сдвиге вправо также устанавливаются флаги состояния. Последний отрасываемый бит копируется во флаг переноса CF. Если результат сдвига равен 0 (например, сдвигается положительное число), то также устанавливается флаг нуля ZF. Но в отличие от операции логического сдвига флаг переполнения OF всегда сброшен (так как знак числа не меняется), а флаг знака SF получает значение старшего бита числа.
Операции вращения влево и вправо ведут себя так же, как операции сдвига влево и вправо, за тем исключением, что бит, смещенный с одного конца, помещается обратно в другой конец. Для вращения предусмотрены две инструкции: rol (поворот влево) и ror (поворот вправо). Синтаксис этих инструкций аналогичен инструкциям сдвига:
rol count, dest ror count, dest
Второй операнд представляет сдвигаемое число (в виде регистра или переменной), а первый операнд - количество сдвигаемых битов.
Эти инструкции также изменяются флаги состояния. Но в отличие от инструкций сдвига, инструкции поворота не влияют на установку флагов знака или нуля. Последний сдвинутый бит копируется во флаг переноса CF. При 1-битном повороте также изменяется флаг OF: при повороте влево флаг OF получает результат операции XOR (исключающее ИЛИ) двух первых старших битов, а при повороте вправо флаг OF получает результат операции XOR двух старших битов после поворота.
Пример вращения влево:
.globl _start .text _start: movq $131, %rax # в AL число 131 или 10000011 rolb $2, %al # вращаем число в AL на 2 разряда влево = 10000011 << 2 = 00001110 = 14 movq %rax, %rdi movq $60, %rax syscall
Здесь в регистр AL помещается число 131 или 100000112. Инструкция rol
поворачивает это число на разряда влево, поэтому два старших бита помещаются в начало
10000011 << 2 = 00001110
Что в десятичной системе будет равно 14.
Пример операции вращения вправо:
.globl _start .text _start: movq $131, %rax # в AL число 131 или 10000011 rorb $2, %al # вращаем число в AL на 2 разряда вправо = 10000011 >> 2 = 11100000 = 224 movq %rax, %rdi movq $60, %rax syscall
Здесь вращаем тоже самое число также на 2 разряда только вправо, поэтому два последних бита перейдут в начало, а результат будет равен 224:
10000011 >> 2 = 11100000
Дополнительно ассемблер предоставляет инструкции для вращения с использованием флага переноса - rcl (вращение влево с переносом) и rcr (вращение вправо с переносом):
rcl count, dest rcr count, dest
При вращении влево смещаемый старший бит числа переходит во флаг переноса, а бит из флага переноса переходит в конец числа. При вращении вправо смещаемый младший бит переходит во флаг переноса, а бит из флага переноса переходит в начало числа.
Пример вращения влево с переносом
.globl _start .text _start: movq $131, %rax # в AL число 131 или 10000011 rclb $2, %al # вращаем число в AL на 2 разряда влево = 10000011 << 2 = 00001101 = 13 movq %rax, %rdi movq $60, %rax syscall
Здесь число 131 или 100000112 вращается влево на 2 разряда с переносом. Вращение на 2 разряда мы можем разбить на два этапа - по каждому разряду. Поскольку вначале программы флаг переноса не установлен, равен 0, то при вращении первого бита числа нулевой бит из флага переноса перейдет в конец числа, а старщий бит числа - во флаг переноса:
Перед операцией CF = 0 AL = 10000011 После операции CF = 1 AL =00000110
При вращении второго бита повторяются действия:
Перед операцией CF = 1 AL = 00000110 После операции CF = 0 AL =00001101