r/m
|
ИА
|
ФА
|
000
|
(BX)
+ (SI) + disp
|
+
(DS)
|
001
|
(BX)
+ (DI) + disp
|
+
(DS)
|
010
|
(BP)
+ (SI) + disp
|
+
(SS)
|
011
|
(BP)
+ (DI) + disp
|
+
(SS)
|
100
|
(SI)
+ disp
|
+
(DS)
|
101
|
(DI)
+ disp
|
+
(DS)
|
110
|
(BP)
+ disp
|
+
(SS)
|
111
|
(BX)+
disp
|
+
(DS)
|
7. Примеры
команд пересылки с различными способами адресации операндов
В командах на ассемблере результат всегда
пересылается по адресу первого операнда.
) Регистровая адресация - операнд находится в
регистре:
MOV
AX, BX;
(BX) ®
AX, содержимое
регистра BX
пересылается в регистр AX.
Машинный формат: 1001 0011 1100 0011
код операции = 100100, d
=
1, w = 1, mod
= 11, reg = 000, r/m
= 011.
2) Непосредственная адресация - операнд
находится непосредственно в команде:
AX,
25 ; 25 ® AX
CONST
EQU 34h;
именованная константа CONST
MOV
AX, CONST;
34h ®
AX
3) Прямая адресация - в команде содержится адрес
операнда. Если известен адрес памяти, начиная с которого размещается операнд,
то в команде можно непосредственно указать этот адрес:
AX, ES:0001 ; ((ES) + 0001) ®AX.
- регистр сегмента данных, 0001 - смещение
внутри сегмента. Содержимое двух байтов, начиная с адреса (ES)
+ 0001 пересылаются в AX.
Прямая адресация может быть записана с помощью символического имени, которое
предварительно поставлено в соответствие некоторому адресу памяти, с помощью
специальной директивы определения памяти, например, DB
- байт, DW - слово, DD -
двойное слово. Если в сегменте ES
содержится директива Var_p
DW, тогда по команде
AX, ES: Var_p ; ((ES)+ Var_p) ®AX
содержимое двух байтов, начиная с адреса (ES)+
Var_p
пересылаются в AX.
Например, если команда имеет вид:
AX, Var_p ; ((DS)+ Var_p) ®AX.
?????????????
4) Косвенно-регистровая адресация. Данный вид
адресации отличается от регистровой адресации тем, что в регистре содержится не
сам операнд, а адрес области памяти, в которой содержится операнд. Например,
MOV
AX, [SI].
Для хранения адреса памяти могут использоваться
регистры SI, DI,
BX, BP,
EAX. EBX,
ECX, EDX,
EBP, ESI,
ED, но не могут
использоваться AX, CX,
DX, SP,
ESP.
) Адресация по базе со смещением:
AX, [BX]+2 ; ((DS)+(BX)+2) ®AX.
º MOV AX, [BX+2] º
MOV AX, 2[BX]AX, [BP+4] ; ((SS)+(BP)+4) ®AX.
) Прямая
адресация с
индексированием
AX, MAS[SI] ; ((DS)+(SI)+MAS) ®AX
- адрес в области памяти. С помощью этой
адресации можно работать с одномерными массивами. Символическое имя определяет
начало массива, а переход от одного элемента к другому осуществляется с помощью
содержимого индексного регистра.
) Адресация по базе с индексированием
AX,
Arr[BX][DI]
; ((DS)+(BX)+(DI)+Arr)
®AX.
Эта адресация используется для работы с
двумерными массивами. Символическое имя определяет начало массива, с помощью
базового регистра осуществляется переход от одной строки матрицы к другой, а с
помощью индексного регистра - переход от одного элемента к другому внутри
строки.
8. Особенности
использования команд пересылки
1. Нельзя пересылать информацию из одной области
памяти в другую.
2. Нельзя пересылать информацию из одного
сегментного регистра в другой.
. Нельзя пересылать непосредственный операнд в
сегментный регистр, но если такая необходимость возникает, то нужно использовать
в качестве промежуточного один из регистров общего назначения:
MOV DX, 100h
DS, DX
4. Нельзя изменять командой MOV
содержимое регистра CS.
. Данные в памяти хранятся в
"перевернутом" виде, а в регистрах в "нормальном" виде, и
команда пересылки учитывает это, например,
R DW
1234h
В байте с адресом R
будет 34h, в байте с адресом
R+1 будет 12h.
AX, R ; 12h ®
AH, 34h ® AL.
6. Размер передаваемых данных определяется типом
операндов в команде.
X DB
? ; Х - адрес одного байта в памяти.W
? ; Y определяет поле в
2 байта в памяти.
MOV
X, 0 ; очищение
одного байта в памяти.
MOV
Y, 0 ; очищение двух
байт в памяти.
MOV
AX, 0 ; очищение двух
байт регистра
MOV [SI],
0 ; сообщение об ошибке.
В двух последних случаях необходимо использовать
специальный оператор PTR.
<тип> PTR
<выражение>
Выражение может быть константным или адресным, а
тип - BYTE, WORD,
DWORD и т.д.
byte
PTR 0 ; 0
воспринимается как байт
word
PTR 0 ; 0
воспринимается как слово
byte
PTR op1;
один байт в памяти начиная с этого адреса
byte PTR [SI], 0
º MOV [SI], byte PTR
0
º MOV [SI], word PTR
0 ; 0 ®
((DS) +(SI))
7. Если тип обоих операндов в команде
определяется, то эти типы должны соответствовать друг другу.
AH,
500 ; сообщение об ошибке.
MOV
AX, X
; ошибка, Х - 1байт, АХ - 2 байта.
MOV
AL, R
; ошибка
MOV
AL, byte
PTR R
; (AL) = 34h
MOV
AL, byte
PTR R+1
; (AL) = 12h
К командам пересылки относят:
) команду обмена значений операндов.
XCHG
OP1, OP2
; r «
r Ú
r "
m
MOV AX, 10h
MOV
BX, 20h
XCHG
AX, BX
; (AX) = 20h,
(BX) = 10h
2) команду BSWOP
для перестановки значений байтов внутри регистра. Например, если (EAX)
= 12345678h, то после
выполнения команды BSWOP
EAX содержимое (EAX)
= 78563412h.
) команды конвертирования:
; безадресная команда, (AL)
® AX.
CWD ;( AX)
® DS:AX
(для i386 и выше)
EWDE ; ( AX)
® EAX
CDF ; (EAX)
® EDX:EAX
4) команды условной пересылки CMOVxx:
AL,
BL ; если (AL)
< (BL), то (BL)
® (AL)
5) команду загрузки адреса:
OP1,
OP2
Эта команда вычисляет адрес OP2
и пересылает первому операнду, который может быть только регистром.
BX, M[DX][DI]
9. Структура
программы на Ассемблере
архитектура
микропроцессор память ассемблер
Ассемблер - это язык программирования низкого
уровня и программа, написанная на Ассемблере, должна пройти три этапа обработки
на компьютере, как и программа, написанная на любом другом языке
программирования.этап - преобразование исходного модуля в объектный -
ассемблирование. Исходных модулей может быть 1 или несколько. II этап - с
помощью программы редактора связей объектные модули объединяются в загрузочный,
исполняемый модуль. III этап - выполнение программы.
Существует два типа исполняемых модулей
(исполняемых файлов): exe-файл
(<имя>.exe) и com-файл
(<имя>.com). В
результате выполнения второго этапа получается исполняемый exe-файл,
чтобы получить com-файл,
необходимо выполнить еще один этап обработки - преобразование exe-файла в
com-файл.
Исходный файл на Ассемблере состоит из команд и
директив. Команды преобразуются в машинные коды, реализующие алгоритм решения
задачи. Директивы описывают, каким образом необходимо выполнять ассемблирование
и объединение модулей. Они описывают форматы данных, выделяемые области памяти
для программ и данных и т.д.
Команда на Ассемблере состоит из четырех полей:
[<имя>[:]] <код операции>
[<операнды>] [;комментарии]
Поля отделяют друг от друга хотя бы одним
пробелом. В квадратных скобках указаны необязательные поля, все поля, кроме
<код операции>, могут отсутствовать. <имя> - символическое имя
Ассемблера. Имя используется в качестве метки для обращения к этой команде,
передачи управления на данную команду, [:] после имени означает, что метка
является внутренней. Код операции определяет, какое действие должен выполнить
процессор. Поле <операнды> содержит адреса данных, или данные,
участвующие в операции, а также место расположения результатов операции.
Операндов может быть от 1 до 3, они отделяются друг от друга запятой.
Комментарии отделяются кроме пробела еще и ";" и могут занимать всю
строку или часть строки.
Например:
M1
; команда безусловной передачи управления на команду с меткой M1.
-----------/-------/------------
M1: MOV
AX, BX
; пересылка содержимого регистра BX
в регистр AX.
-----------/--------/------------
В комментарии будем записывать в виде (BX) AX
Директива, как и команда, состоит из четырех
полей:
[<имя>] <код псевдооперации>
<операнды> [;комментарии]
Здесь <имя> - символическое имя
Ассемблера, <код псевдооперации> - определяет назначение директивы.
Операндов может быть различное количество и для одной директивы.
Например:
M1 DB
1, 0, 1, 0, 1
Директива DB
определяет 5 байтов памяти и заполняет 0 или 1 соответственно, адрес первого
байта определяется символическим именем М1.
M2 DB
?,?,?
Директива DB определяет три байта памяти ничем
их не заполняя, адрес первого - M2.
PP1 Proc
FAR ; директива начала
процедуры,
PP1 endp
; директива конца процедуры,
SS
Segment ; директива начала
сегмента,
SSends ; директива
конца сегмента.
Исходный модуль на Ассемблере это
последовательность строк: команд, директив и комментариев. Исходный модуль
просматривается Ассемблером, пока не встретится директива end.
Обычно программа на Ассемблере состоит из трех сегментов: сегмента стека,
сегмента данных, сегмента кода.
; сегмент стека
Sseg
Segment…
---/-----
Sseg
ends
; сегмент данных
Dseg
Segment…
----/-------
Dseg
end
; сегмент
кодаSegment…
----/-------end
start
Каждый сегмент начинается директивой начала
сегмента - Segment
и заканчивается директивой конца сегмента - ends,
в операндах директивы Segment
содержится информация о назначении сегмента, кроме того в кодовом сегменте непосредственно
за директивой Segment
должна быть специальная директива, устанавливающая соответствие между именами в
директивах Segment
и сегментными регистрами. Например,
ASSUME
SS:SSeg,
DS:DSeg,
CS:CSeg,
CS:DSeg;
Здесь на DSeg
ссылаются два сегментных регистра и DS,
и CS, это значит, что
содержимое этих сегментов будет одинаковым, т.е. адреса начала двух сегментов
данных будут одинаковыми.
Кодовый сегмент оформляется как процедура, это
может быть одна процедура или несколько последовательных процедур, или
несколько вложенных процедур.
Структура кодового сегмента с использованием
двух вложенных процедур выглядит следующим образом:
Segment…SS:SSeg, DS:DSeg, CS:CSeg,
CS:DSegProc
-------/--------Proc
------/-----endp
-----/-----endpends
В сегменте
стека
выделяется
место
под
стек.
В
сегменте данных описываются данные, используемые в программе, выделяется место
под промежуточные и окончательные результаты.
Кодовый сегмент содержит программу решения
поставленной задачи. Представим структуру программы с использованием
стандартных директив сегментации, когда кодовый сегмент реализован как две
последовательные процедуры.
; Prim1.ASM
; сегмент стека
Sseg
Segment…
DB 256 DUP(?)
Sseg
ends
; сегмент даннх
Dseg Segment…DB ‘A’DB ‘B’DB
‘C’endsSegment…SS:SSeg, DS:DSeg, CS:CSegProc FARDSAXDX, DSegDS, DXMainendpProc
NEARAL, XAX, Y
-------/------endpendsStart
Строки 1, 5, 11 - это комментарии. Кодовый
сегмент содержит две последовательные процедуры. Первая процедура - внешняя, об
этом говорит параметр FAR
в директиве proc.
Строки 15 -18 - реализуют связь с операционной
системой и определяют адрес начала сегмента данных. Строка 19 - это обращение к
внутренней процедуре Main,
строка 20, команда Ret
- возврат в ОС. Main
- внутренняя процедура, о чем говорит параметр NEAR
в директиве начала процедуры Proc. Директива end
имеет параметр Start,
определяющий точку входа в программу, т.е. команду, с которой должно начинаться
выполнение программы. Внутренняя процедура это процедура, к которой можно
обратиться только из того сегмента, в котором она содержится. К внешней
процедуре можно обратиться из любого сегмента. По умолчанию (если в директиве
начала процедуры параметр отсутствует) процедура является внутренней.
10. Алфавит,
слова, константы, выражения, переменные
Алфавит языка Ассемблер состоит из символов трех
категорий: букв латинского алфавита, арабских цифр и специальных символов.
Символические имена в Ассемблере могут состоять
из строчных и прописных букв латинского алфавита, цифр от 0 до 9 и некоторых
символов ‘_’, ‘.’, ‘?’, строчные и прописные буквы не различаются, количество
значимых символов в имени равно 31.
В программе на Ассемблере могут использоваться
константы пяти типов: целые двоичные, десятичные, шестнадцатеричные,
действительные с плавающей точкой, символьные.
Целые двоичные - это последовательности 0 и 1 со
следующим за ними символом ‘b’,
например, 10101010b или 11000011b.
Целые десятичные - это обычные десятичные числа,
возможно заканчивающиеся буквой d, например, - 125 или 78d.
Целые шестнадцатеричные числа - должны
начинаться с цифры и заканчиваются символом ‘h’,
если первая цифра шестнадцатеричного числа это - ‘A’,
‘B’, ‘C’,
‘D’, ‘E’,
‘F’, то перед ним
необходимо поставить ноль, иначе они будут восприниматься как символические
имена, например, 12ABh,
79CDh, 0AE5Ah.
Числа действительные с плавающей точкой
представляются в виде мантиссы и порядка, например, - 34.751е+02 - это 3475.1
или 0.547е-2 - это 0.00547.
Символьные данные - это последовательности
символов, заключенные в апострофы или двойные кавычки, например, 'abcd',
'a1b2c3', ‘567'.
Также, как и в языках высокого уровня, в
Ассемблере могут использоваться именованные константы. Для этого существует
специальная директива EQU.
Например,
M
EQU 27 ; директива EQU
присваивает имени М значение 27.
Переменные в Ассемблере определяются с помощью
директив определения данных и памяти, например,
1 DB
0CFh
v2 DW
34
или с помощью директивы ‘ = ’
3 = 100
v3 = v3+1
Константы в основном используются в директивах
определения или как непосредственные операнды в командах.
Выражения в Ассемблере строятся из операндов,
операторов и скобок.
Операнды - это константы или переменные.
Операторы - это знаки операций: арифметических, логических, отношений и
некоторых специальных.
Арифметические операции: ‘+’, ‘-‘, ‘*’, ‘/’, mod.
Логические операции: NOT,
AND, OR,
XOR.
Операции отношений: LT(<),
LE(£),
EQ(=), NE(¹),
GT(>), GE(³).
Специальные операции: offset
и PTR.
offset <имя>
- ее значением является смещение операнда, а операндом может быть метка или
переменная;
PTR -
определяет тип операнда, которым может быть:
BYTE = 1 байт,
WORD = 2 байт,
DWORD = 4 байт,
FWORD = 6 байт,
QWORD = 8 байт,
TWORD = 10 байт;
или тип вызова: NEAR
- ближний, FAR - дальний.
Примеры выражений: 1) 10010101b+37d
2) OP1 LT
OP2
3) (OP3 GE OP4) AND (OP5 LT OP6) 4)
27 SHL 3 ;
. Директива
определения данных и памяти
Общий вид директивы определения следующий:
[<имя>] Dx
[<операнды>] [<; комментарии>],
где x
- это один из символов: B
- определить байт, W -
определить слово, D -
определить двойное слово, F
- определить 6 байтов памяти, Q
- определить 8 байтов памяти, T
- определить 10 байтов памяти. В поле операндов может быть знак вопроса, одна
или несколько констант, разделенных запятой. Имя, если оно есть, определяет
адрес первого байта выделяемой области. Директивой выделяется указанное
количество байтов, и указанные операнды заполняют соответствующие поля памяти.
Если операндом является знак вопроса (?), то значением соответствующего поля будет
случайная величина. Примеры:
)R1
DB 0, 0, 0, 0 ;
выделено 4 байта, заполненных 0.
Адрес первого байта - R1,
второго R1+1, третьего - R1+2,
четвертого - R1+3
)R2
DB ?, ?, ? ; выделено
3 байта, заполненных случайными величинами.
) Если операндом является символическое имя IM1,
которое
соответствует смещению 03АС1h
в сегменте, то после выполнения
M
DD IM1
будет выделено 4 байта памяти, двойное слово с
адресом - М. и значением - 03АС1h.
4) Если необходимо выделить 100 байтов памяти и
заполнить их 1, то это можно сделать с помощью специального повторителя DUP.
D
DB 100 DUP
(1)
5) Определить одномерный массив слов с адресом
первого
элемента массива, именем MAS
и конкретными значениями можно так:
MAS
DW 1, 7, 35, 75, 84
6) Определение двумерного массива ARR(3,4):
Выделили 100 байтов памяти, ничем не заполняя.
В директиве определения байта (слова)
максимально допустимая константа - 255 (65535).
С помощью директивы определения байта можно
определить символьную константу длинной 255 символов, а с помощью определения
слова можно определить символьную константу, которая может содержать не более
двух символов.
12. Команда
прерывания, команды работы со стеком
С помощью команды прерывания приостанавливается
работа процессора, управление передается DOC
или BIOS и после
выполнения какой-то системной обрабатывающей программы, управление передается
команде, следующей за командой Int.
Выполняемые действия будут зависеть от операнда,
(параметры директивы команды Int)
и содержания некоторых регистров.
Например, чтобы вывести на экран символ
восклицательный знак ‘!’ необходимо выполнить три команды:
AH, 6DL, ‘!’21h ;
Команда int
с параметром 21h передает
управление обработчику функций операционной системы, который по содержимому
регистра AH определяет
какую функцию следует выполнить. Функция с номером 6 - это вывод на экран
символа, хранящегося в регистре DL.
Процессор выполняет программу вывода на экран символа и возвращается к
выполнению текущей программы, начиная с команды, следующей за int.
Для работы со стеком используются регистры SS,
SP/ESP
и BP/EBP.
Сегментный регистр SS
содержит адрес начала сегмента стека. ОС сама выбирает этот адрес и пересылает
его в регистр SS. Регистр SP
указывает на вершину стека и при добавлении элемента в стек содержимое этого
регистра уменьшается на длину операнда.
Добавить элемент в стек можно с помощью команды
PUSH
<операнд>,
где операндом может быть как регистр, так и
переменная.
Удалить элемент с вершины стека можно с помощью
операции
POP
<операнд>.
Для i186
и выше команды PUSHA/
POPA позволяют положить
в стек, удалить из стека содержимое всех регистров общего назначения в
последовательности AX,
BX, CX,
DX, SP,
BP, SI,
DI. Для i386
и выше команды PUSHAD/
POPAD позволяют положить
в стек, удалить из стека содержимое всех регистров общего назначения в
последовательности EAX,
EBX, ECX,
EDX, ESP,
EBP, ESI,
EDI.
Используя регистр указателя вазы BP,
к любому элементу стека можно обратиться следующим образом:
BP,
SP; (SP)®BP
MOV
AX, [BP+6]
; (SS:(BP+6))®AX.
Содержимое двух байтов памяти, начиная с адреса,
определенного вторым операндом, пересылается в регистр AX.
Приведем пример программы, использующей
рассмотренные команды и директивы для пересылки содержимого 4 байтов памяти из
одной области памяти в другую и вывод на экран.
Prom.ASM
Page , 120
; описание сегмента стека
SSeg Segment Para stack ‘stack’100h
DUP (?)ends
; описание
сегмента
данных
DSeg Segment Para Public ‘Data’
DAN DB ’1’, ‘3’, ‘5’, ‘7’DB 4 DUP
(?)
DSeg
endS
; кодовый сегмент оформлен как одна внешняя процедура
СSeg Segment Para
Public ‘Сode’SS:SSeg,
DS:DSeg, CS:CSeg Proc
FAR
PUSH
DS; организация связи
с ОС
XOR
AX, AX
PUSH
AX
MOV
AX, DSeg;
; загрузка адреса сегмента данных в регистр DS
MOV
DS, AX;
; пересылка данных в обратной последовательности
с выводом на экран
MOV AH, 6DL, DANREZ+3, DL21h
MOV DL, DAN+1REZ+2, DL21h
MOV DL, DAN+2REZ+1, DL21h
MOV DL, DAN+3
MOV REZ, DL21h
;
MOV AH, 4CH21hendp
ends
End
Start
Директива TITLE
определяет заголовок листинга программы, может содержать до 60 символов.
Директива NAME определяет
количество строк на странице листинга -1-й параметр и количество символов на
странице - 2-й параметр. Если какой-то из параметров отсутствует, то
используется значение по умолчанию. Если отсутствуют оба параметра, то
директива осуществляет переход на следующую страницу листинга.
Директива начала сегмента
Общий вид директивы:
<имя>Segment<ReadOnly>
<выравнивание> <тип> <размер> <’класс’>
Любой из операндов может отсутствовать. Если
<ReadOnly>
есть, то будет выведено сообщение об ошибке при попытке записи в этот сегмент.
Операнд <выравнивание> определяет
требования к адресу начала сегмента. Значениями этого операнда могут быть:
• BYTE
- адрес начала сегмента может быть любым,
• WORD
- адрес начала сегмента кратен 2,
• DWORD
- адрес начала сегмента кратен 4,
• Para
- адрес начала сегмента кратен 16 - по-умолчанию,
• Page
- адрес начала сегмента кратен 256.
Операнд <тип> определяет тип сегмента. В
сегменте стека указывается stack,
для
остальных
сегментов - Public. Если такой
параметр присутствует, то все сегменты с одним именем и различными классами
объединяются в последовательность в порядке их записи. Значение ‘Common’,
говорит, что сегменты с одним именем объединены, но не последовательно, а с
одного и того же адреса так, что общий размер сегмента будет равен не сумме, а
максимуму из них. Значение ‘Private’
означает, что этот сегмент ни с каким другим объединяться не должен.
Точечные директивы
В программе на Ассемблере могут использоваться
упрощенные (точечные) директивы, повышающие уровень программирования на
Ассемблере.
.MODEL
- директива, определяющая модель, размер выделяемой памяти для программы.
Модель определяется параметром директивы,
которым может быть:
tiny - под всю
программу выделяется 1 сегмент памяти,
small - под
данные и под программу выделяются по одному сегменту,
medium - под
данные выделяется один сегмент, под программу выделяется n
сегментов,
compact
- под программу выделяется один сегмент, под данные выделяется n
сегментов,
large - под
данные и под программу выделяется по n
сегментов,
huge - позволяет
использовать сегментов больше, чем позволяет ОП.
Пример использования точечных директив в
программе на Асс-ре.
.MODEL
.STACK
.DATADB ‘Line1’, ‘$’DB ‘Line2’,
‘$’DB ‘Line3’, ‘$’
.CODE
begin: MOV
AH, 9; номер функции
вывода строки на экран
MOV
DX, offset
St1; адрес, который
содержится в регистре DX
Int 21hDX, offset St221hAH, 4CH21h
begin
‘$’определяет конец строки, которую необходимо
вывести на экран в результате выполнения программы на экране будет:
Line1 Line2
Line3.
Если необходимо вывести результат в три строки
1
Line2
Line3,
то в сегмент данных необходимо внести изменения
DB ‘Line1’, 13, 10, ‘$’DB ‘Line2’,
0Dh, 0Ah, ‘$’3 DB
‘Line3’, ‘$’
Здесь константы 13 и 10, или в шестнадцатеричном
формате 0Dh и 0Ah
осуществляют перевод строки и возврат каретки, т.е. переход в начало следующей
строки.
Com-файлы
После обработки исходного текста программы
компилятором и редактором связей будет получен исполняемый exe-файл,
который содержит блок начальной загрузки, размером не менее 512 байт. Однако,
существует возможность создания другого вида исполнительного файла, который
может быть получен на основе exe-файла
с помощью системной обрабатывающей программы EXE2BIN.com
или его можно создать с помощью специальной среды разработки. Но не из всякого exe-файла
можно создать com-файл.
Исходный файл, для которого можно создать com-файл,
должен удовлетворять определенным требованиям. Отличия exe-файла
от com-файла:
• В com-файлах
отсутствует блок начальной загрузки и, следовательно, занимает меньше места,
чем exe-файл. exe-файл
может занимать произвольный объем ОП., com-файл
может занимать только один сегмент памяти.
• Стек создается автоматически ОС,
поэтому у пользователя нет необходимости выделять для него место.
• Исходные данные, место под промежуточные
и окончательные результаты выделяется в том же сегменте, в котором размещается
программа.
Так как вся программа содержится в одном
сегменте, перед выполнением программы все сегментные регистры содержат в
качестве значения адрес префикса программного сегмента, блока PSP
PSP - 256
байтный блок, который содержится как в exe-файле,
так и в com-файле, и
так как адрес первой исполняемой команды отстоит на 256 байтов от адреса начала
сегмента (100h), то сразу после
директивы ASSUME
используется специальная директива
org 100h,
осуществляющая обход префикса программного сегмента .
Примеры программы, удовлетворяющей требованиям
создания исполняемого com-файла.
) С использованием стандартных директив
сегментации:
Prim Сom-файл60
, 85
СSeg Segment Para ‘Сode’SS:CSeg,
DS:CSeg, CS:CSeg100h: JMP MainDB ‘String1’, 13, 10, ‘$’DB ‘String2’, ‘$’ProcAH,
9DX, St121hDX, St221hAH, 4CH21hendpends
Start
2) С использованием точечных директив
сегментации, данные размещены перед программой:
.Model tiny
.CodeMetDB ‘String1’, ‘$’: MOV AH,
09hDX, St121hAH, 4Ch21h Met
3) Фрагмент исходного текста программы для
создания com-файла,
когда данные размещены после программы:
ProcAH, 9DX, St121hAH, 4Ch21hendpDB
‘String1’, ‘$’beg
Замечания:
• Не каждый исходный файл
удовлетворяет требованиям com-файла.
• Небольшие по объему программы
рекомендуется оформлять как com-файл.
• Исходный файл, написанный как com-файл,
не может быть выполнен как EXE-файл
Арифметические операции
Сложение (вычитание) беззнаковых чисел
выполняется по правилам аналогичным сложению (вычитанию) по модулю 2k
, принятым в математике. В математике k+1
разряд теряется, отбрасывается, а в информатике, если в результате более k
разрядов, то флаг CF
устанавливается в 1.
+Y = (X+Y) mod 2k = X+Y,
CF = 0, если
X+Y<2k+Y = (X+Y) mod 2k = X+Y-2k, CF = 1, иначе
Пример: в байте
250 + 10 = (250+10) mod
28 = 260 mod
256 = 4
= 1000001002, CF
= 1, результат 000001002 = 4
X - Y
= (X-Y)
mod 2k
= X-Y,
CF = 0, если X>Y
X+Y
= (X+Y)
mod 2k
= X+2k
-Y, CF
= 1, иначе
Пример: в байте
1 - 2 = 1 - 28 - 2 = 275 - 2 = 255, CF
= 1
Сложение (вычитание) знаковых чисел сводится к
сложению (вычитанию) с использованием дополнительного кода.
= 10n
- |X|
В байте:
-1 = 256 - 1 = 255 = 111111112
= 256 - 3 = 253 = 111111012
3 + (-1) = ( 3 + (-1)) mod
256 = 258 mod 256 = 2
+ (-3) = (1 + (-3)) mod
256 = 256 - 254 = 111111102 = -2
13. Команды
сложения и вычитания в Ассемблере
В Ассемблере команды сложения:
1) ADD
OP1, OP2;
OP1+OP2®OP1
2) ADC OP1, OP2; OP1+OP2+CF®OP1
3) XADD
OP1, OP2;
для i486 и выше OP1"OP2
и затем OP1+OP2®OP1
) INC
OP1; OP1+1®OP1
В Ассемблере команда вычитания:
) SUB OP1, OP2; OP1-OP2®OP1
) SBB OP1, OP2; OP1-OP2-CF®OP1
3) DEC
OP1; OP1
- 1®OP1.
Примеры:
X = 1234AB12h, Y = 5678CD34h,+ Y =
MOV AX, 1234hBX, AB12hCX, 5678hDX,
CD34hBX, DXAX, CX- Y = SUB BX, DXAX, CX
В командах сложения и вычитания можно
использовать любые способы адресации. Команды сложения и вычитания изменяют
содержимое флагов
OF, CF,
SF, ZF,
AF, PF.
Пример1:
AL, 95hAL, 82h
h + 82h = 117h
= 100101012 82 = 100000102
2+100000102=1000101112,
CF = 1, OF = 1, SF = 0, ZF = 0, AF =
0, PF = 1.
Пример2:
AL, 9hAL, 5h
- 5 = 4 5 = 00000101 -5 = 11111011 9
= 00001001
+ (-5) = 11111011 + 00001001 =
100000100
CF = 1, OF = 0, SF = 0, ZF = 0, AF =
1, PF = 0.
Умножение и деление в Ассемблере
Умножение беззнаковых чисел.
OP2 ; OP2*(AL) Ú
(AX) Ú (EAX) ®
AX Ú DX:AX Ú
EAX.
Умножение знаковых чисел.
IMUL
OP2; аналогично MUL
IMUL
OP1, OP2
OP1 - это
всегда регистр, OP2 - непосредственный операнд, регистр или память.
При умножении результат имеет удвоенный формат
по отношению к сомножителям. Иногда точно известно, что результат может
уместиться в формат сомножителей, тогда его можно извлекать из AL,
AX, EAX.
Размер результата можно выяснить с помощью
флагов OF и CF.
Если OF
= CF = 1, то результат
занимает двойной формат,
и OF
= СF = 0 в противном
случае. Остальные флаги не изменяются.
Деление беззнаковых чисел:
DIV
OP2 ; OP2
это r
Ú m
Деление знаковых чисел.
OP2 ; OP2 это
r Ú m
В результате выполнения этих команд содержимое
регистров
(AX)
Ú (DX:AX)
Ú (EDX:EAX)
делится на указанный операнд и результат помещается в AL
Ú AX
Ú EAX,
остаток помещается в AH
Ú DX
Ú EDX.
Значение флагов не меняется, но может наступить деление на ноль, или
переполнение, например:
AX,
600
MOV
BH, 2
DIV
BH ; 300 не умещается
в AL, будет сообщение
об ошибке
Приведем пример программы с использованием
команд деления: цифры целого беззнакового байтового числа N
записать в байты памяти, начиная с адреса D
как символы.
Если цифры трехзначного числа обозначить буквами
a, b,
c - N(abc),
то получить их можно по формулам:
= N mod 10= (N div 10) mod 10
= (N div
10) div 10
Перевести цифру в символ можно так: код(i)
= код (‘0’) + I, тогда фрагмент
программы может быть таким:
------------------
MOV
BL, 10 ; делитель
MOV
AL, N
; делимое
MOV
AH, 0 ; расширяем
делимое до слова
; или CBW
AH конвертируем до
слова
DIV BL ; A L = ab, AH = cD + 2,
AHAH, 0BL ; AL = a, AH = bAL, ‘0’D, ALAH, ‘0’D+1, AH
Директивы внешних ссылок
Директивы внешних ссылок позволяют организовать
связь между различными модулями и файлами, расположенными на диске. Директива
PUBLIC
<имя>[, <имя>,…,<имя>] -
определяет указанные имена как глобальные
величины, к которым можно обратиться из других модулей. Имена это имена меток и
переменных, определенных с помощью директивы ‘=’ и EQU.
Если некоторое имя определено в модуле А как
глобальное и к нему нужно обратиться из других модулей В и С, то в этих модулях
должна быть директива вида
<имя>:<тип>[,<имя>:<тип>…]
Здесь имя тоже, что и в Public,
а тип определяется следующим образом: если <имя> - это имя переменной, то
типом может быть:
BYTE, WORD, DWORD, FWORD, QWORD,
TWORD;
если <имя> - это имя метки, то типом может
быть NEAR, FAR.
Директива EXTRN
говорит о том, что перечисленные имена являются внешними для данного модуля.
Пример:
Пусть в модуле А содержится:
TOT
------/-------
TOT
DW 0;
Чтобы обратиться из В и С к переменной TOT,
в них должна быть директива EXTRN
TOT:WORD
В Ассемблере есть возможность подключения на
этапе ассемблирования модулей, расположенных в файлах на диске с помощью
директивы INCLUDE <имя
файла>
Пример:
С:\WORK\Prim.ASM
Файл Prim.ASM,
расположенный в указанной директории, на этапе ассемблирования записывается на
место этой директивы.
Команды управления
Команды управления позволяют изменить ход
вычислительного процесса. К ним относятся команды безусловной передачи
управления, команды условной передачи управления, команды организации циклов.
Команда безусловной передачи управления имеет
вид JMP <имя>,
где имя определяет метку команды, которая будет
выполняться следующей за этой командой. Команда, на которую передается
управление, может располагаться в том же кодовом сегменте, что и команда JMP
или в другом сегменте.
JMP
M1 ; по умолчанию М1
имеет тип NEAR
Если метка содержится в другом сегменте, то в
том сегменте, в который передается управление, должна быть директива Public
M1, а в том сегменте
из которого передается управление должна быть директива - EXTRN
M1:FAR.
Кроме
того, передачу можно осуществлять с использованием прямой адресации (JMP
M1) или с
использованием косвенной адресации (JMP
[BX]).
Команда,
осуществляющая близкую передачу, занимает 3 байта памяти, а дальнюю - 5 байтов.
Но если передача осуществляется не далее чем на -128 или 127 байтов, то можно
использовать команду безусловной передачи управления JMP
Short <метка>,
занимающую 1 байт памяти.
AX, BXShort M12:
------/-------
M1: MOV
AX, CX
---------/-----------
Замечание: команда, следующая за командой
безусловной передачи управления, должна иметь метку, иначе к ней нельзя будет
возвратиться.
К командам безусловной передачи данных относятся
команды обращения к подпрограммам, процедурам, и возврат из них. Процедура
обязательно имеет тип дальности и по умолчанию тип процедуры - NEAR,
а FAR необходимо
указывать явно.
Proc
FAR
-----/-------
PP
endp
Процедура типа NEAR
может быть вызвана только из того кодового сегмента, в котором содержится ее
описание. Процедура типа FAR
Может быть вызвана из любого сегмента. Поэтому тип вызова процедуры (дальность)
определяется следующим образом: главная программа
всегда имеет тип FAR,
т.к. обращаются к ней из ОС или отладчика, если процедур несколько, и они
содержатся в одном кодовом сегменте, то все остальные, кроме главной, имеют тип
NEAR (пример 1). Если
процедура описана в кодовом сегменте с другим именем, то у нее должен быть тип FARи
должны быть использованы директивы внешних ссылок (пример 2).
Команда вызова процедуры или подпрограммы имеет
вид: CALL <имя>
;
Адресация может быть использована как прямая,
так и косвенная. При обращении к процедуре типа NEAR
в стеке сохраняется адрес возврата, адрес команды, следующей за CALL,
содержится в IP или EIP.
При обращении к процедуре типа FAR
в стеке сохраняется полный адрес возврата CS:EIP.
Возврат из процедуры реализуется с помощью команды RET
Она может иметь один из следующих видов:
RET [n]
; возврат из процедуры типа NEAR,
и из процедуры типа FAR
RETN [n]
; возврат только из процедуры типа NEAR
RETF [n]
; возврат только из процедуры типа FAR.
Параметр n
является необязательным, он определяет какое количество байтов удаляется из
стека после возврата из процедуры.
Примеры прямого и косвенного перехода
В третьем случае произойдет ошибка, если тип
метки, на которую передается управление, описан ниже по тексту программы., т.е.
вначале должно быть описание, а затем использование.
Команды условной передачи управления
Команды условной передачи управления можно разделить
на 3 группы:
• команды, используемые после команд
сравнения
• команды, используемые после команд,
отличных от команд сравнения, но реагирующие на значения флагов
JZ/JNZ
JC/JNC
JO/JNO
JS/JNS/JNP
• команды, реагирующие на начальное
значение регистра CX.
В общем виде команду условной передачи
управления можно записать так: <имя> Jx
<метка> ;<комментарий>
Здесь х - это одна, две, или три буквы
определяют условия передачи управления. Метка, указанная в поле операнда,
должна отстоять от команды не далее чем -128 ÷ +127 байт.
Примеры:
M1
; передача управления на команду с меткой М1, если ZF=1
JNE
M2 ; передача
управления на команду с меткой М2, если ZF=0
JC
M3 ; передача
управления на команду с меткой М3, если CF=1
JNC
M4 ; передача
управления на команду с меткой М4, если CF=0
ADD
AX, BX
JC
M
Если в результате сложения CF
=1, то управление передается на команду с меткой М, иначе - на команду,
следующую за JC.
AX,
BX
JZ
Met
Если результатом вычитания будет 0, то ZF
= 1 и управление передается на команду с меткой Мet.
Часто команды передачи управления используются
после команд сравнения <имя> CMP
OP1, OP2
; <комментарий>
По этой команде выполняется (OP1)
- (OP2) и результат
никуда не посылается, формируются только флаги. Команды условной передачи
управления для беззнаковых и знаковых чисел можно представить в виде таблицы:
условие
|
Для
беззнаковых чисел
|
Для
знаковых чисел
|
>
|
JA
|
JG
|
=
|
JE
|
JE
|
<
|
JB
|
JL
|
> =
|
JAE
|
JGE
|
< =
|
JBE
|
JLE
|
< >
|
JNE
|
JNE
|
Команды условной передачи управления могут
осуществлять только короткий переход, а команды бузусловной передачи управления
могут реализовать как короткую передачу так и длинную. Если необходимо
осуществить условный дальний переход, то можно использовать одну из команд
условной передачи вместе с командой условной передачи, например так:
if
AX = BX
goto m
следует заменить на:
if
AX < > BX
goto L
Goto
m ; m
- дальняя метка
--------------------
L: -----------------
; L - близкая метка
На Ассемблере это будет так:
cmp AX, BXLm
----------------------
L:
--------------------
С помощью команд jx
и jmp можно реализовать
цикл с предусловием 1) и с постусловием 2):
Команды для организации циклов
1) loop
<метка>
2) loope <
метка > ==loopz <
метка >
3) Loopne
< метка > ==loopnz
< метка >
По команде в форме 1): (CX)
= (CX) - 1 и если (CX)
< > 0, управление передается на команду с указанной меткой.
По команде в форме 2): (CX)
= (CX) - 1 и если (CX)
< > 0 и одновременно ZF
= 1, управление передается на команду с указанной меткой, т.е. цикл завершается,
если или (CX) = 0 или ZF
= 0 или (CX) = (ZF)
= 0
По команде в форме 3): (CX)
= (CX) - 1 и если (CX)
< > 0 и одновременно ZF=0,
управление передается на команду с указанной меткой, т.е. выход из цикла
существляется, если или (CX)
= 0 или ZF = 1 или
одновременно (CX) = 0 и (ZF)
= 1.
Следовательно, количество повторений цикла
должно храниться в регистре CX.
Если CX
нужно использовать внутри цикла, то содержание цикла должно начинаться командой
сохранения в стеке содержимого регистра CX,
а перед командой loop
необходимо регистр CX
восстановить. А для индексации, например, элементов массива внутри цикла можно
использовать индексный регистр:
Пример использования команд условного перехода,
сравнения и циклов.
Дана матрица целых байтовых величин, размером
4*5, необходимо подсчитать количество нулей и заменить их числом 0FFh.
Под стек отведем 256 байтов, программу оформим как две последовательные
процедуры: внешняя (FAR)-
это связь с ОС, внутренняя (NEAR)
- решение поставленной задачи.
1. title prim.asm
2. page , 132
3. ; сегмент
стека
4. Sseg segment para stack
‘stack’
. db 256 dup (?)
. Sseg ends
. ; сегмент
данных
. Dseg segment para public
‘data’
. Dan
db 0,2,5,0,,91 ;
адрес первого элемента массива Dan
10. db
4,0,0,15,47 ;
11. db
24,15,0,9,55
12. db
1,7,12,0,4
13. Dseg
ends
. ; сегмент кодов
15. Cseg segment para public ‘code’
16. essume cs: cseg, ds:dseg,
ss:sseg
. start proc far
. push DS ; для
связи
. push
AX ; с ОС
20. mov
BX, Dseg
; загрузка адреса сегмента данных
21. mov
DS, BX
; в регистр DS
22. call main
. ret
. start endp
. main proc near
. mov BX, offset Dan
. mov
DL, 0 ; счетчик нулей
в матрице
29. nz1:push CX
. mov SI, 0
. mov
CX, 5 ; количество повторений
внутреннего цикла
32. nz2: push CX
. cmp byte ptr [BX+SI], 0
. jne mz
. mov byte ptr [BX+SI], 0FFh
. inc DL
. mz: inc SI
38. pop CX
39. kz2:
loop nz2
40. add
BX, 5 ; переход к
следующей строке матрицы
41. pop
CX
42. kz1:
loop nz1
43. add
DL, ‘0’ ; вывод на
экран
44. mov
AH, 6 ; количества
нулей
45. int
21h
46. ret
. main endp
. Sseg ends
. end start
Задача решена с помощью двух вложенных циклов,
во внутреннем осуществляется просмотр элементов текущей строки (32-39),
увеличение счетчика нулей и пересылка константы 0FFh
в байт, содержащий ноль. Во внешнем цикле осуществляется переход к следующей
строке очисткой регистра SI
(строка 30 ) и увеличением регистра BX
на количество элементов в строке (40).
Физически последняя команда программы (49) в
качестве параметра указывает метку команды, с которой необходимо начинать
выполнение программы.
Директива title
задает заголовок каждой странице листинга, заголовок может содержать до 60
символов.
Директива page
устанавливает количество строк на странице листинга - 1-й параметр (здесь он
отсутствует, значит берется значение по умолчанию 57) и количество символов в
каждой строке ( здесь 132, возможно от 60 до 132, по умолчанию - 80).
Page без
параметров осуществляет перевод печати на новую страницу и увеличение на 1
номера страницы листинга. Эти директивы могут отсутствовать.
. Массивы в
Ассемблере
Массивы в языке Ассемблера описываются
директивами определения данных, возможно с использование конструкции повторения
DUP.
Например, x
dw 30 dup
( ? )
Так можно описать массив чисел, состоящий из 30
элементов длиной в слово, но в этом описании не указано как нумеруются элементы
массива, т.е. это может быть
[0..29] и x[1..30]
и x[k..29+k].
Если в задаче жестко не оговорена нумерация
элементов, то в Ассемблере удобнее считать элементы от нуля, тогда адрес любого
элемента будет записываться наиболее просто: адрес (x[i])
= x + (type
x) * i
В общем виде, когда первый элемент имеет номер k
, для одномерного массива будет: адрес (x[i])
= x + (type
x) * (i
- k)
Для двумерного массива - A[0..n-1,
0..m-1] адрес (i,j)
- го элемента можно вычислить так: адрес (A[i,j])
= A + m
* (type A)
* i + (type
A) *j
С учетом этих формул для записи адреса элемента
массива можно использовать различные способы адресации. Для описанного выше массива
слов, адрес его i-го элемента
равен: x + 2*i
= x + type
(x) * i,
т.е. адрес состоит из двух частей: постоянной x
и переменной 2 * i, зависящей
от номера элемента массива. Логично использовать адресацию прямую с
индексированием:
x - смещение, а 2*i
- в регистре модификаторе SI
или DI - x[SI]
Для двумерного массива, например:
A
DD n
DUP (m
Dup (?) )
; A[0..n-1, 0..m-1] получим:
адрес (A[i,j]) =
A + m * 4 * i + 4 *j,
Т.е. имеем в адресе постоянную часть А и две
переменных m * 4 * i
и 4 *j , которые можно хранить
в регистрах. Два модификатора есть в адресации по базе с индексированием,
например: A[BX][DI].
Запишем фрагмент программы, в которой в регистр AL
записывается количество строк матрицы X
DB 10 dup
( 20 dup (?) ), в
которых начальный элемент повторяется хотя бы один раз.
----------------------------------
mov
AL, 0 ; количество
искомых строк
mov
CX, 10 ; количество
повторение внешнего цикла
mov
BX, 0 ; начало строки
20*i
m1: push
CX
mov
AH, X[BX]
; 1-й элемент строки в AH
mov
CX, 19 ; количество
повторений внутреннего цикла
mov
DI, 0 ; номер
элемента в строке ( j )
m2: inc DI ; j = j + 1AH, X[BX][DI]
; A[i,0] = A[i,j] m2
; первый не повторился? Переход на m2
jne
L ; не было в строке
равных первому? Переход на L
inc
AL ; первый
повторился, увеличиваем счетчик строк
L: pop
CX ; восстанавливаем CX
для внешнего цикла
add
BX, 20 ; в BX
начало следующей строки
loop
m1
15. Команды
побитовой обработки данных
К командам побитовой обработки данных относятся
логические команды, команды сдвига, установки, сброса и инверсии битов.
Логические команды: and,
or, xor,
not. Для всех
логических команд, кроме not,
операнды одновременно не могут находиться в памяти, значения флажков:
OF
= CF = 0, AF
- не определен, SF,
ZF, PF
- определяются результатом команды.
Общий вид команды логического умножения:
<имя>and
OP1, OP2
; < комментарий>
По этой команде содержимое первого операнда (OP1)
логически умножается на содержимое второго операнда (OP2),
рез-т передается по адресу первого операнда. Результатом операции AND
является "истина", если оба операнда имеют значение
"истина", в остальных случаях результатом будет "ложь". Для
компьютера "истина" - это 1, а "ложь" - это 0.
Пример: (AL)
= 1011 0011, (DL) = 0000
1111,
and
AL, DL
; (AL) = 0000 0011
Второй операнд называют маской. Основным
назначением команды and
является установка в ноль с помощью маски некоторых разрядов первого операнда.
Нулевые разряды маски обнуляют соответствующие разряды первого операнда, а
единичные оставляют соответствующие разряды первого операнда без изменения.
Маску можно задавать непосредственно в команде и можно извлекать из регистра
или памяти. Например:
1) and
CX, 0FFh
; маской является константа
2) and
AX, CX;
маска содержится в регистре CX
3) and
AX, TOT;
маска в ОП по адресу (DS)
+ TOT
4) and
CX, TOT[BX+SI];
маска в ОП по адресу (DS)
+ (BX) + (SI)
+ TOT
5) and
TOT[BX+SI],
CX; в ноль
устанавливаются некоторые разряды ОП
6) and
CL, 0Fh;
в ноль устанавливаются старшие 4 разряда регистра CL
Команда логического сложения имеет вид:
<имя> or
OP1,OP2
; <комментарий>
Результатом выполнения этой команды является
поразрядное логическое сложение содержимого первого и второго операндов,
результат пересылается по адресу первого операнда. Результатом операции OR
является "ложь", если оба операнда имеют значение "ложь", в
остальных случаях результат равен "истина". Эта команда используется
для установки в 1 заданных битов 1-го операнда с помощью маски OP2.
Нулевые биты маски оставляют без изменения, а единичные устанавливают в единицу
соответствующие биты первого операнда. Например:
(AL)
= 1011 0011, (DL) = 0000
1111
or
AL, DL
; (AL) = 1011 1111
Старшие биты регистра AL
остались без изменения, а младшие все стали равными единицами. В команде могут
использоваться различные операнды:
CX,
00FFh ;
or
TAM, AL
;
or
TAM[BX][DX],
CX
Если во всех битах результата будут 0, то ZF
= 1.
Команда сложения по модулю 2 (исключающее или)
имеет вид:
<имя> xor
OP1, OP2
; <комментарий>
Результат этой операции:
1 xor
1 = 0, 0 xor 0 = 0
xor 0 = 1 0 xor
1 = 1
Например:
(AL)
= 1011 0011, маска = 000 01111
xor
AL, 0Fh
; (AL) = 1011 1100
Команда отрицания:
<имя> not
OP ;
<комментарий>
Результом выполнения команды является инверсия
значения операнда. Например,
(AL)
= 0000 0000, not
AL ; (AL)
= 1111 1111
Значения флагов не изменяются.
Примеры.
1) xor
AX, AX
; обнуляет регистр AX
быстрее, чем команды mov
и sub
2) Xor
AX, BX;
меняет местами значения AX
и BX
xor
BX, AX
; быстрее, чем команда
xor
AX, BX;
xchg AX,
BX
3) Определить количество задолжников в
группе из 25 студентов. Информация о студентах содержится в массиве байтов X
DB 25 DUP
(?), причем в младших 4 битах каждого байта содержатся оценки, т.е. 1 - сдал
экзамен, 0 - "хвост". В DL
сохраним количество задолжников.
-----------------------------
mov DL, 0SI, 0 ; i = 0
CX, 25 ; количество
повторений цикла
nz: mov AL, X[SI]
and
AL, 0Fh
; обнуляем старшую часть байта
xor
AL, 0Fh
;
jz
m; ZF
= 1, хвостов нет, передаем на повторение цикла
inc
DL; увеличиваем
количество задолжников
m: inc
SI;переходим к
следующему студенту
loop nzDL, "0"AH, 6
21h
--------------------------
Команды сдвига
Формат команд арифметического и логического
сдвига можно представить так:<имя> sXY
OP1, OP2
; <комментарий>
Здесь X
- h или a,
Y - l
или r; OP1
- r или m,
OP2 - r
или CL
И для всех команд сдвига в CL
используются только 5 младших разрядов, принимающих значения от 0 до 31. При
сдвиге на один разряд:
Здесь знаковый бит распространяется на
сдвигаемые разряды. Например,
(AL)
= 11010101
sar
AL, 1 ; (AL)
= 11101010 и CF = 1
Сдвиги больше, чем на 1, эквивалентны
соответствующим сдвигам на 1, выполненным последовательно.
Сдвиги повышенной точности для i186
и выше:
OP1, OP2, OP3 ;OP1, OP2, OP3 ;
Содержимое первого операнда (OP1)
сдвигается на (OP3)
разрядов также, как и в командах shr
и shl но бит, вышедший
за разрядную сетку, не обнуляется, а заполняется содержимым второго операнда,
которым может быть только регистр.
Циклические сдвиги:
После выполнения команды циклического сдвига CF
всегда равен последнему биту, вышедшему за пределы приемника
Циклические сдвиги с переносом содержимого
флажка CF:
Для всех команд сдвига флаги ZF,
SF, PF
устанавливаются в соответствии с результатом. AF
- не определен. OF
- не определен при сдвигах на несколько разрядов, при сдвиге на 1 разряд в
зависимости от команды: - для циклических команд повышенной точности и sal
, shl флаг OF
= 1, если после сдвига старший бит изменился; - после sar
OF = 0; - после shr
OF = значению
старшего бита исходного числа.
16. Структуры в
Ассемблере
Структура состоит из полей-данных различного
типа и длины, занимая последовательные байты памяти. Чтобы использовать
переменные типа структура, необходимо вначале описать тип структуры, а затем
описать переменные такого типа. Описание типа структуры:
<имя типа> struc
<описание поля>
-----------------------
<описание поля>
<имя типа> ends
<имя типа> - это идентификатор типа
структуры, struc и ends
- директивы, причем <имя типа> в директиве ends
также обязательно, так как такой директивой заканчивается сегмент. Для описания
полей используются директивы определения DB,
DW, DD
и т.д. Имя, указанное в этих директивах, является именем поля, но имена полей
не локализованы внутри структуры, поэтому они должны быть уникальными в рамках
всей программы, кроме того, поля не могут быть структурами - не допускаются
вложенные структуры.
Например,
TData
struc ; data
- идентификатор типа
y DW 2000DB ?DB 28ends ;
y, m, d - имена
полей. Значения, указанные в поле операндов директив DW и DB ,
называются значениями полей, принятыми по умолчанию, ? - означает, что значения
по умолчанию нет.
На основании описания типа в программу ничего не
записывается и память не выделяется. Описание типа может располагаться в любом
месте программы, но только до описания переменных данного типа. На основании
описания переменных Ассемблером выделяется память в соответствии с описанием
типа в последовательных ячейках, так что в нашем случае размещение полей можно представить
так:
Описание переменных типа структуры
осуществляется с помощью директивы вида: имя
переменной имя типа <начальные значения>
Здесь уголки не метасимволы, а реальные символы
языка, внутри которых через запятую указываются начальные значения полей.
Начальным значение может быть: 1) ? 2) выражение
3) строка 4) пусто.
Например:
Идентификатор типа TData
используется как директива для описания переменных так же, как используются
стандартные директивы DB,
DW и т.д. Если
начальные значения не будут умещаться в отведенное ему при описании типа поле,
то будет фиксироваться ошибка. Приоритетными являются начальные значения полей,
указанные при описании переменных, т.е. если при описании переменной для поля
указан ?, или какое-либо значение, то значения этих полей по умолчанию
игнорируются.
Правила использования начальных значений и
значений по умолчанию:
) Если в поле переменной указан знак ?, то это
поле не имеет начального значения, даже если это поле имеет значение по
умолчанию (поле y переменной dt1);
) Если в поле переменной указано выражение или строка,
то значение этого выражения или сама строка становится начальным значением
этого поля (поля m и d
переменной dt1 и поле y
переменной dt2);
) Если начальное значение поля переменной
"пусто" - ничего не указано при описании переменной, то в качестве начального
устанавливается значение по умолчание - значение, указанное при описании типа,
если же в этом поле при описании типа стоит знак ?, то данное поле не имеет
никакого начального значения (поля m
переменных dt2 и dt3).
Значения по умолчанию устанавливаются для тех
полей, которые являются одинаковыми для нескольких переменных одного типа,
например, год поступления на факультет одинаков для группы студентов. Любая
переменная может изменять свое значение в процессе выполнения программы и
поэтому структура может не иметь как значений по умолчанию, так и начальных
значений.
Отсутствие начального значения отмечается
запятой. Если отсутствуют начальные значения нескольких последних полей, то
запятые можно не ставить. Если отсутствует значение первого поля или полей,
расположенных в середине списка полей, то запятые опускать нельзя. Например:
4 TData
<1980, ,> можно заменить на dt4
TData <1980>
dt5 TData
<, , > нельзя заменить на dt5
TData < 5 >.
Если отсутствуют все начальные значения,
опускаются все запятые, но угловые скобки сохраняются:
dt6
TData < >
При описании переменных, каждая переменная
описывается отдельной переменной, но можно описать массив структур, для этого в
директиве описания переменной указывается несколько операндов и (или)
конструкция повторения DUP.
Например: dst
TData <, 4, 1>, 25
DUP (<>)
Описан массив из 26 элементов типа Data,
и первый элемент (первая структура) будет иметь начальные значения 2000, 4, 1,
а все остальные 25 в качестве начальных будут иметь значения, принятые по
умолчанию: 2000, ?, 28. Адрес первой структуры определяется именем dst,
второй - (dst + 4),
третьей - (dst + 8) и т.д
Работать с полями структуры можно так же, как с
полями переменной комбинированного типа в языках высокого уровня:
<имя
переменной > . < имя поля>
Например, dt1.y,
dt2.m,
dt3.d
Ассемблер приписывает имени типа и имени
переменной размер (тип), равный количеству байтов, занимаемых структурой
TData
= type dt1
= 4
И это можно использовать при программировании,
например, так:
; выполнить побайтовую пересылку dt1
в dt2
mov
CX, type
TData ; количество
повторений в CX
mov
SI, 0 ; i
= 0
m:mov
AL, byte
ptr dt1[si]
; побайтовая пересылка
mov byte ptr dt2[si], AL ; dt1 в
dt2inc SI ; i = i+1 m
;
использование byte
ptr обязательно, так
как
Точка, указанная при обращении к полю, это
оператор Ассемблера, который вычисляет адрес по формуле:
<адресное выражение> + <смещение поля в
структуре>
Тип полученного адреса совпадает с типом поля,
т.е.
(dt1.m)
= type m
= byte
Адресное выражение может быть любой сложности,
например:
1) mov AX, (dts+8).y
2) mov SI, 8
inc (dts[SI]).m ; Aисп
= (dts + [SI]).m = (dts + 8).m
3) lea BX, dt1
mov [BX].d, 10 ; Aисп
= [BX] + d = dt1.d
Замечания:
type (dts[SI]).m = type (dts[SI].m)
= 1, но
type dts[SI].m = type dts = 4
Если при описании типа структуры в директиве,
описывающей некоторое поле, содержится несколько операндов или конструкция
повторения, то при описании переменной этого типа данное поле не может иметь
начального значения и не может быть определено знаком ?, это поле должно быть
пустым.
Одно исключение: если поле описано как строка,
то оно может иметь начальным значением строку той же длины или меньшей, в
последнем случае строка дополняется справа пробелами.
Например:
student strucDB 10 DUP (?) ; фамилия
DB " *******
" ; имя
gr
DW ? ; группа
oz
DB 5, 5, 5 ; оценки
student
ends
Описание переменных:
1 student
<"Petrov", >
; нельзя, т.к. поле f не строка
st2 student
< , "Petr", 112,
> ; можно, f - не имеет
начального значения
st3 student
< , "Aleksandra"
> ; нельзя, в i 10 символов,
а допустимо не больше 7.
Примеры программ с использованием данных типа
структура.
Прямое обращение к полям структуры:
; prim1.asm
.model
tiny
. code
org 100h
; обход 256 байтного префикса пр-го сегмента - PSP…
Start: mov AH, 9DX, offset message21h
;DX, st1.s21hDX, st1.f21hDX,
st1.i21h
;DB "
hello",0dh,0ah,"$" struc
; описание типа структуры
s
DB "student","$"
f DB "Ivanov
","$"DB "Ivan ","$"
ends
st1 tst
< > ; описание переменной типа tst
end
start
Все сегментные регистры вначале выполнения программы
содержат адрес блока PSP,
который резервируется непосредственно перед EXE
и COM файлами. Смещением
для 1-ой команды программы является адрес 100h.
Переход на первую выполняемую команду происходит с помощью директивы ORG
100h.
Обращение к полям структуры в цикле.
;Prim2.asm
.model
tiny
.code
org 100h
; обход 256 байтного префикса программного сегмента
Start: mov AH, 9
mov DX, offset message21hSI, 0CX,
3:lea DX, st1[SI]21hSI, 9m1
message DB
"hello",0dh,0ah,"$"
tst
struc; описание типа
структуры
s
DB "student","$"
f DB "Ivanov
","$"DB "Ivan ","$"
tst endstst < >start
3.asm
- обращение к полям структур: цикл в цикле для работы с 2-мя записями
.model
tiny
. code
org 100h
; обход 256 байтного префикса пр-го сегмента - PSP
Start: mov AH, 9
mov DX, offset message
21h
lea
BX, st1
; адрес первой записи в BX
mov CX, 2:push CXSI, 0
CX, 3
m1: push
CX
lea
DX, [BX]
[SI] ; адресация по
базе с индексированием
int 21h
add
SI, 9 ; переход к
следующему полю
pop
CX
loop
m1
add
BX, type
tst ;переход к
следующей записи ; BX
+ количество байтов, занимаемой структурой типа tst
pop CXm2
message DB
"hello",0dh,0ah,"$"
struc ; описание типа
структуры
s
DB ?
f DB ?DB ?
tst endstst < "student
$","Inanov $","Ivan, $" >tst < "student
$","Petrov $","Petr, $" >
nd
start
Результат работы программы:
helloIvanov Ivan, student Petrov
Petr
17. Записи в
Ассемблере
Запись - это упакованные данные, которые
занимают не отдельные, полные ячейки памяти (байты или слова), а части ячеек.
Запись в Ассемблере занимает байт или слово (другие размеры ячеек для записи не
допускаются), а поля записи - это группы последовательных битов. Поля должны
быть прижаты друг к другу, между ними не должно быть пробелов. Размер поля в
битах может быть любым, но в сумме размер всех полей не должен быть больше 16.
Сумма размеров всех полей называется размером записи. Если размер записи меньше
8 или 16, то поля прижимаются к правой границе ячейки, оставшиеся левые биты
равны нулю, но к записи не относятся и не рассматриваются. Поля имеют имена, но
обращаться к ним по именам нельзя, так как наименьший адресуемый элемент памяти
это байт. Для работы с записью необходимо описать вначале тип записи, а затем
описать переменные этого типа.
Описание типа может располагаться в любом месте
программы, но до описания переменных этого типа. Директива описания типа записи
имеет вид:
<имя типа записи > record
<поле> {, <поле>}
<поле> ::= <имя поля> :
<размер> [= <выражение>]
Здесь <размер> и <выражение> - это
константные выражения.
<размер> определяет размер поля в битах,
<выражение> определяет значения поля по умолчанию. Знак ? Не допускается.
Например:
Год (Y),
записанный двумя последними цифрами, удовлетворяет соотношению: 26 <
Y max
= 99 < 27 , а это значит, что для хранения года достаточно 7
битов.
Имена полей, также как и в структурах, должны
быть уникальными в рамках всей программы, в описании они перечисляются слева
направо. В описании поля <выражение>
может отсутствовать, если оно есть, то его значение должно умещаться в
отведенный ему размер в битах. Если для некоторого поля выражение отсутствует,
то его значение по умолчанию равно нулю, не определенных полей не может быть.
Определенное директивой record
имя типа (Trec, TData)
используется далее как директива для описания переменных - записей такого типа.
имя записи имя типа записи <начальные
значения>,
Угловые скобки здесь не метасимволы, а символы
языка, внутри которых через запятую указываются начальные значения полей.
Начальными значениями могут быть:
) константное выражение, 2) знак ?, 3) пусто
В отличие от структуры, знак ? Определяет
нулевое начальное значение, а "пусто", как и в структуре, определяет
начальное значение равным значению по умолчанию. Например:
Так же, как и для структур:
1 TData
< 00, , > == Dat1
TData < 00 >
Dat2 TData
< , , > == Dat2
TData < >
Одной директивой можно описать массив записей,
используя несколько параметров в поле операндов или конструкцию повторения,
например,
TData
100 Dup ( < >
)
Описано 100 записей с начальными значениями,
равными принятым по умолчанию.
Со всей записью в целом можно работать как
обычно с байтами или со словами, т.е. можно реализовать присваивание Rec1
= Rec2 :
mov
AL, Rec2
mov
Rec1, AL
Для работы с отдельными полями записи существуют
специальные операторы width
и mask. Оператор width
имеет вид:
<имя поля записи>
width <имя
записи или имя типа записи>
Значением оператора width
является размер в битах поля или всей записи в зависимости от операнда.
Оператор mask
имеет вид:
<имя поля записи>
Mask <имя
записи или имя типа записи>
Значением этого оператора является
"маска" - это байт или слово, в зависимости от размера записи,
содержащее единицы в тех разрядах, которые принадлежат полю или всей записи,
указанных в качестве операнда, и нули в остальных, не используемых разрядах.
Например:
A = 00111000bB = 00000111bY =
1111111000000000bRec1 = mask TRec = 00111111b
Этот оператор используется для выделения полей
записи, например, чтобы выявить всех родившихся 1-го числа, придется выделять
поле D и сравнивать его
значение с 1-ей.
mov AX, Dat1AX, mask DAX,
1yes:--------------------
------------------------ jmp
m1
yes:
------------------------
При работе с записями, ассемблер имени любого
поля приписывает в качестве значения число, на которое нужно сдвинуть вправо
это поле, чтобы оно оказалось прижатым к правой границе ячейки, занимаемой
записью. Так значением поля D
для записи типа TData
является ноль, для поля M
- 5, для поля Y - 9. Значения имен
полей используются в командах сдвига, например, определить родившихся в апреле
можно так:
AX, Dat ; Ax = Y M DAX, mask M ; AX
= 0 M 0CL, M ; CL = 5Ax, CL ; AX = 0 0 MAX, 4 ; M = 4 ?yes: ------------------
m1
yes:
--------------------------
18. Работа с
подпрограммами в Ассемблере
Программа, оформленная как процедура, к которой
обращение происходит из ОС, заканчивается командой возврата ret.
Подпрограмма (ПП), как вспомогательный алгоритм, к которому возможно
многократное обращение с помощью команды call,
тоже оформляется как процедура с помощью директив proc
и endp. Структуру
процедуры можно оформить так:
<имя процедуры> proc
<параметры>
<тело процедуры>
ret
<имя процедуры> endp
В Ассемблере один тип подпрограмм - процедура.