Сдвиг и вращение

Последнее обновление: 23.09.2023

Инструкции сдвига и вращения также манипулируют отдельными битами числа.

Сдвиг влево

Для сдвига влево применяется инструкция 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
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850