Что такое циклический сдвиг
Перейти к содержимому

Что такое циклический сдвиг

Не зная брода, не лезь в воду. Часть третья

Сдвиги
Продолжу рассказы о том, как программисты ходят по краю, даже не подозревая об этом. Поговорим об операциях сдвига <<, >>. Принципы работы операторов сдвига очевидны и многие программисты даже не знают, что их использование согласно стандарту Си/Си++ может приводить к неопределенному или к неуточненному поведению (undefined behaviour/unspecified behavior).

Предыдущие статьи доступны здесь: [1], [2].

Исторический экскурс

В начале немного истории. Необходимость операций сдвига битов очевидна любому программисту. Рано или поздно каждый сталкивается с необходимостью работать с отдельными битами и битовыми масками. Тем не менее, операции сдвига намного более популярны, чем должны были быть. Причина в том, что используя сдвиги можно умножать и делить числа на степень двойки. Например, операция «X << 3» умножит X на 8. Преимущество такого способа умножения и деления заключалось в скорости их работы в прошлом.

Сейчас я достал с пыльной полки книжку с описанием ассемблерных команд для процессоров, начиная с 8086 и заканчивая 80486. И нашёл таблицу о количестве тактов, необходимых для выполнения различных инструкций.

Умножение 16-битного регистра на ячейку памяти с использованием инструкции MUL на процессоре 8086 требует порядка 124-139 тактов!

Сдвиг 16-битного регистра на N позиций с использованием инструкции SHL на процессоре 8086 требует 8+4*N тактов. То есть в худшем случае получается 72 такта.

Можно было получить существенное ускорение при вычислении арифметических выражений используя различные трюки при работе с битовыми операциями. Это и стало причиной массового использования сдвигов, в начале, на языке ассемблер, потом в языках Си и Си++. Первые компиляторы Си/Си++ были просты. Человек мог получить выигрыш в скорости, явно подсказав компилятору, что здесь следует использовать сдвиг, а не инструкции умножения или деления.

С развитием процессоров польза от использования сдвигов долгое время сохранялась. В 80486 процессоре умножение стало занимать около 26 тактов. Вроде намного лучше. Однако сдвиг стал занимать всего 3 такта и вновь, был более привлекателен, чем умножение.

К счастью эти вынужденные оптимизации в большинстве своём канули в небытие. Во-первых, компиляторы стали умны и используют оптимальный набор инструкций для вычисления арифметических выражений. Во-вторых, процессоры также колоссально изменились. Появились конвейеры, предсказание переходов, переименование регистров и много чего ещё. Поэтому сказать, сколько времени займет выполнение той или иной инструкции обыкновенный программист уже не в состоянии. Но ясно, что если код местами будет не идеален, этого можно даже не заметить. Процессор разобьет инструкции на микроинструкции и начнет выполнять их параллельно. Если честно, то я уже не разбираюсь, как сейчас там всё происходит. Я понял, что разбираться в тонкостях бессмысленно, начиная с процессора Intel Pentium. И сделал вывод, что не стоит думать, что ты лучше знаешь, как нужно писать оптимизирующий код, использовать сдвиги и битовые операции, где только можно. Далеко не факт, что в результате код будет быстрее, чем сделает это оптимизатор в компиляторе. Зато точно можно сказать, что программа станет запутанной и сложной для понимания.

Примечание. Вышесказанное не значит, что использование битовых операций больше не может приносить выгоды. Есть много интересных и полезных трюков [3]. Главное не увлекаться.

Неопределенное поведение

Началось всё с того, что я решил увеличить в анализаторе PVS-Studio количество предупреждений связанных с undefined behaviour [4] и unspecified behavior [5]. Достаточно быстро и просто было реализовано правило, выявляющее некорректное использование операций сдвига. После этого мне пришлось остановиться и задуматься.

Оказалось, что программисты очень любят сдвиги. И используют их всевозможнейшим способом, приводящим часто с точки зрения стандарта к неопределенному поведению. Но одно дело теория, а другое практика. Есть ли смысл ругаться на код, который верой и правдой служил десятилетия и пережил уже не один компилятор? Сложный вопрос. Не смотря на то, что код некорректен, компиляторы следуют некому негласному соглашению и обрабатывают его единообразным способом.

После долгих раздумий, я всё-таки решил оставить данное диагностическое правило в PVS-Studio, не делая из него исключений. Если будет слишком много жалоб от пользователей, то возможно я изменю своё мнение. Впрочем, возможно пользователи удовлетворятся возможностью выключить эту диагностику или используют другие методы подавления предупреждений.

Кстати, именно эти душевные терзания и побудили меня написать статью. Надеюсь информация, которую я покажу, будет интересна и полезна.

Итак, посмотрим, что написано в стандарте C++11 по поводу операторов сдвига:

The shift operators << and >> group left-to-right.

shift-expression << additive-expression

shift-expression >> additive-expression

The operands shall be of integral or unscoped enumeration type and integral promotions are performed.

1. The type of the result is that of the promoted left operand. The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand.

2. The value of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are zero-filled. If E1 has an unsigned type, the value of the result is E1 * 2^E2, reduced modulo one more than the maximum value representable in the result type. Otherwise, if E1 has a signed type and non-negative value, and E1*2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

3. The value of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a non-negative value, the value of the result is the integral part of the quotient of E1/2^E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.

Читать подобные тексты грустно и печально. Но не волнуйтесь. Сейчас мы рассмотрим различные некорректные ситуации на примерах.

Самый простой случай приводящий к неопределенному поведению, это когда правый операнд имеет отрицательное значение. Пример:

Так, слава богу, никто не делает. По крайней мере, проанализировав более 70 open-source проектов, мы не встретили подобных ошибок.

Следующий случай гораздо интереснее. Это сдвиг на N бит, где N, большее, чем количество бит в левом операнде. Простейший пример:

Посмотрим, как такая ошибка может выглядеть на практике. Следующий фрагмент кода был обнаружен нами в библиотеке Lib7z:

Диагностическое сообщение PVS-Studio: V610 Undefined behavior. Check the shift operator ‘<<. The right operand (‘(8 * i)’ = [0..56]) is greater than or equal to the length in bits of the promoted left operand. lib7z 7zin.c 233

Функция пытается побайтно прочитать 64-битное значение. К сожалению, у неё это не получится, если число было больше 0x00000000FFFFFFFF. Обратите внимание на сдвиг «(UInt32)b << (8 * i)». Размер левого операнда составляет 32 бита. При этом сдвиг происходит от 0 до 56 бит. На практике это приведёт к тому, что старшая часть 64-битного значения останется заполнена нулями. Теоретически здесь вообще имеет место неопределенное поведение и результат непредсказуем.

Корректный код должен выглядеть так:

У читателя может возникнуть вопрос, а корректен ли код приведенный ниже?

Да, корректен. Слева от оператора << находится переменная A, состоящая только из 8 бит. Но перед началом операции сдвига, левая часть будет расширена до типа int. Соответственно, значение типа ‘int’ можно сдвинуть на 20 бит.

А теперь самый интересный момент. Это сдвиг отрицательных значений. Простейший пример:

В этом коде имеет место неопределённое и неуточненное поведение. С практической точки зрения разницы нет. Вывод один — так писать нельзя.

На этом можно было бы поставить точку и привести пару примеров. К сожалению, есть два нюанса, которые портят идеальную картину мира.

Нюансы, которые портят идеальную картину мира

Нюанс N1. В старом стандарте языка Си++ от 1998 года ситуации с неопределенным поведением обходятся стороной. Сказано, как ведет себя оператор << при сдвиге беззнаковых значений. А про знаковые значения ничего не сказано. В общем, тот самый случай, когда чтение стандарта не прибавляет ясности. Не рассматривается такой случай и всё.

Итак, с точки зрения Си++ от 1998 года, конструкция «-1 << 5» не приводит к неопределенному поведению. Впрочем, как она должна работать, тоже не описывается.

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

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

В старом стандарте Си++ и не сказано про неопределенное поведение. В новом сказано. Получается, что старый стандарт просто был недостаточно точен. Кстати, в новом стандарте языка Си (я смотрел черновик от 25 июня 2010), также сказано, что сдвиги отрицательных значений приводят к неопределенному поведению. Вывод — следует избавиться от некорректного кода.

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

Это записано так:

Сложно назвать библиотеку JPEG плохой. И этот код проверен временем и разными компиляторами.

С точки зрения стандарта его теперь следует переписать так:

Но стоит ли вносить такие правки решать вам. Я могу только дать совет всё-таки это сделать. Неизвестно когда и как, это может себя проявить.

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

17. Циклический сдвиг в ассемблере

Статья основана на материале xrnd с сайта asmworld (из учебного курса по программированию на ассемблер 16-битного процессора 8086 под DOS).

Циклический сдвиг отличается от линейного тем, что выдвигаемые с одного конца биты вдвигаются с другой стороны, то есть движутся по кольцу. В процессора x86 существует 2 вида циклического сдвига: простой и через флаг переноса ( CF ). У всех команд, рассматриваемых в этой части учебного курса, по 2 операнда, таких же, как у команд линейного сдвига. Первый операнд — сдвигаемое значение и место для записи результата. Второй операнд — счётчик сдвигов, который может находится в регистре CL или указываться непосредственно.

Простой циклический сдвиг

Циклический сдвиг вправо выполняется командой ROR , а влево — командой ROL . Схема работы этих команд представлена на рисунке (на примере 8-битного операнда):

Значение последнего выдвигаемого бита копируется в флаг CF . Для сдвигов на 1 бит устанавливается флаг OF , если в результате сдвига изменяется знаковый бит операнда. (Честно говоря, не помню, чтобы я этим когда-то пользовался. Но может вам пригодится ? ) Примеры использования команд:

Циклический сдвиг через флаг переноса

Отличие от простого циклического сдвига в том, что флаг CF участвует в сдвиге наравне с битами операнда. При сдвиге на 1 бит выдвигаемый бит помещается в CF , а значение CF вдвигается в операнд с другой стороны. При сдвиге на несколько бит эта операция повторяется многократно. Циклический сдвиг через флаг переноса выполняется командами RCR (вправо) и RCL (влево).

Опять же для сдвигов на 1 бит устанавливается флаг OF , если в результате сдвига изменяется знаковый бит операнда. Примеры использования команд:

Пример программы

В качестве примера напишем программу для подсчёта единичных битов в байте. Для анализа битов используется команда ROL в цикле. Цикл выполняется 8 раз — по числу битов в байте. Если очередной бит равен 1, то выполняем инкремент счётчика единичных битов.

Эту программу можно немного оптимизировать. Во-первых, использовать вместо условного перехода команду ADC со нулевым вторым операндом. Это позволит прибавить 1, если CF=1 и прибавить 0, если CF=0 . Во-вторых, считать единичные биты можно в регистре AH , а обнулить его в начале с помощью команды MOVZX , совместив с загрузкой байта в регистр AL . Ноль для команды ADC можно взять в регистре CH , это делает команду короче и быстрее, чем при использовании непосредственного операнда. CH равен 0 во время выполнения цикла, так как CX изменяется от 8 до 0. Вот что получилось в итоге:

Всего 6 команд для подсчета битов в байте. А попробуйте написать то же самое на языке высокого уровня ?

Упражнение

Объявите в программе строку. Длина строки должна быть больше 8 символов и храниться в байте без знака. Напишите цикл для шифрования строки по алгоритму: первый символ циклически сдвигается вправо на 1 бит, второй символ — на 2 бита, …, 7-й — на 7 битов, 8-й — снова на 1 бит, 9-й на 2 бита и т.д. Затем напишите цикл для расшифровки строки и выведите её на экран.

FasmWorld Программирование на ассемблере FASM для начинающих и не только

Циклический сдвиг отличается от линейного тем, что выдвигаемые с одного конца биты вдвигаются с другой стороны, то есть движутся по кольцу. В процессора x86 существует 2 вида циклического сдвига: простой и через флаг переноса (CF). У всех команд, рассматриваемых в этой части учебного курса, по 2 операнда, таких же, как у команд линейного сдвига. Первый операнд — сдвигаемое значение и место для записи результата. Второй операнд — счётчик сдвигов, который может находится в регистре CL или указываться непосредственно.

Простой циклический сдвиг

Циклический сдвиг вправо выполняется командой ROR, а влево — командой ROL. Схема работы этих команд представлена на рисунке (на примере 8-битного операнда):

Значение последнего выдвигаемого бита копируется в флаг CF. Для сдвигов на 1 бит устанавливается флаг OF, если в результате сдвига изменяется знаковый бит операнда. (Честно говоря, не помню, чтобы я этим когда-то пользовался. Но может вам пригодится �� ) Примеры использования команд:

rol bl,1 ;Циклический сдвиг BL на 1 бит влево ror word[si],5 ;Циклический сдвиг слова по адресу в SI на 5 бит вправо rol ax,cl ;Циклический свдиг AX на CL бит влево

Циклический сдвиг через флаг переноса

Отличие от простого циклического сдвига в том, что флаг CF участвует в сдвиге наравне с битами операнда. При сдвиге на 1 бит выдвигаемый бит помещается в CF, а значение CF вдвигается в операнд с другой стороны. При сдвиге на несколько бит эта операция повторяется многократно. Циклический сдвиг через флаг переноса выполняется командами RCR (вправо) и RCL (влево).

Опять же для сдвигов на 1 бит устанавливается флаг OF, если в результате сдвига изменяется знаковый бит операнда. Примеры использования команд:

rcr dh,3 ;Цикл. сдвиг DH на 3 бита вправо через флаг CF rcl byte[bx],cl ;Цикл. сдвиг байта по адресу в BX на CL бит влево через флаг CF rcl dx,1 ;Цикл. сдвиг DX на 1 бит влево через флаг CF

Пример программы

В качестве примера напишем программу для подсчёта единичных битов в байте. Для анализа битов используется команда ROL в цикле. Цикл выполняется 8 раз — по числу битов в байте. Если очередной бит равен 1, то выполняем инкремент счётчика единичных битов.

use16 ;Генерировать 16-битный код org 100h ;Программа начинается с адреса 100h mov al,[x] ;AL = x xor bl,bl ;BL = 0 (Здесь будем считать единичные биты) mov cx,8 ;Инициализация счётчика цикла lp: rol al,1 ;Цилический сдвиг AL на 1 бит влево jnc bit0 ;Переход, если CF=0 inc bl ;Инкремент счетчика единичных битов bit0: loop lp ;Команда цикла mov [n],bl ;Сохраняем результат в n mov ax,4C00h ;\ int 21h ;/ Завершение программы ;———————————————————————- x db 89h ;Байт n db ? ;Количество единичных битов в байте

Эту программу можно немного оптимизировать. Во-первых, использовать вместо условного перехода команду ADC со нулевым вторым операндом. Это позволит прибавить 1, если CF=1 и прибавить 0, если CF=0. Во-вторых, считать единичные биты можно в регистре AH, а обнулить его в начале с помощью команды MOVZX, совместив с загрузкой байта в регистр AL. Ноль для команды ADC можно взять в регистре CH, это делает команду короче и быстрее, чем при использовании непосредственного операнда. CH равен 0 во время выполнения цикла, так как CX изменяется от 8 до 0. Вот что получилось в итоге:

use16 ;Генерировать 16-битный код org 100h ;Программа начинается с адреса 100h movzx ax,[x] ;AL = x, AH = 0 mov cx,8 ;Инициализация счётчика цикла lp: rol al,1 ;Цилический сдвиг AL на 1 бит влево adc ah,ch ;Прибавляем флаг CF к AH, так как CH = 0 loop lp ;Команда цикла mov [n],bl ;Сохраняем результат в n mov ax,4C00h ;\ int 21h ;/ Завершение программы ;———————————————————————- x db 89h ;Байт n db ? ;Количество единичных битов в байте

Всего 6 команд для подсчета битов в байте. А попробуйте написать то же самое на языке высокого уровня ��

Упражнение

Объявите в программе строку. Длина строки должна быть больше 8 символов и храниться в байте без знака. Напишите цикл для шифрования строки по алгоритму: первый символ циклически сдвигается вправо на 1 бит, второй символ — на 2 бита, …, 7-й — на 7 битов, 8-й — снова на 1 бит, 9-й на 2 бита и т.д. Затем напишите цикл для расшифровки строки и выведите её на экран.

Комментарии:

;ура, вроде верно
use16
org 100h

start:
movzx cx,[len]
xor di,di
xor bh,bh
xor bl,bl

lp1:
mov bh,cl ;сохраняем значение cl, чтобы не нарушить цикл
inc bl

cmp bl,8 ;если 8-й символ, то снова двигаем на 1 бит
jnz metka1
mov bl,1

metka1:
mov cl,bl

mov al,byte[text+di]
ror al,cl
mov byte[rezult1+di],al
add di,1

mov cl,bh ;останавливаем счетчик цикла

;решил вывести закодированные символы на экран
xor di,di
movzx cx,[len]
mov ah,02h

lp3:
mov dl,byte[rezult1+di]
int 21h
inc di

;———расшифровка
movzx cx,[len]
xor di,di
xor bh,bh
xor bl,bl

lp2:
mov bh,cl ;сохраняем значение cl, чтобы не нарушить цикл
inc bl

cmp bl,8 ;если 8-й символ, то снова двигаем на 1 бит
jnz metka2
mov bl,1

metka2:
mov cl,bl

mov al,byte[rezult1+di]
rol al,cl
mov byte[rezult2+di],al
add di,1

mov cl,bh ;останавливаем счетчик цикла

mov ah,02h
mov dl,13
int 21h
mov dl,10
int 21h

lp4:
mov dl,byte[rezult2+di]
int 21h
inc di

mov ax,4C00h
int 21h

Ух. Ты молодец, упорный ��
Всё правильно.
Можно немного оптимизировать. Например, заменить

xor bh,bh xor bl,bl

И «add di,1» лучше делать как «inc di»

помогите пожалуйста найти ошибку в программе, надо выполнить деление натуральных чисел не используя команды div, idiv

Я тебе помогу. Такое деление можно написать с помощью вычитаний и сдвигов в цикле. Какой размер чисел для деления? Числа со знаком или без? Можешь в комментарий написать свою программу с ошибкой )

Безбожно тупил, но кажется осилил.

lp_kod:
mov al,[string+di]
mov bx,cx
sub cx,cx
mov cl,dl
ror al,cl
mov [string_2+di],al
cmp cl,7
je sbros
inc dl
lp:
inc di
sub ax,ax
mov cx,bx
loop lp_kod
jmp dekod

sbros:
sub dl,6
jmp lp

dekod:
mov cx,12
mov dl,7
sub di,di

lp_dekod:
mov bx,cx
mov al,[string_2+di]
mov cl,dl
ror al,cl
mov [string_3+di],al
cmp cl,1
je nabros
dec dl
lp2:
inc di
mov cx,bx
loop lp_dekod
jmp vivod

nabros:
add dl,6
jmp lp2

vivod:
sub di,di
mov cx,12
mov ah,02h

lp_vivod:
mov dl,[string_2+di]
int 21h
inc di
loop lp_vivod

sub di,di
mov cx,12
mov ah,02h
mov dl,13
int 21h
mov dl,10
int 21h

lp_vivod_2:
mov dl,[string_3+di]
int 21h
inc di
loop lp_vivod_2

mov ah,08h
int 21h

mov ax,4c00h
int 21h

Как-то даже взрывает мозг �� Особенно сброс и наброс. Что-то здесь не правильно. Отпишусь, когда проверю.

Да, точно, надо кометарии в програму добавить, а то сам забуду чего нагородил))).

Извиняюсь. Долго проверял, но работает всё верно. Хотя конечно можно проще написать тоже самое.
В первом цикле есть код:

sub cx,cx mov cl,dl

Здесь CX можно не обнулять, так как новое значение всё равно затирает старое. Кстати, во втором цикле у тебя нет этой команды.
Чуть ниже:

Тоже обнулять не нужно. Эту команду можно просто убрать.

И гораздо проще можно сделать «сброс» и «наброс». У тебя 3 перехода получается. Можно сделать только один. Например, вот так:

cmp cl,7 jne ne_sbros xor dl,dl ne_sbros: inc dl inc di

Регистры я для себя обнулял, мне так проще в отладчике смотреть какие числа куда идут. А про «сброс», «наброс» — хорошее замечание , спасибо.

Почему в предыдущих примерах резервируется под строковую переменную количество байтов без знака $?

Вот, что у меня получилось:

use16
org 100h
jmp start

arr db ‘Telephone$’
arrz rb 10 ;резервируем под шифровку
arrd rb 10 ;резервируем под дешифровку
l db 10
pak db 13,10,'(de)coding press any key…$’
pak1 db 13,10,’out press any key…$’
start:
mov ah,09h
mov dx,arr
int 21h ;Выводим слово на экран
mov ah,9
mov dx,pak
int 21h
mov ah,8
int 21h ;Предлагаем его (де)шифровать
movzx bx,[l]
mov cx,7
sub bx,cx
mov si,cx
shifr: dec si
mov al,[arr+si]
rol al,cl ;шифруем символы с 7 по 1
mov [arrz+si],al;сохраняем первую часть шифровки
cmp bx,cx
jna men_str ;если в строке меньше 14символов, то начнем шифровать символы >8 попозже
mov ah,[arr+si+7]
rol ah,cl ;шифруем символы с 14 по 8
jmp kon
men_str: jnz con_str ;если это последний символ строки, то записываем $
mov ah,24h
kon: mov [arrz+si+7],ah ;сохраняем вторую часть шифровки

con_str: loop shifr

mov ah,09h
mov dx,arrz
int 21h ;выводим шифровку на экран
mov ah,9
mov dx,pak
int 21h
mov ah,8
int 21h ; предлагаем опять (де)шифровать

mov cx,7
mov si,cx
deshifr: dec si ;дальше тоже самое, но со сдвигом вправо
mov al,[arrz+si]
ror al,cl
mov [arrd+si],al
cmp bx,cx
jna men_str1
mov ah,[arrz+si+7]
ror ah,cl
jmp kon1
men_str1: jnz con_str1
mov ah,24h
kon1: mov [arrd+si+7],ah

con_str1: loop deshifr

mov ah,09h
mov dx,arrd
int 21h ;выводим расшифровку
mov ah,9
mov dx,pak1
int 21h
mov ah,8
int 21h

mov ax,4C00h
int 21h ; happy end

Там строка выводится по одному символу функцией 02h, поэтому конец строки не нужен.

Вообще не понял, как твоя программа работает ��
А что будет, если строка длиннее 14 символов?

Больше 14 символов в строке программа не обработает. Идея была такая — разбить строку по 7символов и обрабатывать символы 1,8;2,9;…. за один цикл, так как они кодируются одинаково. Несложно доработать программу на любую длину строки.

И почему-то русские слова не показываются на экране правильно в моей программе?

Дело в том, что при выводе на консоль используется кодировка cp866 (DOS), а строки в программе в кодировке win1251. Если набрать исходник в каком-нибудь редакторе с поддержкой кодировки дос, то будет выводиться нормально.
Например, можно набрать код в программе FAR.

я буду первый в подкатегории форума «Ассемблер в DOS» �� нуже промедаретируй мой расказ ��

Вот, написал, можно сказать, «в лоб». Два куска кода совершенно одинаковы,
отличие в одной команде. Так и не смог придумать, как избежать повтора ��

use16
org 100h
mov bx,msg ;в bx — адрес строки
xor si,si ;si указывает номер символа
counter_1:
mov cl,1 ;считает 7 символов
code_msg:
mov al,[bx+si] ;символ — в al
cmp al,0dh ;это конец строки ?
je print_msg_code ;да, напечатать
ror al,cl ;кодируем символ
mov [bx+si],al ;заменим символ на закодированный
inc si
inc cl
cmp cl,7 ;7 символов закодировали ?
jna code_msg ;нет еще, продолжим
jmp counter_1 ;да, кодируем след. группу символов
print_msg_code: ;печать закодированной строки
mov ah,09h
mov dx,bx
int 21h
;Декодирую, все то же самое, за исключением
;направления циклического сдвига:
xor si,si
counter_2:
mov cl,1
decode_msg:
mov al,[bx+si]
cmp al,0dh
je print_msg_decode
rol al,cl
mov [bx+si],al
inc si
inc cl
cmp cl,7
jna decode_msg
jmp counter_2
print_msg_decode: ;печать декодированной строки
int 21h
mov ax,4c00h ;выходим
int 21h
;——————————————————————-
msg db ‘Pochemu,esli ya pishu kirilitzey,’
db ‘vivoditsa kakaya-to abrakadabra ?’,0dh,0ah,’$’

Не стал использовать loop, т. к. счетчик уже занят под роры-ролы.

Не так уж и «в лоб» получилось. Хорошая программа и работает правильно со строкой любой длины.
LOOP использовать не обязательно. В этом упражнении проще цикл сделать командами условных переходов.

Про кириллицу я ответил на предыдущий комментарий. Дело в кодировке. Windows использует win1251 и в редакторе FASM тоже эта кодировка. А на консоль выводится в кодировке cp866. Поэтому и получается абракадабра.
Нужно исходник набрать в 866 кодировке и тогда русские буквы будут отображаться. Например, можно файл набрать в FARe.

Спасибо, тогда буду набирать код в FARе.

не обязательно можно в реестре изменить значение кодировки в консольных приложениях …
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Nls\CodePage->OEMCP поменять с 866 на 1251 и не надо прибегать к фар’у ��

как одобрят скину ссылку на форум ��

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

tak nado nastroykah prilojenia nastroit shrift na Lucida Console i sohranit kak postoianuu nastroyku i vse budet ok �� uje provereno godami )))

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

А как можно в качестве параметра передать направление сдвига ?

IgorKing, что-то я не совсем понял твое объяснение, я бы даже сказал, совсем не понял �� В виде кода можешь показать ?

Ну в смысле если значение параметра 1, то будет сдвиг влево, если 2, то вправо.
А в самой процедуре просто сравниваешь параметр с 1, если да сдвиг влево, иначе вправо. Хотя, если честно код не читал поэтому вот просто пример.

cmp [arg],1
jnz Ror
rol al,1
jmp Contine
Ror:
ror al,1
Contine:

А, ну понятно. Но так можно обойтись и без подпрограммы. Просто после шифровки строки пометить какой-нибудь байт в памяти, и потом уже смотреть: байт помечен — ага, ror обходим, используем rol(расшифровываем). Вопрос был немножко в другом, как передать в подпрограмму параметр(направление сдвига), внутри самой же подпрограммы уже не заботиться о нем.

Т.е. без проверки?
Незнаю, идея бредовая, но может быть можно в качестве параметра передавать код операции или что-то вроде…В таких вопросах нам может помочь только наш Сенсей!

Получилось! �� Правда, делал в MASMе (в нем пока поуверенней себя чувствую), вместо подпрограммы использовал макрос.

code_decode macro shft

mov bx,offset msg
xor si,si
counter:
mov cl,1
code_decode:
mov al,[bx+si]
cmp al,0dh
je print_msg
shft al,cl
mov [bx+si],al
inc si
inc cl
cmp cl,7
jna code_decode
jmp counter
print_msg:
mov ah,09h
mov dx,bx
int 21h
endm

start:
code_decode ror
code_decode rol

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

А с процедурой код получился бы меньше ��

На FASMe тоже можно написать аналогичный макрос (см. Часть 28)

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

Но это уже извращение получается ��

Нет уж, на сегодня с меня хватит извращений, голова уже пухнет.
Спасибо, xrnd и IgorKing за толковые ответы, буду двигаться дальше ��

В общем, правильно. Но я бы сделал 0 или не ноль.
Параметр можно передать через регистр или через стек.
Например, у тебя не используется регистр DI, можно в него поместить значение, которое будет определять направление сдвига.

Как производится циклический арифметический сдвиг?

Не очень понятно, что имеется в виду. Я о таком не слышал.
При арифметическом сдвиге знаковый бит сохраняется, поэтому неясно, как он может быть ещё и циклическим? ��

Может быть, циклически сдвигаются все биты кроме старшего?
Тогда это можно сделать с помощью сдвига и логических операций:

mov al,10001111b ;AL = 10001111b sar al,1 ;Арифметический сдвиг, CF=выдвинутый бит jc set_bit and al,10111111b ;Сбросить 6-й бит jmp continue set_bit: or al,01000000b ;Установить 6-й бит continue: ;Результат AL = 11000111b

Чучуть с циклом перепутал.
Он 1-7
А у меня 1-7 ,+8элемент сдвинуть на 1 , -это как 1 цикл.
Поэтому вышил такой страный
После 9 символа нет действий , так как если цикл , не зная конца строки , просто смешение задать.
А если с конец строки , то там длинно слишком выходит

Замороченно как-то получилось.
Проще всё-таки начинать с первого символа строки. Можно сделать цикл командами перехода, а счетчик держать не в CX.

body:
mov al,byte[wor_d+di]
ror al,cl
mov byte[code_w+di],al
inc di
inc cl
cmp di,8
je cl2one
cmp di,12
je output
jmp body

cl2one:
mov cl,1
jmp body
output:
xor di,di
xor ax,ax

oloop:
mov dl,byte[wor_d+di]
mov ah,02h
int 21h
inc di
cmp di,12
jne oloop

mov dl,10
mov ah,02h
int 21h
mov dl,13
mov ah,02h
int 21h
xor di,di
oloop2:
mov dl,byte[code_w+di]
mov ah,02h
int 21h
inc di
cmp di,12
jne oloop2

mov ah,08h
int 21h
mov ax,4c00h
int 21h

Шифрование написано хорошо, но нет расшифровки строки.

я в бешенстве xrnd помоги пожалуйста почему не работает такой код:
mov [deStr+di], dl .

use16
org 100h
jmp enCode

enStr db «HeL1o asmworld and fasm!», 0
deStr db 32 dup (‘$’)
max db 7

enCode:
mov si, enStr ; адресс строки
xor di, di ; общий счетчик символов
en_loop:
xor cx, cx
mov ch, [max] ; ch = счетчик
_encode:
inc cl ; cl = для сдвига
mov dl, [si]
cmp dl, 0 ; конец строки
je Exit_encode ; подобие break
ror dl, cl
inc si
mov [deStr+di], dl ; ОШИБКА не записывает
inc di ; общий счетчик символов
dec ch
jnz _encode
jmp en_loop

; вывод закодированой строки
mov ah, 2
mov si, deStr-1

pr_deStr:
inc si
mov dl, [si]
cmp dl, ‘$’
jz Exit
int 21h
jmp pr_deStr

Exit:
mov ah, 8
int 21h

mov ax, 4c00h
int 21h

программа еще не закончена … ;((

;))) оказывается я написал баг программу )) ‘H’ ror 1 == ‘$’ )))
поэтому на выводе строки — первый же символ заканчивал мою программу )) , блин придется менять

Ура почти все работает. �� не могу тока понять почему не выводится строка
0ah, 0dh, ‘Decoded: $’ а так все работает как швейцарские часы, можно даже зашифрованые сообщения в пентагон посылать )))

use16
org 100h
jmp enCode

source db «HeL1o asmworld and fasm!», 0
enStr db 32 dup (0)
deStr db 32 dup (0)
max db 7
str1 db «Encoded: $»
str2 db 0dh, 0ah, «Decoded: $»

mov si, source ; адресс строки
xor di, di ; общий счетчик символов
en_loop:
xor cx, cx
mov ch, [max] ; ch = счетчик
_encode:
inc cl ; cl = для сдвига
mov dl, [si]
cmp dl, 0 ; конец строки
je enCode_print ; подобие break
ror dl, cl
inc si
mov [enStr+di], dl
inc di ; общий счетчик символов
dec ch
jnz _encode
jmp en_loop

mov si, enStr ; адресс закодированой строки
xor di, di ; общий счетчик символов
de_loop:
xor cx, cx
mov ch, [max] ; ch = счетчик
_decode:
inc cl
mov dl, [si]
cmp dl, 0
jz deCode_print ; вывод декодированой строки
rol dl, cl
mov [deStr+di], dl
inc si
inc di
dec ch
jnz _decode
jmp de_loop

;—[вывод закодированой строки]——
enCode_print: ;

mov ah, 9
mov dx, str1
int 21h
mov ah, 2
mov si, enStr-1

lp_enStr:
inc si
mov dl, [si]
cmp dl, 0
jz deCode ; декодируем и выводим оригинал
int 21h
jmp lp_enStr

mov ah, 9
mov dx, str2
int 21
mov ah, 2
mov si, deStr-1

lp_deStr:
inc si
mov dl, [si]
cmp dl, 0
jz Exit
int 21h
jmp lp_deStr

Exit:
mov ah, 8
int 21h
mov ax, 4c00h
int 21h

mov dx, str2 int 21

Должно быть 21h, иначе это другое прерывание.

;шифровка строки по алгоритму циклического сдвига каждого байта
;строки на количество битов равное индексу кодируемого байта
;с основанием 7, т.е. 1-7,1-7.
use16
org 100h
jmp main
;=================================;
strDecode db ‘Пример зашифровываемой строки’,0
strBR db 10,13,0
;=================================;

;=================================;
main:
mov bx,strDecode ;
mov ax,bx ;
call proc_strOut ;
mov bx,strBR ;
call proc_strOut ;выводим начальную строку
mov bx,ax
xor ax,ax ;
call proc_strDE ;шифруем строку, выводим
mov ah,1 ;
call proc_strDE ;дешифруем строку, выводим
;———————————;
exit:
mov ax,4c00h
int 21h
;=================================;

;=================================;
;кодируем/декодируем строку
;bx — указатель на строку
;ah — флаг работы подпрограммы (0 — шифруем, 1 — дешифруем)
proc_strDE:
xor cx,cx
mov si,cx
get_char:
inc cl
js slide_more7 ;если счетчик сдвига равен 8
slide:
mov al,[bx+si] ;получаем текущий символ
cmp al,0
je display_string ;если конец строки, выводим на экран
cmp ah,0 ;проверяем флаг работы подпрограммы
jne decode
ror al,cl ;шифруем, если ah=0
jmp short end_de_char
decode:
rol al,cl ;дешифруем, если ah0
end_de_char:
mov [bx+si],al
inc si
jmp short get_char
slide_more7:
mov cx,1 ;начинаем счетчик сдвига сначала
jmp short slide
display_string:
mov ax,bx ;
call proc_strOut ;
mov bx,strBR ;
call proc_strOut ;отображаем строку
mov bx,ax
proc_strDE_end:
ret
;=================================;

;=================================;
;посимвольный вывод кириллической строки на экран (0 — завершающий символ)
;bx — указатель на строку
proc_strOut:
push ax
push dx
push si
mov si,-1
proc_strOut_getChar:
inc si mov al,[bx+si]
cmp al,0xf0
jb proc_strOut_charLessF0
and al,0xef
jmp short proc_strOut_displayChar
proc_strOut_charLessF0:
cmp al,0xef
ja proc_strOut_displayChar
cmp al,0xc0
jnb proc_strOut_charC0EF
cmp al,0
je proc_strOut_end
jmp short proc_strOut_displayChar
proc_strOut_charC0EF:
and al,0xbf
proc_strOut_displayChar:
mov ah,02h
mov dl,al
int 21h
jmp short proc_strOut_getChar
proc_strOut_end:
pop si
pop dx
pop ax
ret
;=================================;

В принципе всё хорошо.
Вроде есть одна ошибка:

inc cl js slide_more7 ;если счетчик сдвига равен 8

Переход по флагу SF будет выполнен, если старший бит равен 1, то есть CL=128 и больше.

Очень интересная процедура proc_strOut. Я так понял, она преобразует кодировку?
Есть более простой способ — сделать табличку из 256 байт и заменять байты по этой таблице (существует даже специальная команда XLAT)

Да, надо вместо js вставить cmp cl,8 и jb.
Опять тетраду с байтом перепутал ��
а xlat моя пока не знать ��

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

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