Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

epd627

.pdf
Скачиваний:
22
Добавлен:
02.05.2015
Размер:
816.97 Кб
Скачать

mov al,1

; al := 1

add BaseCount,cx

; BaseCount := cx

cmpsi,[bx]

; сравнение (из содержимого регистра si вычесть

; значение слова по адресу, взятому из регистра bx, и установить флаги). Если имеется два операнда и регистровым операндом является правый операнд, то он будет исходным регистром (источником), а если регистровым операндом является левый операнд – то это целевой регистр (приемник). Если в инструкции требуется два источника, то один из регистров является и источником и приемником (левый операнд).

Например, во фрагменте программы:

mov cx,1

; загрузить в сх значение 1

mov dx,2

; загрузить в значение 2

sub dx,cx

; dx:=dx-cx= 2-1= 1

из регистра dx вычитается cx, и результат (1) снова записывается в регистр dx. В инструкции sub регистр cx является правым операндом (источник), dx – левый операнд (второй источник и приемник). Действие данной инструкции – "вычесть cx из dx".

4.36. Операнды-константы

Часто в операндах требуется использовать постоянное значение. Предположим, необходимо в цикле уменьшать значение регистра si на 4, повторяя цикл, пока значение si не станет равным 0. Можно использовать следующие операции:

met1:

 

sub

si,4

; si := si - 4

jnz

met1

 

В

качестве

постоянных операндов (операндов-констант) можно

использовать также символы, поскольку символ представляет собой определенное значение. Например, так как символ a имеет десятичное значение 65, то инструкция sub al,’a’ и инструкция sub al,65 эквивалентны.

Постоянные значения можно задавать в двоичном, восьмеричном или шестнадцатеричном представлении, а также в десятичном виде.

При использовании двух операндов операнды-константы никогда не могут располагаться слева, так как невозможно использовать константу в качестве операнда-приемника. Ограничение: невозможно занести константу (например, 5) непосредственно в стек – можно только через регистр (это ограничение касается только процессоров 8086/8088):

mov ax,5

; ax := 5

push ax

;занести содержимое ax в стек

 

4.37. Операнды-метки

41

Во многих инструкциях в качестве операндов можно использовать метки. При указании их в соответствующих операциях метки могут использоваться для получения постоянных значений (констант). Например:

;

сегмент данных

 

MemWord dw 1

; директива определения слова

;

сегмент кода

 

 

. . .

 

 

mov al, size MemWord

; al:=2

Здесь метка используется в операции size, в результате значение 2 (размер в байтах переменной MemWord) помещается в al.

Метки могут также использоваться в качестве целевых операндов в операциях call, jmp. Например, во фрагменте программы:

cmp

ax,100

; zf:=1, если (ax-100)

=0

;

 

 

zf:=0, если (ax-100)

<> 0, т.е.не равно нулю

jz

met2

 

 

.

.

.<какие-либо команды>

 

met2:

. . .

инструкция jz используется для перехода по адресу, заданному операндом met2, если значение ax равно 100. Здесь метка задает адрес перехода.

Наконец, метки можно использовать в качестве операндов почти так же, как используются регистры, то есть как операнд-источник (TempVar в инструкции sub) или операнд-приемник (TempVar в инструкции mov):

;сегмент данных

TempVar dw ?

; сегмент кода

. . .

mov TempVar,ax ; ячейке TempVar присвоить значение регистра ax sub ax,TempVar ; ax:= ax–TempVar (обнуление регистра)

Как задать ячейку памяти, с которой хотите работать? Очевидно, нужно присвоить переменной имя. С помощью, например, следующих операторов можно вычесть переменную Debts (долги) из переменной Assets (имущество):

; сегмент данных

Assets dw ? Debts dw ?

; сегмент кода

. . .

mov ax,Debts sub Assets,ax

42

Предположим, имеется строка CharString, содержащая буквы abcdefghijklm, которые начинаются в сегменте данных со смещения 100. Как прочитать девятый символ (i), который расположен по адресу 108?

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

datasg segment para ‘Data’

; начало сегмента данных

CharString db 'abcdefghijklm'

datasg ends

 

codesg segment para ‘Code’

; начало сегмента кода

mainproc far

 

. . .

 

mov

ax,datasg

 

mov

ds,ax

 

mov

al,CharString+8

 

Смещение CharString и

8 складываются, а полученное значение

используется в качестве адреса памяти.

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

Имеется другой, более гибкий путь адресации памяти. Например:

mov

bx, offset CharString+8 ; bx := смещение CharString плюс 8

mov

al,[bx]

; процессор обращается по адресу из регистра bx.

Здесь

для

ссылки на девятый символ используется регистр bx.

(Вычисление offset и сложение с константой 8 выполняется во время ассемблирования). Затем в al загружается содержимое ячейки памяти, на которую указывает bx.

Квадратные скобки показывают, что в качестве операнда-источника должна использоваться ячейка, на которую указывает регистр bx, а не сам регистр bx. Например:

mov

ax,[bx]

;а) ax:= содержимое ячейки, на которую указывает bx

mov

ax,bx

;б) ax:= содержимое регистра bx

это две совершенно различные инструкции.

А зачем сначала загружать в регистр bx смещение ячейки памяти и затем использовать bx как указатель, если то же самое можно сделать с помощью одной инструкции с непосредственным операндом? Дело в том, что в отличие от инструкций, использующих непосредственные операнды, инструкции, использующие в качестве указателей регистры, в процессе выполнения программы могут ссылаться на разные ячейки памяти.

Если требуется определить последний символ строки CharString, необходимо, начиная с первого символа строки CharString, найти

43

завершающий строку нулевой байт, затем вернуться назад на один символ и считать этот символ. Это невозможно с непосредственной адресацией. Использование bx значительно облегчает задачу:

mov

bx,offset CharString ; указывает на строку Char String

FindLastCharLoop:

 

mov

al,[bx]

; получить следующий символ строки

cmp

al,0

; это нулевой байт?

jz FoundEndOfString; да, bx указывает на ноль в конце строки

inc

bx

; bx:= bx+1

jmp

FindLastCharLoop

; проверить следующий символ

FoundEndOfString:

 

dec

bx

; bx:= bx-1

снова указывает на последний символ

mov

al,[bx] ; получить последний символ строки (перед нулем)

Это очень удобно для поиска символов или слов в строке, при работе с элементами массивов, при копировании блоков данных.

Регистр bx – это не единственный регистр, который можно использовать для ссылки на память. Допускается также использовать регистры bp, si и di вместе с необязательным значением – константой или меткой. Общий вид операндов в памяти выглядит следующим образом:

[базовый регистр + индексный регистр + смещение]

где базовый регистр – это bx или bp, индексный регистр – si или di, а смещение – любая 16-битовая константа, включая метки и выражения (см.п. 4.47 «Выражения»). Каждый раз, когда выполняется инструкция, использующая операнд в памяти, процессором 8086 эти три компоненты складываются. Каждая из трех частей операнда в памяти является необязательной, но должен использоваться хотя бы один из трех элементов.

 

 

 

 

 

 

 

Таблица 4.1

 

 

 

(bx или bp)

+ (si или di)

 

 

 

 

 

 

Существует

база

 

индекс

+ смещение

 

 

16 способов

 

задания адреса

в памяти:

Таблица 4.2

 

 

 

 

 

 

 

 

 

 

Задание адреса

 

Задание адреса

 

 

Задание адреса

п/п

 

 

п/п

 

 

 

 

п/п

 

 

1

[смещение]

7

 

[di]

 

 

13

 

[bp+si]

2

[bp+смещение]

8

 

[di+смещение]

 

 

14

 

[bp+si+смещение]

3

[bx]

9

 

[bx+si]

 

 

15

 

[bp+di]

4

[bx+смещение]

10

 

[bx+si+смещение]

 

16

 

[bp+di+смещение]

5

[si]

11

 

[bx+di]

 

 

 

 

 

6

[si+смещение]

12

 

[bx+di+смещение]

 

 

 

 

Смещение – это то, что можно свести к 16-битовому постоянному значению.

44

Все режимы адресации получаются из нескольких элементов, комбинируемых различными путями. Вот еще несколько способов, с помощью которых можно, используя различные режимы адресации,

загрузить девятый символ строки CharString в регистр al:

datasg segment para ‘Data’

; начало сегмента данных

CharString db 'abcdefghijklm',0

datasg ends

 

codesg segment para ‘Code’

; начало сегмента кода

mainproc far

mov

ax,datasg

mov

ds,ax

. . .

А затем идет набор инструкций по одному из вариантов (табл.4.3).

 

 

 

 

 

 

Таблица 4.3

mov bx,8

; способ 1

 

mov bx, offset CharString

; способ 5

mov al,[Charstring+bx]

 

mov

di,8

 

 

 

 

 

mov

al,[bx+di]

 

mov

si,8

; способ 2

 

mov bx, offset CharString

; способ 6

mov

al,[CharString+si]

 

mov

si,7

 

 

 

 

 

mov

al,[bx+si+1]

 

mov

bx,3

; способ 3

 

mov

si,offset CharString+8

; способ 7

mov

si,5

 

 

mov

al,[si]

 

mov

al,[bx+CharString+si]

 

 

 

 

mov bx,8

; способ 4

 

mov bx, offset CharString

; способ 8

mov

si, offset CharString

 

mov

al,[bx+8]

 

mov

al,[si+bx]

 

 

 

 

Все эти

инструкции

ссылаются

на одну и ту же

ячейку памяти

[CharString]+8.

Во время ассемблирования ассемблер складывает все константы внутри квадратных скобок, поэтому инструкция mov [10+bx+si+100],cl

принимает вид mov [bx+si+110],cl

При реальном выполнении инструкции операнды в памяти складываются процессором. Если bx содержит значение 25, а si содержит 52, то при выполнении содержимое cl записывается по адресу памяти 25 + 52 + 110 = 187. Ассемблер складывает константы во время ассемблирования, а процессор 8086 складывает содержимое базового регистра bx, индексного регистра si и смещения 110 во время действительного выполнения инструкции.

Заметим, что регистр bx используется как смещение внутри сегмента данных, а регистр bp используется как смещение в сегменте стека. Т.е. регистр bp не может использоваться для адресации к строке CharString, которая находится в сегменте данных.

45

Регистр bp можно использовать так же, как использовался bx, только адресуемые данные должны находиться в стеке.

4.38. Комментарии

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

mov [bx],al ; сохранить измененный символ

Не обязательно комментировать каждую строку. Например: mov ah,1

int 21h ; получить следующую клавишу

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

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

; Сгенерировать для буфера передачи байт контрольной суммы

mov

bx,offset TransferBuffer

mov cx,transfer_buffer_length

sub

al,al

; очистить аккумулятор контрольной суммы

CheckSum:

 

add

al,[bx]

; добавить в него текущее значение байта

inc

bx

; указать на следующий байт

В комментарии к этому блоку кратко суммируется его работа, поэтому тот, кто будет просматривать программу, больше полезного извлечет из комментариев к блокам, чем из комментариев к строкам.

Третий метод состоит во включении перед каждой подпрограммой описательного заголовка-комментария ("шапки" программы). Такой заголовок может содержать описание подпрограммы, ее входные и выходные значения и различные замечания по ее работе. Например:

;Функция, возвращающая контрольную сумму (один байт) буфера данных

;Входные данные ds: bx – указатель на начало буфера

;cx – длина буфера

;Выходные данные: al – контрольная сумма буфера

;Используемые регистры (содержимое не сохраняется): bx, cx

;Примечание: буфер не должен превышать 64 Кб

;

и не должен пересекать границу сегментов.

CheckSum proc near

sub

al,al ; очистить аккумулятор контрольной суммы

 

46

add al,[bx]; прибавить текущее значение байта inc bx ; ссылка на следующее значение loop CheckSum

ret CheckSum endp

Если подпрограмма написана и отлажена, то редко приходится снова просматривать ее текст. Все, что требуется знать, – это какие функции выполняет подпрограмма и как подпрограмма взаимодействует с вызывающей ее программой. Этим целям хорошо отвечает описательный заголовок.

4.39. Набор инструкций процессоров семейства iAPx86

Набор инструкций включает в себя все действия, которые программист может заставить выполнить процессор. Перечень используемых в первой лабораторной работе инструкций (команд) ассемблера приведен в табл.3.2, а полный набор имеется в [6, с.134-138]. Эти инструкции выполняют множество действий – от пустой операции, которая не выполняет никаких функций (nop), до копирования 65 535 слов

(rep movsw).

4.40. Системное программное обеспечение для семейства IBM PC

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

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

Ваша программа всегда имеет возможность управлять аппаратными средствами непосредственно, но лучше по возможности пользоваться функциями DOS и базовой системы ввода-вывода BIOS.

4.41.Операционная система DOS

Воперационной системе (ОС) предусмотрено также большое число функций, которые широко используются любой прикладной задачей. С помощью функций DOS прикладные задачи выполняют чтение из файлов и запись в них данных, получают символы клавиш или устанавливают и получают текущее время. Например, фрагмент программы на ассемблере:

mov ah,2; функция DOS вывода символа

mov

dl,'A' ; A – это символ, который нужно вывести на экран

int

21h

вызывает функцию DOS вывода на экран, чтобы вывести символ A в текущей позиции курсора.

47

Функции DOS следует вызывать для ввода с клавиатуры или из файла, вывода на экран или в файл и на печать.

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

Рассмотрим функции DOS, используемые в лабораторной работе №1.

4.42. Получение символов с клавиатуры

Ввод информации с клавиатуры – один из основных способов взаимодействия с компьютером IBM PC. Одним из наиболее простых способов получения символов клавиш является функция "Ввод с клавиатуры", то есть функция DOS номер 1. Функции DOS вызываются путем помещения номера функции в регистр ah и выполнения затем инструкции int 21h. Набранный на клавиатуре символ возвращается в регистре al. Например, когда выполняется код

mov

ah,1

; ah:=1 (функция ввода одного символа с клавиатуры)

int

21h

 

DOS помещает следующий набранный на клавиатуре символ в регистр al. Если клавиша не нажата, DOS будет ждать, когда она будет нажата, поэтому для выполнения данной функции может потребоваться неопределенное время.

4.43. Вывод символов на экран

Функция DOS с номером 2 обеспечивает наиболее непосредственный путь вывода символа на экран. Для этого нужно просто поместить 2 в регистр ah и выводимый символ в регистр dl, а затем вызвать DOS с помощью int 21h. Следующий код отображает каждый введенный символ на экране:

mov

ah,1

; ah:=1 (функция ввода одного символа с клавиатуры)

int

21h

; т.е. получить код следующей нажатой клавиши (в al)

mov

dl,al

; скопировать считанный символ из al в dl (dl:=al)

mov

ah,2

 

int

21h

; вывести его на экран.

4.44. Вывод сообщения на экран в базовой версии DOS

Вывод на экран в базовой версии DOS требует определения текстового сообщения в области данных, установки в регистре ah значения 09 (вызов функциии DOS) и указания команды DOS int 21h. В процессе

48

выполнения операции конец сообщения определяется по oграничителю ($), как это показано ниже:

;сегмент данных

msg db 'Имя покупателя?','$'

;сегмент кода

mov ah,09h ;запрос вывода на экран lea dx, msg ;загрузка адреса сообщения int 21h ;вызов DOS

Знак ограничителя $ можно кодировать:

непосредственно после cимвольной строки (показано выше);

внутри строки: 'Имя покупателя?$';

или в следующем операторе db '$'.

Используя данную операцию, нельзя вывести на экран символ доллара $. Кроме того, если знак доллара будет отсутствовать в конце строки, то на экран будут выводиться все последующие символы, пока знак $ не встретится в памяти.

Команда lea загружает адрес области msg в регистр dx для передачи в DOS адреса выводимой информации. Этот адрес является oтносительным, поэтому для вычисления абсолютного адреса данных DOS «складывает» (см. п.4.18) значения регистров ds и dx (ds:dx).

4.45. Ввод данных с клавиатуры в базовой версии DOS

Для ввода, использующего базовую DOS, область ввода требует наличия cписка параметров, содержащего поля, необходимые при

выполнении команды int.

Пример.

vvod_b

label byte

; Список параметров:

maxlen_b db 4

; максимальная длина

fact_b

db 0

; реальная длина

simv_b

db "0000"

; введенные символы

Первый байт содержит максимальную длину вводимых данных. Это необходимо для предупреждения пользователя звуковым сигналом, если набран слишком длинный текст; символы, превышающие максимальную длину, не принимаются. Максимальное значение поля – 999#. Символ # использован здесь для индикации конца данных, так как шестнадцатеричный код клавиши Enter (0dh) не имеет отображаемого символа

Второй байт необходим DOS для занесения в него действительного числа введенных символов.

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

49

Так как директива label не занимает места, то vvod_b и maxlen указывают на один и тот же aдрес памяти. Атрибут byte – один из возможных атрибутов этой директивы (см. п.4.56.)

Для запроса на ввод необходимо поместить в регистр ah номер функции – 10 (шестнадцатеричное 0ah), загрузить адрес списка параметров (vvod_b в нашем примере) в регистр dx и выполнить int 21h:

mov ah,0ah ;запрос функции ввода

lea dx, vvod_b

;загрузить адреса списка параметров

int 21h

;вызвать DOS

Команда int ожидает, пока пользователь не введет с клавиатуры текст, проверяя при этом, чтобы число введенных cимволов не превышало максимального значения, указанного в списке параметров (4 в нашем примере). Для указания конца ввода пользователь нажимает клавишу Enter. Код этой клавиши (шестнадцатеричное 0d) также заносится в поле ввода (у нас – simv_b).

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

иметь вид:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Datasg segment para ‘Data’

 

 

 

 

 

 

 

 

 

 

 

 

spisok

label byte

 

;Список параметров

 

 

 

 

 

 

 

 

maxlen

db 11

 

 

; максимальная длина

 

 

 

 

 

 

 

fact db 0

 

 

; реальная длина

 

 

 

 

 

 

 

 

 

simv_sp db "00000000000"; введенные символы

 

 

 

 

 

 

 

Допустим, пользователь ввел имя BROWN и нажал клавишу Enter.

Список параметров в этом случае будет содержать информацию:

 

 

 

 

Значение

 

spisok

 

simv_sp

+1

+2

+3

+4

+5

+6

 

 

 

 

 

 

Десятичное

11

6

В

R

O

W

N

#

0

0

0

0

0

 

 

Шестнадцатеричное

0B

06

42

52

4F

57

4E

0d

30

30

30

30

30

 

Во второй байт списка параметров (fact в нашем примере) команда заносит длину введенного имени – 6. Код Enter находится по адресу simv_sp+5. Т.к. максимальная длина в 11 символов включает шестнадцатеричное 0d, то можно ввести текст не более 10 символов. Подробнее о функциях DOS в [1, с.131 – 192].

В DOS не предусмотрен форматный ввод и вывод. DOS выполняет только посимвольный или построчный ввод-вывод.

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

Программа, которая выполняет просто эхоотображение на экране строки набранных на клавиатуре символов, приведена ниже. Для завершения программы имеется несколько функций DOS, но наиболее

50

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]