с инструкции mov ax,datasg по метке ProgramStart, как определено в директиве end. И уже в процессе выполнения этой процедуры вызывается процедура, обеспечивающая временну'ю задержку call Delay.
Если программа состоит только из одного модуля (то есть одного исходного файла), то в директиве end всегда нужно определять адрес запуска программы. В программе, состоящей из нескольких модулей, определять адрес запуска программы следует только в директиве end модуля, содержащего инструкцию, с которой должно начаться выполнение программы. В директивах end других модулей должно указываться только ключевое слово end и ничего более.
4.29. Основные элементы программы на языке ассемблера
Теперь можно назвать детали программирования:
–основные компоненты программы;
–минимальные требования к программе;
–способы, с помощью которых программа может получить доступ к памяти;
–определение и использование сегментов;
–выделение и использование переменных в памяти;
–наиболее общеупотребительные инструкции.
4.30. Элементы и структура программы на языке ассемблера
Даже простая программа на ассемблере обязательно включает в себя некоторые строки. Рассмотрим, например, следующую программу:
stacksg segment para ‘Stack’
db 200h |
; стек объемом 512 байтов (200h) |
|
stacksg ends |
|
|
datasg segment para ‘Data’ |
; начало сегмента данных |
|
DisplayString db |
13,10 |
; символы "возврат каретки/перевод строки" |
ThreeChars db 3 dup (?) |
; память для трех введенных символов |
|
db |
'$' |
; символ $, указывающий DOS, где |
datasg ends |
; завершить вывод строки DisplayString |
|
|
|
|
codesg segment para ‘Code’ |
; начало сегмента кода |
|
mainproc far
assume cs:codesg, ds:datasg, ss:stacksg
mov ax,datasg |
|
mov ds,ax |
; ds указывает на сегмент данных datasg |
mov bx,offset ThreeChars ; указывает на ячейку с первым символом
mov ah,1 |
; функция DOS ввода с клавиатуры |
int 21h |
; получить следующую нажатую клавишу |
|
36 |
dec al |
; вычесть из символа 1 |
mov [bx],al |
; сохранить измененный символ |
inc bx ; указать на ячейку памяти, где содержится следующий символ |
|
inc 21h |
; получить следующую нажатую клавишу |
dec al |
; вычесть из символа 1 |
mov [bx],al |
; сохранить измененный символ |
inc bx |
; ссылка на ячейку памяти со следующим символом |
int 21h |
; получить следующую нажатую клавишу |
dec al |
; вычесть из символа 1 |
mov [bx],al |
; сохранить измененный символ |
mov dx,offset DisplayString ; ссылка на строку измененных символов |
|
mov ah,9 |
; функция DOS вывода строки |
int 21h |
; вывести измененные символы |
mov ah,4ch |
; функция DOS завершения |
int 21h |
; программы |
ret |
|
main endp |
|
codesg ends |
|
end main |
; конец кода и указание на входную точку |
Данная программа содержит стандартные директивы определения сегментов стека, данных и кода, а также директиву end. В каждой программе на ассемблере, чтобы обеспечить определение сегментов и управление ими, необходимы директивы определения сегментов (стандартные, как в приведенном выше примере, или упрощенные – см. уч. пос., ч.4), а завершать программу на ассемблере всегда должна директива end. Директивы представляют собой только "рамки" программы на ассемблере. В самой программе необходимы также строки исходного кода, выполняющие какие-либо действия. Что делает программа, приведенная в примере? Введите ее, получите объектный файл и выполняемую программу, запустите, наберите “IBM”, после чего программа ответит Вам: “hal”
4.31. Формат строки
Строки исходного кода на языке ассемблера имеют следующий формат:
[<метка>] <инструкция/директива> [<операнды>] [<;комментарий>]
где <метка> представляет собой необязательное имя идентификатора; <инструкция/директива> – мнемоника инструкции или директивы;
37
<операнды> – содержат сочетание 0, 1 или 2 (иногда более) констант, ссылок на регистры, или на память, или на текстовые строки, как это требуется в каждой конкретной инструкции или директиве;
<;комментарий> – необязательный комментарий. Рассмотрим каждый из этих элементов более подробно.
4.32. Метки
Метки – это имена, используемые в программе для ссылки на числа и строки символов или ячейки памяти. Метки позволяют присваивать имена переменным в памяти, значениям и адресам, где находятся конкретные инструкции. Метки могут состоять из следующих символов:
A – Z a – z _ @ $ ? 0 – 9
Цифры 0 – 9 не могут использоваться в качестве первых символов метки. Символы $ и ? имеют специальное значение, поэтому их не следует использовать в идентификаторах.
Каждая метка должна определяться только один раз, т.е. метки должны быть уникальными. Как операнды метки могут использоваться любое число раз.
Метка может занимать всю строку, то есть на этой строке, кроме метки, может отсутствовать инструкция или директива. В этом случае значением метки является адрес инструкции или директивы, расположенной на следующей строке программы. Например, во
фрагментах программы 1) и 2): |
|
||
1) |
jmp |
formula1 |
2) jmp formula1 |
|
. . . |
. . . |
|
formula1: |
|
formula1: add ax, dx |
|
|
add |
ax,dx |
. . . |
следующей инструкцией, выполняемой после инструкции jmp, которая выполняет переход на метку formula1, является инструкция add ax,dx.
Преимущества размещения метки на своей собственной строке:
1)удается использовать длинные метки без нарушения формата программы;
2)легче добавить новую инструкцию (не надо разделять строку). Метка не может совпадать с любым из используемых в выражениях
встроенных символов, включая имена регистров (ax, bx и т.д.) и операторы, используемые в выражениях (ptr, byte, word и т.д.). В качестве меток нельзя также использовать любую из директив .ifxxx или .errxxx. Несколько других зарезервированных в ассемблере идентификаторов могут использоваться только в определенных контекстах. Эти идентификаторы включают в себя name, include и comment.
38
Метки в коде программы (и на отдельной строке, и в той же строке, что и директивы или инструкции) должны заканчиваться двоеточием (:). Двоеточие просто завершает метку и не является ее частью. Например, в следующем фрагменте программы:
cmp |
al,'a' |
jb |
NotALowerCaseLetter ; не строчная буква |
cmp |
al,'z' |
ja |
NotALowerCaseLetter |
sub |
al,20h ; преобразовать в верхний регистр (прописную букву) |
NotALowerCaseLetter:
. . .
метка NotALowerCaseLetter определена с двоеточием, но в ссылках на эту метку двоеточие не указывается.
Могут быть метки, которые не должны завершаться двоеточием. В примерах программы (пп.4.30, 4.45 и 4.56) содержатся несколько меток без двоеточий (DisplayString и ThreeChars в п.4.30, vvod_b в п.4.45 и KeyBuffer в п.4.56).
Имена меткам следует назначать так, чтобы они имели смысл. Сравните предыдущий пример и следующий фрагмент программы:
cmpal,'a' jb P1 cmpal,'z' ja P1
sub al,20h ; преобразовать в верхний регистр
P1:
. . .
Вариант с описательной меткой гораздо более понятен. Метки могут содержать символы подчеркивания. Можно использовать метки типа not_a_lower_case_letter, или Not_A_Lower_Case_Letter, или NotA_LowerCaseLetter.
4.33. Мнемоники инструкций и директивы
Основным полем в строке программы на ассемблере является поле <инструкция/директива>. Это поле может содержать мнемонику инструкции или директиву.
Мнемоники инструкций представляют собой удобные для чтения имена инструкций, непосредственно выполняемых процессором 8086. Например, mov, add, mul и jmp – это мнемоники инструкций, соответствующие инструкциям процессора 8086 перемещения данных, сложения, умножения и перехода соответственно. Ассемблер преобразует
39
каждую мнемонику инструкции непосредственно в соответствующую инструкцию на машинном языке.
В отличие от мнемоник инструкций, директивы совсем не генерируют выполняемого кода. Вместо этого они управляют различными аспектами работы ассемблера – от типа ассемблируемого кода (процессоры 8086, 80286, 80386 и т.д.) до используемых сегментов и формата создаваемых файлов листингов. Имеется несколько директив, которые необходимы в любой программе на ассемблере (segment, ends, assume и end; см.пп.4.25– 4.28).
4.34. Операнды
Мнемоники инструкций и директивы сообщают ассемблеру, что нужно делать (например, mov – перемещение данных). Операнды указывают, какие регистры, ячейки памяти и т.д. нужно использовать.
Для инструкций требуются 0, 1, 2 или более операндов. Возможные операнды включают в себя регистры, константы, метки, переменные в памяти и текстовые строки. Инструкция с одним операндом выполняет операции с этим операндом. Например:
push ax
заносит регистр ax в стек. Инструкции без операндов еще более очевидны. В случае инструкции с двумя операндами один является источником, а другой – приемником. Когда процессор выполняет инструкцию
mov ax,bx
то содержимое регистра bx помещается (копируется) в регистр ax.
4.35. Регистровые операнды
Наиболее часто используются регистровые операнды. Регистры могут использоваться в качестве источника (исходный операнд) или приемника (целевой операнд) и могут даже содержать адрес, на который нужно выполнить переход. С регистрами можно делать много того, чего нельзя делать с константами, метками или переменными в памяти. Имеются некоторые инструкции, в которых можно использовать только регистровые операнды. Примеры регистровых операндов:
1)mov bx,ax ;bx:=ax
2) |
push |
ds |
; занести в стек содержимое регистра ds |
3) |
xchg |
al,dl |
;al:=dl и dl:=al |
4)mov cl,5
|
ror dx,cl |
; циклический сдвиг регистра dx на 5 битов вправо |
5) |
in al,dx |
; из порта с номером в dx ввод байта в регистр al |
6) |
inc si |
;si:=si+1 |
Регистровые операнды могут использоваться вместе с другими операндами:
40