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

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

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

Сдвиг влево

Для сдвига влево применяется инструкция shl, которая имеет следующий синтаксис:

shl dest, count

Первый операнд - dest представляет число, в котором сдвигаются биты. Это может быть либо регистр, либо переменная.

Второй операнд - count указывается на сколько разрядов сдвигаем влево число из первого операнда. Это может быть либо регистр CL, либо константа в диапазоне от 0 до n, где n на единицу меньше числа битов в первом операнде (например, n = 7 для 8-разрядных операндов, n = 15 для 16-разрядных, n = 31 для 32-разрядных и n = 63 для 64-разрядных операндов).

Например:

.code
main proc
    mov rax, 0      ; обнуляем регистр RAX
    mov al, 5       ; в AL число 5 или 00000101
    shl al, 1       ; сдвигаем число в AL на 1 разряд влево = 00000101 << 1 = 00001010 = 10
    ret
main endp
end

Здесь в регистр AL помещаем число 5. Поскольку регистр 8-разрядный, то его значение будет равно 00000101 (число 5 в двоичной системе). С помощью инструкции shl сдвигаем число из регистра AL влево на 1 разряд:

shl al, 1

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

00000101 << 1 = 00001010 = 10

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

Другой пример:

.code
main proc
    mov rax, 0      ; обнуляем регистр RAX
    mov al, 69      ; в AL число 69 или 01000101
    mov cl, 2       ; в CL число 2
    shl al, cl      ; сдвигаем число в AL на 2 разряда влево = 01000101 << 2 = 00010100 = 20
    ret
main endp
end

Для разнообразия здесь количество сдвигаемых битов - число 2 помещаем в регистр CL. В регистре AL число 69, которое в двоичной системе равно 01000101. Поскольку его сдвигаем на два разряда влево, то отбрасываем 2 крайних левых бита, а справа добавляем два нулевых бита. В итоге получим 00010100, что представляет число 20 в десятичной системе.

Можно заметить, что при сдвиг на n разрядов влево фактически равнозначен умножению на n. Поэтому операцию сдвига влево можно использовать вместо операции умножения.

Стоит учитывать, что при сдвиге влево последний отбрасываемый бит копируется в регистр флагов во флаг переноса CF. И при необходимости мы можем инструкциями условного перехода, которые производят переход, если флаг переноса установлен:

.code
main proc
    mov al, 69      ; в AL число 69 или 01000101
    shl al, 2      ; сдвигаем число в AL на 2 разряда влево = 01000101 << 2 = 00010100 = 20
    jc carry_set   ; при сдвиге отброшены 2 левых бита - 01 - флаг CF установлен
    mov eax, 0
    ret
carry_set:
    mov eax, 1
    ret
main endp
end

Здесь также сдвигаем число 69 или 01000101 влево на 2 разряда. Это ведет к тому, что левые два бита - 01 отбрасываются. Последний из этих битов копируется во флаг переноса CF. Поскольку последний отбрасываемый бит здесь равен 1, то соответственно флаг CF будет установлен. А с помощью инструкции jc мы можем проверить установку этого флага и выполнить переход к метке carry_set, если флаг установлен.

Кроме флага CF инструкция сдвига также устанавливает и другие флаги состояния. Флаг нуля ZF устанавливается, если в результате сдвига получилось число 0. Флаг знака устанавливается, если старший бит результата равен 1. Флаг переполнения OF устанавливается, если при сдвиге на 1 разряд меняется старший бит (с 0 на 1 или с 1 на 0).

Логический сдвиг вправо

Для сдвига вправо предназначена инструкция shr, которая работает во многом аналогично инструкции shl:

shr dest, count

В качестве первого операнда также передается сдвигаемое числа, а в качестве второго операнда - на сколько разрядов надо сдвинуть число вправо. Например:

.code
main proc
    mov rax, 0     ; обнуляем регистр rax
    mov al, 69     ; в AL число 69 или 01000101
    shr al, 2      ; сдвигаем число в AL на 2 разряда вправо = 01000101 >> 2 = 00010001 = 17
    ret
main endp
end

Здесь помещаем в регистр AL число 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 dest, count

Она работает во многом аналогично за тем исключением, что сдвигаемое число слево дополняется не нулями, а значением из старшего бита. Это позволяет сохранить значение знакового бита. Например, возьмем сначала стандартный логический сдвиг вправо:

.code
main proc
    mov eax, -32    ; в EAX число -8 или FFFFFFE0h
    shr eax, 4      ; сдвигаем число в EAX на 4 разряда вправо = FFFFFFE0 >> 4 = 0FFFFFFE = 268435454
    ret
main endp
end

Здесь в регистр eax помещается отрицательное число -32 (FFFFFFE0 в 16-чной системе). Когда логического сдвига сдивает на 4 байта вправо, в итоге получаем 0FFFFFFE или 268435454 в десятичной системе. С точки зрения логического сдвига это нормальный результат. Но например выше мы видели, что логический сдвиг вправо беззнакового числа на N разрядов вправо эквивалентен делению на 2*N. С числами со знаком такого не проходит. И в этом случае мы можем использовать арифметический сдвиг вправо:

.code
main proc
    mov eax, -32    ; в EAX число -8 или FFFFFFE0h
    sar eax, 4      ; сдвигаем число в EAX на 4 разряда вправо = FFFFFFE0 >> 4 = FFFFFFFE = -2
    ret
main endp
end

При арифметическим сдвиге вправо также устанавливаются флаги состояния. Последний отрасываемый бит копируется во флаг переноса CF. Если результат сдвига равен 0 (например, сдвигается положительное число), то также устанавливается флаг нуля ZF. Но в отличие от операции логического сдвига флаг переполнения OF всегда сброшен (так как знак числа не меняется), а флаг знака SF получает значение старшего бита числа.

Вращение

Операции вращения влево и вправо ведут себя так же, как операции сдвига влево и вправо, за тем исключением, что бит, смещенный с одного конца, помещается обратно в другой конец. Для вращения предусмотрены две инструкции: rol (поворот влево) и ror (поворот вправо). Синтаксис этих инструкций аналогичен инструкциям сдвига:

rol dest, count
ror dest, count

Первый операнд представляет сдвигаемое число (в виде регистра или переменной), а второй операнд - количество сдвигаемых битов.

Эти инстркции также изменяются флаги состояния. Но в отличие от инструкций сдвига, инструкции поворота не влияют на установку флагов знака или нуля. Последний сдвинутый бит копируется во флаг переноса CF. При 1-битном повороте также изменяется флаг OF: при повороте влево флаг OF получает результат операции XOR (исключающее ИЛИ) двух первых старших битов, а при повороте вправо флаг OF получает результат операции XOR двух старших битов после поворота.

Пример вращения влево:

.code
main proc
    mov rax, 0
    mov al, 131    ; в AL число 131 или 10000011
    rol al, 2      ; вращаем число в AL на 2 разряда влево = 10000011 << 2 = 00001110 = 14
    ret
main endp
end

Здесь в регистр AL помещается число 131 или 100000112. Инструкция rol поворачивает это число на разряда влево, поэтому два старших бита помещаются в начало

10000011 << 2 = 00001110

Что в десятичной системе будет равно 14.

Пример операции вращения вправо:

.code
main proc
    mov rax, 0
    mov al, 131    ; в AL число 131 или 10000011
    ror al, 2      ; вращаем число в AL на 2 разряда вправо = 10000011 >> 2 = 11100000 = 224
    ret
main endp
end

Здесь вращаем тоже самое число также на 2 разряда только вправо, поэтому два последних бита перейдут в начало, а результат будет равен 224:

10000011 >> 2 = 11100000

Дополнительно ассемблер предоставляет инструкции для вращения с использованием флага переноса - rcl (вращение влево с переносом) и rcr (вращение вправо с переносом):

rcl dest, count
rcr dest, count

При вращении влево смещаемый старший бит числа переходит во флаг переноса, а бит из флага переноса переходит в конец числа. При вращении вправо смещаемый младший бит переходит во флаг переноса, а бит из флага переноса переходит в начало числа.

Поворот и сдвиг числа в ассемблере

Пример вращения влево с переносом

.code
main proc
    mov rax, 0
    mov al, 131    ; в AL число 131 или 10000011
    rcl al, 2      ; вращаем число в AL на 2 разряда влево = 10000011 << 2 = 00001101 = 13
    ret
main endp
end

Здесь число 131 или 100000112 вращается влево на 2 разряда с переносом. Вращение на 2 разряда мы можем разбить на два этапа - по каждому разряду. Поскольку вначале программы флаг переноса не установлен, равен 0, то при вращении первого бита числа нулевой бит из флага переноса перейдет в конец числа, а старщий бит числа - во флаг переноса:

Перед операцией
CF = 0
AL = 10000011 
После операции
CF = 1
AL =00000110

При вращении второго бита повторяются действия:

Перед операцией
CF = 1
AL = 00000110 
После операции
CF = 0
AL =00001101
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850