Лаб10. Макросы. Числовые массивы
Цель работы: ознакомление с принципами написания макросов и их использования в программах, а также с технологиями обработки числовых массивов.
Теоретическая часть
Макроопределения
Одними из самых мощных языковых средств Ассемблера являются макроопределения. Макроопределением (или макросом) называется набор команд, которому присвоено имя и который вставляется в программу всякий раз, когда Ассемблер встречает это имя в тексте программы. Макрос начинается директивой MACRO и заканчивается ENDM. Например, пусть описано макроопределение PUTSPACE, выводящее символ пробела на экран:
PUTSPACE macro mov ah,2 mov dl,’ ’ int 21h endm |
Теперь в программе можно использовать слово PUTSPACE как имя команды, и Ассемблер заменит каждое такое слово на макрорасширение, на три команды, содержащиеся в макроопределении. Макроопределения могут вызываться с параметрами, следовательно, в зависимости от ситуации, включаемый код будет генерироваться с различными параметрами, например, как макрос обмена значений в памяти:
CHANGE_VAR macro var1, var2 push var1 push var2 pop var1 pop var2 endm |
Теперь можно использовать макрос CHANGE_VAR для того, чтобы поменять значения двух областей памяти местами.
Следующее важное средство, использующееся в макроопределениях, –это директивы условного ассемблирования. Например: напишем макрос, выполняющий умножение регистра AL на число, причем, если множитель – степень двойки, то умножение будет выполняться более быстрой командой сдвига влево.
fast_mul macro number if number eq 2 shl ax, 1 ;умножение на 2 elseif number eq 4 shl ax, 2 ;умножение на 4 elseif number eq 8 shl ax, 3 ;умножение на 8 ... elseif number eq 128 shl ax, 7 ;умножение на 128 else mov dx, number ;умножение на число, не являющееся mul dx ;степенью двойки. endif endm |
Можно, конечно, усложнить этот макрос, применяя особые свойства команды LEA и ее комбинации, сдвиги и сложения, однако в таком виде он чрезмерно громоздкий. Проблема решается с помощью третьего средства, постоянно использующегося в макросах, – блоков повторений.
fast_mul_1 macro number i=1 while i le number and i ne 0 ;пока i меньше или равно number ;и не равно 0 if number eq ;если i = number rept number shl ax,1 ;умножаем ax на 2number логическим сдвигом ;на единицу number раз endm i=0 ;если умножение уже сделано, то i = 0 else ;если i ? number, i=i*2 ;получаем следующую степень числа 2 endif endm if i ne 0 mov dx, number ;умножение на число, не являющееся mul dx ;степенью двойки. endif endm |
Числовые массивы
Массив – структурированный тип данных, состоящий из некоторого числа элементов одного типа.
Описание и инициализация массива в программе:
1) Можно перечислить элементы массива в поле операндов. При перечислении элементы разделяются запятыми: ;массив из 5 элементов. Размер каждого элемента 2 байта mas1 dw 1, 2, 3, 4, 5 2) Можно использовать оператор повторения DUP: ;массивы из 5 элементов. Размер каждого элемента 2 байта mas2 dw 5 dup (?) mas3 dw 5 dup (0) 3) Инициализировать элементы массива можно и в цикле: mov cx, 5 ;значение счетчика циклов в cx mov si, 0 ;индекс начального элемента в si mov ax, 1 ;значение первого элемента массива go: ;метка начала цикла mov mas2[si], ax ;запись в массив inc si ;продвижение к следующему элементу массива inc si ;или add si,2 ,т.к. данные размером в слово inc ax ;инкремент ax loop go ;повторить цикл |
Доступ к элементам массива
Все элементы массива располагаются в памяти компьютера последовательно. Для того чтобы локализовать определенный элемент массива, к его имени нужно добавить индекс. В языке Ассемблер индексы массивов – это обычные адреса. Нумерация элементов массива начинается с нуля.
Например, пусть дан массив
mas dw 5, 3, 7, 1, 2
Считываем третий элемент – число 7 в регистр dx. Этот элемент отстоит от начала массива на 4 байта. Поэтому, чтобы обратиться к этому элементу, к базовому адресу необходимо добавить смещение 4.
mov si, 4
mov dx, mas[si]
В общем случае для получения адреса элемента в массиве необходимо начальный (базовый) адрес массива сложить с произведением индекса этого элемента на размер элемента массива:
база + (индекс * размер_элемента)
Возможность масштабирования появилась в процессорах Intel, начиная с модели i486. С учетом этих возможностей обращение к элементу массива может быть представлено следующим образом:
mov si, 2
mov dx, mas[si*2]
Масштабирование эффективно лишь тогда, когда размерность элементов массива равна 2, 4 или 8. Если же размерность элементов другая, то обращение к элементам массива нужно организовывать обычным способом, описанным ранее.
Типовые операции с массивами
Рассмотрим пример, в котором просматривается массив, состоящий из элементов типа word. Выполняется сравнение этих элементов с нулем и выводится соответствующее сообщение.
Примеры
.model small puts macro s ;вывод строки на экран mov ah, 9 lea dx, s int 21h endm putch macro c ;вывод символа на экран mov ah, 2 mov dl, c int 21h endm prolog macro ;связка ds с сегментом данных mov ax, @data mov ds, ax endm epilog macro ;стандартный выход mov ah, 4ch int 21h endm .stack 64 .data ;начало сегмента данных ;тексты сообщений mes1 db “Не равен 0!”, 0ah, 0dh, “$” mes2 db “Равен 0!”, 0ah, 0dh, “$” mes3 db 0ah, 0dh, “Элемент $” mas dw 5, 3, 0, 7, 1, 0, 0, 2 ;исходный массив .code .486 ;директива, позволяющая использовать ;32–разрядные регистры и масштабирование ;при обращении к элементам массива main: prolog xor ax, ax ;обнуление ax prepare: mov cx, 8 ;в cx – количество элементов массива, ;которые служат счетчиком циклов mov esi, 0 ;индекс в esi compare: puts mes3 ;вывод сообщения на экран mov dx, si or dl, 30h ;преобразование цифры в символ putch dl ;вывод значения индекса mov dx, mas[esi*2] ;помещаем элемент массива в dx cmp dx, 0 ;сравнение dx с 0 je equal ;переход, если равно not_equal: puts mes1 ;вывод сообщения на экран jmp next equal: puts mes2 ;вывод сообщения на экран next: inc esi ;на следующий элемент dec cx ;условие для выхода из цикла jcxz exit ;cx = 0? если да – на выход jmp compare ;нет – повторить цикл exit: epilog end main |
Задания
- Подсчитать количество положительных и количество отрицательных элементов в заданном числовом векторе размера m.
- Дана числовая матрица размера mх Создать вектор размера m, содержащий минимальные элементы каждой строки матрицы.
- Дана числовая матрица размера mх Создать вектор размера m, содержащий максимальные элементы каждого столбца матрицы.
- Дана числовая матрица размера nхn. Определить минимальные элементы в каждой строке матрицы, записать их в первый столбец, а элементы первого столбца – на место соответствующих минимальных.
- Дана числовая матрица размера nхn. Определить максимальные элементы в каждом столбце матрицы, записать их в первую строку, а элементы первой строки – на место соответствующих максимальных.
- Дан числовой вектор размера m. Подсчитать, сколько раз встречается в нем максимальный элемент.
- Дана матрица размера nхn. Определить такие k, где k–я строка матрицы совпадает с k–м столбцом.
- Дана целочисленная матрица размера nхn. Определить столбец, содержащий только такие элементы, которые по модулю не больше наперед заданного числа a.
- Дан числовой вектор размера m. Найти минимальный из положительных и максимальный из отрицательных элементов.
- Дана матрица a размера mхn. Построить вектор b, элементы которого вычисляются по формуле: bi=ai,0+ai,n-1, где i=0,1,…,m-1.
- Дана матрица a размера mхn. Построить вектор b, элементы которого вычисляются по формуле: bj=a0,j+am-1,j, где j=0,1,…,n-1.
- Дана числовая матрица размера nхn. Построить вектор, элементы которого являются суммами элементов каждой строки матрицы.
- Дана числовая матрица размера nхn. Построить вектор, элементы которого являются суммами элементов каждого столбца матрицы.
- Дана числовая матрица a размера nхn. Получить матрицу b размера nхn, элементы которой определяются по правилам:
- Даны числовой вектор из n элементов и числовая матрица размера mхn. Умножить матрицу на вектор.
- Перемножить две данные числовые матрицы размера nхn.
Лаб9. Обработка одномерных символьных массивов
Цель работы: ознакомление с основными командами обработки символьных массивов.
Теоретическая часть
Все команды работы со строками (символьными массивами) могут оперировать со строками до 64 Kбайт. Команде может предшествовать префикс, который задает повторное выполнение команды заданное число раз, что облегчает обработку строк.
Все команды обработки строк имеют некоторые общие особенности. Строка (массив) может выступать в качестве операнда–источника, операнда–приемника или одновременно и источником, и приемником. По умолчанию, строка–источник располагается в сегменте данных, обычно адресуемом регистром DS, регистр SI (индекс источника) используется для адресации смещения в строке–источнике.
Строка–приемник располагается в дополнительном сегменте данных, обычно адресуемом регистром ES, регистр DI (индекс приемника) используется для адресации смещения в строке–приемнике. Регистры SI и DI должны быть инициализированы адресами массивов источника и приемника. Обрабатываемые строки могут содержать элементы стандартных типов (байт, слово или двойное слово).
Для строк можно явно переопределять сегмент с помощью префикса, например, DS:ARR1, DS:ARR2, а это означает, что обе строки находятся в одном сегменте. Если же загрузить регистры DS и ES одним значением, то обе строки смогут находиться в одном сегменте.
При выполнении любой из команд обработки строк значения регистров SI и DI изменяются, указывая на следующий элемент строки, требующий обработки. Шаг изменения зависит от значения флажка DF. Значение DF = 0 приводит к инкрементации регистров SI и DI, при DF = 1 – к декрементации. Если размерность элементов массива равна одному байту, значения регистров SI и DI изменяются после каждой операции на 1, при размерности WORD – на 2.
При использовании префикса повторения команды, количество повторений хранится в регистре CX. После каждой итерации значение регистра CX будет уменьшаться на единицу. Если CX = 1, команда выполнится только один раз и управление будет передано следующей команде.
Префиксы повторений REP, REPE, REPZ, REPNE и REPNZ
Префикс REP(repeat) используется с командами MOVS и STOS и интерпретируется как «повторять до конца строки» (пока CX отлично от 0).
Префиксы REPE и REPZ (repeat while equal, repeat while zero) идентичны и используются с командами CMPS и SCAS. В случае использования данных префиксов команда будет повторяться до тех пор, пока флажок ZF =1 и значение регистра CX отлично от 0. Префиксы REPNE и REPNZ (repeat while not equal, repeat while not zero) повторяют команду, пока ZF = 0 и CX отлично от 0.
Команда CLD
Команда CLD (CLear Direction flag) устанавливает флажок направления (DF) в 0 для автоматического увеличения значений регистров SI и DI (просмотр массива слева направо). Команда CLD других флажков не меняет.
Синтаксис: CLD
Команда STD
Команда STD (SeT Direction flag) устанавливает флажок направления (DF) в 1 для автоматического уменьшения значений регистров SI и DI (просмотр массива справа налево). Команда STD других флажков не меняет.
Синтаксис: STD
Команда MOVS
Команда MOVS (MOVe String) копирует байт, слово или двойное слово из строки–источника, адресованной регистром SI в строку–приемник, адресованную регистром DI. После копирования элемента значения регистров–указателей SI и DI автоматически сдвигаются на следующий элемент в зависимости от значения флажка направления DF. С префиксом повторения команда MOVS может быть использована для копирования блоков памяти.
Синтаксис: MOVs [<строка_приемник>, <строка_источник>]
MOVSB ;пересылка байта
MOVSW ;пересылка слова
MOVSD ;пересылка двойного слова
В команде MOVS операнды указывают только на тип элементов, а не на сами обрабатываемые элементы. Типы операндов можно задавать явно, используя команды MOVSB, MOVSW и MOVSD, сами операнды указывать необязательно. Команды работают, соответственно, с массивами, типы которых определяются последним символом команды: B – BYTE, W – WORD, D – DWORD.
Команда CMPS
Команда CMPS (CoMPare String) сравнивает один элемент из строки–источника, адресованный регистром SI, с элементом строки–приемника, адресованным регистром DI. После сравнения элементов признак результата сохраняется во флажковом регистре и значения указателей SI и DI автоматически сдвигаются на следующий элемент в зависимости от значения флажка направления DF.
В сочетании с префиксом условного повторения данную команду можно использовать для поиска элемента, соответствующего заданному условию. Тип элемента соответствует типам операндов.
Синтаксис: CMPS [<строка_приемник>, <строка_источник>]
cmpsb ;сравнение байта
cmpsw ;сравнение слова
cmpsd ;сравнение двойного слова
Команда SCAS
Команда SCAS (SCAn String) сравнивает элемент строки–приемника, адресованный регистром DI, с содержимым регистра al/ax/eax, изменяет содержимое регистра DI, формирует признак результата и сохраняет его во флажковом регистре (флажки AF, CF, OF, PF, SF, ZF). Команда SCAS может быть использована с условным префиксом повторения для сравнения символов строки с элементом, хранящимся в регистре.
Сравнение будет производиться до тех пор, пока не будет найдено совпадение (REPNE) или несовпадение (REPE). В случае, когда условие ни разу не выполняется, цикл прекратится после заданного в CX количества раз.
Синтаксис: SCAS [< строка_приемник >]
SCASB ;сканирование байта
SCASW ;сканирование слова
SCASD ;сканирование двойного слова
Команда LODS
Команда LODS (LOaD String) копирует один элемент (байт, слово или двойное слово) из строки–источника в регистр al/ax/eax и изменяет содержимое регистра SI для возможности копирования следующих элементов строки. Команда LODS может быть использована с префиксом повторения, но обычно префикс не используется, так как запомнится только результат последнего копирования.
Синтаксис: LODS [<строка_источник>]
LODSB ;загрузка байта
lodsw ; загрузка слова
lodsd ; загрузка двойного слова
Команда STOS
Команда STOS (STOre String) копирует байт, слово или двойное слово из регистра al/ax/eax в строку–приемник, адресованную регистром DI. После выполнения команды регистр DI обновляется, указывая на следующий символ строки. Если перед командой использовать префикс безусловного повторения, можно всю строку инициализировать одним и тем же значением, например – заполнить строку символами пробела или числами 0.
Синтаксис: STOS [< строка_приемник>]
STOSB ;запись байта
STOSW ;запись слова
STOSD ;запись двойного слова
Пример
С клавиатуры вводятся 2 строки. Вывести сообщение “Yes”, если строки совпадают, и “No” в противном случае.
.data str1 db 100 dup (?) ;буфер ввода1 str2 db 100 dup ('$') ;буфер ввода2 len1 dw ? len2 dw ? .code mov ax, @data ;инициализация регистров ds и es mov ds, ax ;регистры ds и es инициализированы одним и тем же mov es, ax ;зачением, т.к. строки str1 и str1 находятся ;в одном и том же сегменте ... ;ввод 2–х строк ;длина введенных строк сохраняется в len1 и len2 ... lea di, str2 ;в di – адрес строки–приемника lea si, str1 ;в si – строки–источника cld ;задаем направление просмотра ;строки слева направо mov cx, len1 ;cx – количество повторений для repe cmp cx, len2 ;сравниваем длины строк jne NO ;если длины строк не совпадают, идем на NO repe cmpsb ;сравниваем строки jne NO ;если строки не совпадают, переход на NO ;вывод сообщения “Yes” ... jmp finish NO: ... ;вывод сообщения “No” finish: ... |
Задания
- Составьте программу (вариант A) на языке Ассемблер, которая читает строку символов с клавиатуры и обрабатывает ее, используя команды работы со строками.
- Составьте программу (вариант B), используя команды сравнения, перехода и команды организации циклов.
- Заменить в строке все заглавные буквы ‘A’ на ‘а’.
- Определить, является ли введенная строка целым положительным числом.
- Найти первый символ ‘*’ в строке и заменить его на символ ‘#’.
- Найти последний символ ‘?’ в строке и заменить его на символ ‘!’.
- Подсчитать количество слов в предложении (разделитель между словами – символ пробела).
- Удалить в строке повторяющиеся пробелы.
- Найти в строке все слова длиннее заданного количества символов (разделитель между словами – символ пробела).
- Найти в строке положение последней запятой и поменять ее на первый непробельный символ строки.
- Для всех слов в строке, заканчивающихся на “ing”, заменить окончания на “ed” (разделитель между словами – символ пробела).
- Заменить в строке все символы ‘–’, предшествующие первому символу ‘+’, на символ ‘*’.
- Определить, сколько раз каждая буква встречается в заданном предложении.
- Заменить в строке каждую точку ‘.’ на многоточие “…”.
- Найти в строке все слова, в которых буква ‘a’ встречается более двух раз (разделитель между словами – символ пробела).
- Найти в строке все слова, состоящие не более чем из 6 символов (разделитель между словами – символ пробела).
- Определить, расположены ли слова предложения в алфавитном порядке (разделитель между словами – символ пробела).
- Определить, начинается ли каждое следующее слово в предложении с последней буквы предыдущего слова (разделитель между словами – символ пробела).
Лаб8. Циклы. Процедуры
Цель работы: изучение операторов цикла, приобретение навыков использования команд циклов при написании ассемблерных программ; организация и вызов процедур, передача параметров в процедуры и возврат результатов из процедур.
Теоретическая часть
Команды циклов
Циклы организуются для многократного повторения одной или нескольких команд программы или процедуры. Цикл можно организовать, используя команды условного и безусловного переходов, рассмотренных в лабораторной работе 6, а можно с помощью специальных команд. Формат команд представлен в таблице.
ТАБЛИЦА ФОРМАТОВ КОМАНД ЦИКЛА
Название команды | Мнемоника и формат команды | Проверяемое условие цикла |
Повторять цикл | LOOP label | (ECX/ CX) = 0 |
Повторять цикл, пока ноль или равно | LOOPZ/ LOOPE label | (ECX/ CX) = 0 или ZF = 0 |
Повторять цикл, пока не ноль или не равно | LOOPNZ/ LOOPNE label | (ECX/ CX) = 0 или ZF = 1 |
Переход по CX | JCXZ label | (ECX/ CX) = 0 |
Команда LOOP и ее расширения позволяют организовывать циклы, подобные циклам в языках высокого уровня. Регистр ECX/CX служит счетчиком максимального числа повторений цикла, его загружают перед началом цикла. Все команды, за исключением команды JCXZ, которая не изменяет ECX/CX, вычитают единицу из ECX/CX перед повторением цикла. В случае, когда условие цикла удовлетворяется, управление передается на метку label, в противном случае управление переходит на следующую за LOOP команду. Флажки этими командами не модифицируются. Метка label должна находиться в диапазоне от -128 до 127 байтов от команды, следующей за командой цикла.
Примеры.
;складываем числа от 1 до 12 mov bx, 0 mov ax, 0 mov cx, 12 ;количество повторений цикла a1: inc bx add ax, bx ;складываем числа loop a1 ;повторять, пока cx не станет равен 0 ;тот же самый пример, но с использованием jcxz ;складываем числа от 1 до 12 mov bx, 0 mov ax, 0 mov cx, 12 ;количество повторений цикла a1:jcxz a2 ;выход из цикла, если cx равен 0 inc bx add ax, bx ;складываем числа dec cx jmp a1 ;переход на начало цикла a2: ... ;ищем первую букву ’а’ в строке, адресуемой регистром bx ;длина строки 120 байтов mov cx, 120 ;количество повторений цикла m1: inc bx cmp byte ptr[bx],’а’;ищем первую букву ’а’ loopne m1 ;повторяем цикл 120 раз или пока не найдем ’а’ ;проверяем, закончилась ли строка или найдена ’а’ jz naidena ... ;строка закончилась, буква ’а’ не найдена naidena:... ;адрес ’а’ в bx |
Обращение к процедурам
Процедура (подпрограмма) – это группа команд для решения конкретной подзадачи, получающая управление из точки вызова задачи более высокого уровня и возвращающая управление в эту точку.
Описание процедуры состоит из заголовка, тела и конца процедуры:
имя_процедуры PROC [расстояние] ;заголовок процедуры
;команды, директивы Ассемблера – тело процедуры
ret
имя_процедуры ENDP ;конец процедуры
Имя процедуры – это идентификатор (символический адрес), по которому происходит обращение к процедуре. Необязательный параметр расстояние характеризует возможность обращения к процедуре из другого сегмента кода. Этот параметр может принимать значения: NEAR – для процедур ближнего вызова (описание процедуры находится в том же самом сегменте кода, что и основная программа) и FAR – для процедур дальнего вызова (описание процедуры находится в другом сегменте кода). По умолчанию этот параметр равен NEAR.
Процедура может размещаться в любом месте программы, но так, чтобы на нее случайным образом не попало управление.
Процедуру можно разместить:
1) в начале программы (до первой исполняемой команды);
c_s segment assume cs:c_s pr1 proc near ... ret p1 endp begin: ;начало программы ... end begin |
2) в конце программы (после команды корректного завершения работы и возвращения управления операционной системе);
c_s segment assume cs:c_s begin: ... mov ah, 4ch int 21h ;корректное завершение работы ;и передача управления ОС ;ret ;можно использовать команду ret p1 proc near ... ret p1 endp c_s ends end begin |
3) внутри тела программы или другой процедуры (должен быть предусмотрен обход процедуры с помощью оператора jmp);
c_s segment assume cs:c_s begin: ... jmp m1 p1 proc near ... ret p1 endp m1: ... mov ah, 4ch int 21h ;корректное завершение работы ;и передача управления ОС c_s ends end begin |
4) в другом модуле.
Команда CALL осуществляет вызов процедуры, сохраняя предварительно в стеке адрес возврата – адрес команды, следующей после команды CALL. При выполнении команды CALL осуществляется передача управления по адресу с символическим именем имя_процедуры.
Формат команды CALL: CALL [модификатор] имя_процедуры
Модификатор может принимать значения NEAR или FAR, для обращения к процедурам ближнего или дальнего вызовов, соответственно.
Команда RET считывает из стека адрес возврата и загружает его в регистр IP/EIP или в пару регистров CS и IP/EIP, возвращая тем самым управление на команду, следующую в программе за командой CALL. Для процедур ближнего вызова адрес возврата полностью определяется содержимым регистра IP/EIP, а для процедур дальнего вызова – содержимым двух регистров: CS и IP/EIP. При этом команда CALL запоминает в стеке сначала значение регистра CS, а затем значение регистра IP/EIP.
Параметры в процедуру можно передавать через регистры, стек или через общую область памяти. Возвращать параметры можно аналогичным образом.
Примеры
;1. Фрагмент программы для вычисления в цикле выражения ;y = 5+6+7+8+9+10+16 mov cx, 6 ;количество циклов (10-5+1) mov ax, 0 ;накопитель результата mov bx, 5 ;первоначальное значение i=5 m: add ax, bx ;ax=5+6+… inc bx ;инкрементируем i loop m ;если цикл не завершен, возвращаемся на m add ax, 16 ;прибавляем к сумме 16 ;2. Программа и процедуры вывода сообщений dseg segment para public ’data’ m1 db ’message1’,10,13,’$’ m2 db ’message2’,10,13,’$’ contor1 dw 0 contor2 dw 1 dseg ends cseg segment para public ’code’ assume cs:cseg, ds:dseg main_pr proc ;основная программа ... lea dx, m1 ;параметр для процедуры print в dx call print lea dx, m2 ;параметр для процедуры print в dх call print ... mov ax,0 push ax ;параметр для incnum в стеке call incnum pop ax ;получение результата ... mov cx,offset contor1 push cx ;параметр для addnum в стеке call addnum mov cx,offset contor2 push cx ;параметр для addnum в стеке call addnum ret main_pr endp print proc near ;процедура вывода сообщений mov ah, 9 int 21h ret print endp incnum proc near ;процедура увеличения значения на 3 pop cx ;извлечение параметра из стека в сх add cx,3 push cx ret incnum endp ;процедура увеличения значения в памяти на 1 ;параметр передается через стек ;результат записывается в память в вызывающей программе addnum proc near pop bx ;извлечение адреса памяти из стека в bх mov ax,[bx] add ax,1 mov [bx],ax ;запись результата в память ret addnum endp cseg ends end main_pr end |
Задания
Написать программу на языке Ассемблер, которая использует процедуры для вычисления следующего выражения:
7. Команды целочисленной арифметики
Цель работы: изучение арифметических команд Ассемблера, принципов их действия и применения в программе.
Теоретическая часть
Команды целочисленной двоичной арифметики
Команда ADD
Команда суммирует два операнда, результат сохраняет в операнде назначение.
Синтаксис: ADD <назначение>, <источник>
Примеры:
add ax, [bx] ;элемент, хранящийся по адресу, указанному ;в регистре BX, сложить с содержимым регистра AX, ;результат сохранить в регистре AX. add ax, 10 ;к содержимому регистра AX прибавить 10, ;результат сохранить в регистре AX. add bh, LM ;к содержимому регистра BH прибавить значение ;переменной LM, результат сохранить в регистре BH. |
Команда INC
Команда увеличивает значение операнда на 1.
Синтаксис: INC <назначение>
Примеры:
inc CX ;увеличить значение регистра CX на 1 inc [BX] ;значение, хранящееся по адресу, ;указанному в регистре BX, увеличить на 1 inc LM ;значение переменной LM увеличить на 1. |
Команда SUB
Команда из операнда назначение вычитает операнд источник. Результат сохраняется в операнде назначение.
Синтаксис: SUB <назначение>, <источник>
Примеры:
sub ax, [bx] ;значение, хранящееся по адресу, указанному ;в регистре BX, вычитается из регистра AX и ;результат сохраняется в регистре AX sub ax, 10 ;из содержимого регистра AX вычитается число 10, ;результат сохраняется в регистре AX. sub bh, LM ;содержимое переменной LM вычитается из ;регистра DH, результат сохраняется в регистре BH. |
Команда DEC
Команда уменьшает значение операнда на 1.
Синтаксис: DEC <назначение>
Примеры:
dec cx ;значение регистра CX уменьшается на единицу dec [bx] ;значение, хранящееся по адресу, указанному в ;регистре BX, уменьшается на единицу dec LM ;значение переменной LM уменьшается на 1 |
Команда NEG
Действие команды: операнд назначение вычитается из 0, и результат помещается в назначение.
Синтаксис: NEG <назначение>
Примеры:
neg CX ;изменить знак значения в регистре CX neg [BX] ;изменить знак значения, хранящегося по адресу, ;указанному в регистре BX neg LM ;изменить знак переменной LM |
Команда изменения знака neg не изменит значения операнда, если число находится на границе допустимого диапазона значений. Например, последовательность команд
mov AL, -128
neg AL
оставит без изменения значение регистра AL, так как 128 и -128 имеют одинаковое внутреннее представление (80H).
Команда MUL
Команда предназначена для беззнакового умножения. Операнд–источник умножается на содержимое регистра–накопителя. Если операндом–источником является величина типа BYTE, то источник умножается на содержимое регистра AL и результат сохраняется в регистре AX. Если операндом–источником является величина типа WORD, то источник умножается на содержимое регистра AX и результат помещается в пару регистров DX:AX
Синтаксис: MUL <источник>
Примеры:
mul CL ;CL умножается на AL и результат сохраняется ;в регистре AX mul word ptr [BX] ;значение, хранящееся по адресу, указанному ;в регистре BX, умножается на AX, результат ;помещается в пару регистров DX:AX mul LM ;действие команды зависит от размера памяти, ;адресуемой LM |
Команда IMUL
Команды MUL и IMUL похожи. Разница в том, что команда IMUL при умножении учитывает знаки операндов.
Синтаксис: IMUL <источник>
Рассмотрим команды деления. В командах деления предполагается, что делимое занимает в два раза больше памяти, чем делитель. Результат представляет собой пару значений: частное и остаток, а каждая из полученных величин занимает столько же места, что и делитель.
Команда DIV
Команда осуществляет беззнаковое деление. Регистр–накопитель делится на операнд, и если источником является величина типа BYTE, то содержимое регистра AX делится на операнд–источник, а частное помещается в регистр AL, а остаток от целочисленного деления – в регистр AH. В случае, когда источником является операнд типа WORD, в качестве делимого предполагается пара регистров DX:AX. Результат деления помещается в регистр AX, а остаток – в регистр DX.
Синтаксис: DIV <источник>
Примеры:
div CL ;содержимое регистра AX делится на CL, ;частное – в AL, остаток – в AH div LM ;действие команды зависит от размера памяти, ;адресуемой LM |
Команда IDIV
Команда осуществляет деление со знаком. Действия команд DIV и IDIV схожи. Разница в том, что команда IDIV при делении учитывает знаки операндов.
Синтаксис: IDIV <источник>
Примеры:
idiv DL ;содержимое регистра AX делится на DL, со знаком div MEM_WORD ;разделить DX:AX на слово памяти, со знаком ;частное – в AX, остаток – в DX |
Использование команд деления может привести к неожиданным результатам. В случае, если в результате деления получится величина, превосходящая максимально допустимое значение (8 или 16 бит), или если делитель окажется равным 0, будет сгенерировано программное прерывание Divide Overflow (переполнение при делении).
Например, в последовательности
mov ax, 1000 mov bl, 2 div bl |
результат должен быть 500. Эта величина должна быть сохранена в однобайтовом регистре AL. В этом случае выполнение программы будет прервано и на консоль будет выведено сообщение об ошибке, т.к. число со знаком, помещаемое в один байт, должно находиться в диапазоне [-128, +127].
Команда CBW
Команда преобразует байт в слово, воспроизводя старший 7-й бит регистра AL во всех битах регистра AH. Таким образом, команда CBW позволяет сложить байт и слово, вычесть слово из байта и т.д.
Синтаксис: CBW
Например:
cbw add AX, BX ;сложить байт в AL со словом в BX cbw imul BX ;умножить байт в AL на слово в BX |
Команда CWD
Команда преобразует слово в двойное слово, воспроизводя старший 15-й бит регистра AX во всех битах регистра DX. Таким образом, команда CWD позволяет разделить слово на слово. Например:
cwd idiv BX ;разделить слово в AX на слово в BX |
Синтаксис: CWD
Вычисление значения выражения
Рассмотрим задачу вычисления арифметического выражения. Так как Ассемблер не располагает встроенными средствами ввода и вывода чисел, для работы с числовыми переменными необходимы некоторые преобразования. Так, перед отображением числа его надо преобразовать в последовательность символов. Вводить Ассемблер также позволяет только символы, поэтому для работы с числами введенную строку надо преобразовать в число.
Рассмотрим пример преобразования числа в строку. Пусть numar – число, которое необходимо вывести на экран. Преобразуем его в строку символов s и выведем её на экран.
s db 6 dup(’ ’),’$’ .code ... mov ax, ;число может быть отрицательным ;или положительным push ax ;сохраняем число из ax в стеке mov si, 10 ;делитель для выделения младшего разряда mov di, 5 ;максимально число типа dw может занимать 6 ;позиций. Устанавливаем указатель на последнюю ;позицию в строку результата mov dx, 0 ;dx=0, так как (dx:ax)/si cmp ax, 0 ;проверяем знак числа jge m ;если число положительное, переходим на метку m neg ax ;если число отрицательное, меняем знак m: div si ;значение из (dx:ax) делим на 10, выделяя ;последний разряд числа, который помещается в ;регистр dx. Так как это число не будет больше, ;чем 9, оно помещается в dl add dl, 30h ;преобразуем полученную цифру в символ mov s[di], dl ;вставляем полученный символ в строку результата xor dx, dx ;очищаем dx dec di ;сдвигаем позицию указателя в строке влево cmp ax, 0 ;проверяем, преобразованы ли все символы числа jne m ;если преобразованы не все символы, возвращаемся ;на начало цикла pop ax ;если всё число преобразовано, извлекаем ;первоначальное значение из стека для проверки ;знака cmp ax, 0 ;проверяем знак числа jge m1 ;если число >0, переходим на вывод строки mov s[di], ’-’ ;если число отрицательное, перед числом ;вставляем ”-” m1: mov ah, 9 ;вывод строки, содержащей преобразованное число lea dx, s int 21h |
Пример
Вычислим значение выражения ((120+15)/5-12)*16, значения переменных задаются в тексте программы.
data a db 120 b db 15 c db 5 d db 12 e db 16 .code mov al, a ; al=120 add al, b ; al=120+15 mov ah, 0 div c ; al=(120+15)/5 sub al, d ; al=(120+15)/5-12 mov ah,0 mul e ; ax=((120+15)/5-12)*16 ;Для преобразования введенной строки в число ;со знаком можно использовать следующий алгоритм: .data n dw 0 ;результирующее число mult10 dw 1 ;отвечает за разряд числа i. Будет ;содержать значение 10i. ten dw 10 ;множитель 10 sir db 8 dup (?) ;исходная строка .code ... mov ah, 3fh ;вводим число в виде набора символов mov bx, 0 lea dx, sir int 21h ;регистр ax будет содержать количество введенных ;символов +2 mov di, ax ;сохраняем количество введенных символов в di sub di, 3 ;находим позицию последнего символа cmp di, 0 ;для случая числа из одной цифры je b a: mov al, sir[di] ;копируем символ, на который показывает di, в al sub al, 30h ;преобразуем символ в число (цифру) mov ah, 0 ;ah обнуляется mul mult10 ;умножаем полученное число на mult10 dec di ;сдвигаем позицию индекса на один разряд влево add n, ax ;n=c0*100 +c1*101 +… mov ax, ten ;помещаем в ax 10 mul mult10 ;mult10=mult10*10 получаем 10i mov mult10,ax ;перемещаем новое значение mult10 из ax в mult10 cmp di, 0 ;проверяем, преобразованы ли все символы, кроме ;первого, который может содержать знак числа jne a ;если не первый символ, повторяем цикл cmp sir[di], '-';сравниваем первый символ с символом '-' je m1 ;если '-'‚переход на m1,иначе число положительное b: mov al, sir[di] ;преобразуем старший разряд числа sub al, 30h mov ah, 0 mul mult10 add n, ax ;копируем полученное число из ax в n jmp m2 m1: neg n ;получаем отрицательное число m2:... |
Задания
а). Вычислить значение выражения (значения переменных задаются в тексте программы):
|
b). Вычислить значение выражения (значения переменных вводятся с клавиатуры):
1 | (a+b+c)/a*d | a – define byte,b, c, d – define word |
2 | (a+b/c)*d | a, b, c – define byte, d – define word |
3 | (10*a-2*b)*c-d | a, b – define byte,c, d – define word |
4 | a/b+c*d/(c+b) | b, c, d – define byte, a – define word |
5 | 10*a/(a+b)+10*b/(c+d) | a, b, c – define byte, d – define word |
6 | 60*a+b*d+c*(a–b) | a, b – define byte, c, d – define word |
7 | (a+b)/c+d*a-c | a – define byte, b, c, d – define word |
8 | c-(a+b)/(a–c)+c*d | d – define byte, b, c, a – define word |
9 | a*b+c*d)/d | a, d – define byte, b, c – define word |
10 | (a*c+b*d)*c | a, b, d – define byte, c – define word |
11 | (a*b*c+c*d)/d | b – define byte, a, c, d – define word |
12 | (a*b–c*d)*(a+b) | a, b – define byte, c, d – define word |
13 | a*c*(a+b)-d | a, b, c – define byte, d – define word |
14 | d*b+c*a/(a+b) | c – define byte, b, a, d – define word |
15 | 25*a+13*b-d*c | c, b – define byte, a, d – define word |
16 | a*c-(a+b)/c*d | c, b, d – define byte, a – define word |
6. Команды сравнений и переходов
Цель работы: ознакомление с командами переходов и сравнений, а также правилами их использования при применении ближних и дальних ссылок.
Теоретическая часть
Команда CMP
Команда сравнения производит вычитание второго операнда из первого, не изменяя их значений, изменяются только флажки.
Синтаксис:
CMP <назначение>, <источник>
Изменяемые флажки: af, cf, pf, sf, zf, of.
Анализируя флажки, можно определить результат сравнения. Известно, что команда cmp ax, bx выполняет операцию (ax)–(bx), и если после этого вычитания zf = 1, следовательно, (ax)=(bx), а если cf = 1, то первый операнд меньше второго для беззнаковых чисел:(ax)<(bx).
Команда cmp обычно используется вместе с командами условного перехода, т.к. работа команд условного перехода зависит от значений флажков, которые изменяет команда cmp.
Команда безусловного перехода JMP
Синтаксис:
JMP <назначение>
где назначение – это адрес (метка), по которому должен быть осуществлен переход. Определить назначение можно также с помощью метки или выражения. Метка может быть дальней или ближней (near или far) и содержать имя процедуры или имя метки, описанной с помощью директивы label.
Примеры задания меток:
proc_add procedure
m1:
aici:
et3 label far
gata label near
Примеры команды jmp:
JMP proc_add
JMP m1
JMP et3
JMP gata
С помощью команды jmp можно задать три типа переходов:
- короткий переход – short, адрес назначения находится на расстоянии [-128, +127] байтов от места нахождения команды jmp;
- ближний переход – near, адрес назначения находится в том же сегменте, что и команда jmp;
- дальний переход – far, адрес назначения может находиться в другом сегменте кода.
Команды условного перехода
Синтаксис:
jcc <назначение>
где cc – условие перехода.
Директивы этой группы осуществляют переход только при выполнении определенных условий. Команды условного перехода имеют следующие характеристики:
- Все инструкции условного перехода являются командами короткого перехода (тип short), что означает, что целевой адрес должен быть в пределах диапазона [-128, 127] байт от команды перехода;
- Для одной и той же команды может существовать несколько мнемонических обозначений;
- Команды условных переходов флажков не меняют.
Команды условных переходов используются после команд, изменяющих флажки состояний. Чаще всего их используют после команды сравнения cmp. В таблице приведены мнемоники команды перехода. При чтении таблицы необходимо помнить два правила: 1) для анализа знаковых величин используются сравнения ,,greater” и ,,less”; 2) для анализа беззнаковых величин используются сравнения ,,above” и ,,bellow”.
Мнемоника команды | Условия перехода | Пояснения |
je, jz | zf=1 | Equal, Zero |
jl, jnge | sf?of | Less, Not Greater or Equal |
jle, jng | sf?of или zf=1 | Less or Equal, Not Greater |
jb, jnae, jc | cf=1 | Below, Not Above or Equal, Carry |
jbe, jna | cf=1 или zf=1 | Below or Equal, Not Above |
jp, jpe | pf=1 | Parity, Parity Even |
jo | of=1 | Overflow |
js | sf=1 | Sign |
jne, jnz | zf=0 | Not Eaual, Not Zero |
jnl, jge | sf=of | Not Less, Greater or Equal |
jnle, jg | sf=of и zf=0 | Not Less or Equal, Greater |
jnb, jae, jnc | cf=0 | Not Below, Above or Equal, Not Carry |
jnbe, ja | cf=0 или zf=0 | Not Below or Equal, Above |
jnp, jpo | pf=0 | Not Parity, Parity Odd |
jno | of=0 | Not Overflow |
jns | sf=0 | Not Sign |
Пример
Написать программу, которая проверяет, является ли введенный символ буквой ‘t’, и выводит ее эквивалент в верхнем регистре или сообщение “false” в противном случае.
Например:
Введите символ: t
T
или
Введите символ: s
false
Рассмотрим фрагмент программы.
mov ah,1 ;читаем символ int 21h mov var, al ;сохраняем введенный символ в переменной var cmp var, ’t’ ;сравниваем введенный символ с символом t jne m1 ;если символы не совпали, то переходим на m1 sub var, 20h ;если символы совпали, то преобразуем ‘t’ в ‘T’ mov ah, 2 ;выводим новый символ на экран mov dl, var int 21h jmp sfarsit m1: mov ah, 9 ;выводим сообщение False, содержащее lea dx, False ;текст “false” int 21h sfarsit: ... |
Задания
- Написать программу, соответствующую вашему варианту.
- Зациклить выполнение задания до тех пор, пока не будет введен символ пробел.
Варианты задач
1. Написать программу, которая проверяет, является ли введенный символ заглавной латинской буквой, и выводит ее эквивалент в нижнем регистре или сообщение „error”.
Например:
Введите символ: A
a
или
Введите символ: b
Error
2. Написать программу, которая проверяет, является ли введенный символ арифметической операцией (+, -, *, /), и выводит сообщение „true”, иначе выводит сообщение „false”.
Например:
Введите символ: *
true
или
Введите символ: b
false
3. Написать программу, которая проверяет, является ли введенный символ цифрой, и выводит символ ‚!’, иначе выводит сообщение „error”.
Например:
Введите символ: 5
!
или
Введите символ: (
error
4. Написать программу, которая проверяет, является ли введенный символ знаком препинания (– , . ! ? : ;), и выводит сообщение „true”, иначе выводит сообщение „false”.
Например:
Введите символ: !
true
или
Введите символ: 2
false
5. Написать программу, которая проверяет, является ли введенный символ малой латинской буквой, и выводит ее эквивалент в верхнем регистре или сообщение „error”.
Например:
Введите символ: r
R
или
Введите символ: 6
error
6. Написать программу, которая проверяет, является ли введенный символ скобкой (, ), [, ], {, }, и выводит сообщение „true”, иначе выводит сообщение „false”.
Например:
Введите символ: )
true
или
Введите символ: k
false
7. Написать программу, которая проверяет, является ли введенный символ гласной буквой (a, e, i, o, u, A, E, I, O, U), и выводит сообщение „ok”, иначе выводит сообщение „error”.
Например:
Введите символ: e
ok
или
Введите символ: m
error
8. Написать программу, которая проверяет, является ли введенный символ согласной буквой ‚m’, ‘n’, ‘p’ или ‘r’, и выводит сообщение „ok”, иначе выводит сообщение „error”.
Например:
Введите символ: m
ok
или
Введите символ: 4
error
9. Написать программу, которая проверяет, является ли введенный символ специальным символом (~, @, #, $, %, ^, &, |, \), и выводит сообщение „ true”, иначе выводит сообщение „ false”.
Например:
Введите символ: &
true
или
Введите символ: A
false
10. Написать программу, которая проверяет, является ли введенный символ символом с кодом меньше чем 48 или больше чем 122, и выводит сообщение „true”, иначе выводит сообщение „false”.
Например:
Введите символ: “
true
или
Введите символ: z
false
11. Написать программу, которая проверяет, является ли введенный символ одной из цифр ‘1’, ‘3’, ‘5’, ‘7’ или ‘9’, и выводит сообщение „ok”, иначе выводит сообщение „error”.
Например:
Введите символ: 3
ok
или
Введите символ: z
error
12. Написать программу, которая проверяет, является ли введенный символ одной из цифр ‘2’, ‘4’, ‘6’ или ‘8’, и выводит сообщение „ok”, иначе выводит сообщение „error”.
Например:
Введите символ: 4
ok
или
Введите символ: z
error
13. Написать программу, которая проверяет, является ли введенный символ символом ‘$’ или ‘@’, и выводит сообщение „true”, иначе выводит сообщение „false”.
Например:
Введите символ: $
true
или
Введите символ: z
false
14. Написать программу, которая проверяет, является ли введенный символ одним из символов ‘j’, ‘k’, ‘f’, ‘g’, и преобразовывает их в верхний регистр, иначе выводит сообщение „false”.
Например:
Введите символ: k
K
или
Введите символ: T
false
15. Написать программу, которая проверяет, является ли введенный символ одним из символов ‘A’, ‘C’, ‘D’, ‘E’, и выводит сообщение ‘OK’, иначе выводит сообщение „error”.
Например:
Введите символ: D
OK
или
Введите символ: T
error
16. Написать программу, которая проверяет, является ли введенный символ одним из символов ‘<’, ‘=’, ‘>’, ‘?’, и выводит сообщение “true”, иначе выводит сообщение „false”.
Например:
Введите символ: <
true
или
Введите символ: T
false