mov al,1 |
; al := 1 |
add BaseCount,cx |
; BaseCount := cx |
cmpsi,[bx] |
; сравнение (из содержимого регистра si вычесть |
; значение слова по адресу, взятому из регистра bx, и установить флаги). Если имеется два операнда и регистровым операндом является правый операнд, то он будет исходным регистром (источником), а если регистровым операндом является левый операнд – то это целевой регистр (приемник). Если в инструкции требуется два источника, то один из регистров является и источником и приемником (левый операнд).
Например, во фрагменте программы:
mov cx,1 |
; загрузить в сх значение 1 |
mov dx,2 |
; загрузить в dх значение 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