Mov DX, offset message
Int 21h
Mov SI, 0
Mov CX, 3
m1: lea DX, st1[SI]
Int 21h
Add SI, 9
Loop m1
Ret
message DB “hello”,0dh,0ah,”$”
Tst struc ; описание типа структуры
s DB “student ”,”$”
f DB “Ivanov “,”$”
i DB “Ivan “,”$”
Tst ends
st1 tst < >
End start
Prim3.asm – обращение к полям структур: цикл в цикле для работы с 2-мя записями
.model tiny
.code
org 100h ; обход блока PSP
Start: mov AH, 9
mov DX, offset message
int 21h
lea BX, st1 ; адрес первой записи в BX
mov CX, 2 ; количество повторений внешнего цикла
m2: push CX
mov SI, 0
mov 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 CX
loop m2
ret
message DB “hello”,0dh,0ah,”$”
tst struc ; описание типа структуры
s DB ?
f DB ?
i DB ?
tst ends
st1 tst < “student $”,”Inanov $”,”Ivan, $” >
st2 tst < “student $”,”Petrov $”,”Petr, $” >
end start
Результат работы программы:
hello
student Ivanov Ivan, student Petrov Petr
10) Записи в Ассемблере, их описание и использование.
Запись – это упакованные данные, которые занимают не отдельные, полные ячейки памяти (байты или слова), а части ячеек.
Запись в Ассемблере занимает байт или слово (другие размеры ячеек для записи не допускаются), а поля записи - это группы последовательно расположенных битов этой ячейки (байта или слова).
Поля должны быть прижаты друг к другу, между ними не должно быть пробелов.
Размер поля в битах может быть любым, но в сумме размер всех полей не должен быть больше 16.
Сумма размеров всех полей называется размером записи.
Если размер записи меньше 8 битов, то под запись 1 байт, если 8<x<=16, то – 2 байта. Если сумма полей меньше байта или двух байт, то поля прижимаются к правой границе ячейки, оставшиеся левые биты равны нулю, но к записи не относятся и не рассматриваются.
Поля имеют имена, но обращаться к ним по именам нельзя, так как наименьший адресуемый элемент памяти это байт.
Для работы с записью необходимо описать вначале тип записи, а затем описать переменные этого типа.
Описание типа может располагаться в любом месте программы, но до описания переменных этого типа.
Директива описания типа записи имеет вид:
<имя типа записи > record <поле> {, <поле>}
<поле> :: = <имя поля> : <размер> [= <выражение>]
Здесь <размер> и <выражение> - это константные выражения.
<размер> определяет размер поля в битах, <выражение> определяет значение поля по умолчанию. Знак ? не допускается.
Например: графическое представление
TRec record A : 3, B : 3 = 7 7 6 А B имена полей
0 7
3 3 размер в битах
TData record Y : 7, M : 4, D : 5 Y M D
0 0 0
7 4 5
Год, записанный двумя последними цифрами 26 < Y max = 99 < 27
Имена полей, также как и в структурах, должны быть уникальными в рамках всей программы, в описании они перечисляются слева направо. <выражение> может отсутствовать, если оно есть, то его значение должно умещаться в отведенный ему размер в битах. Если для некоторого поля выражение отсутствует, то его значение по умолчанию равно нулю, не определенных полей не может быть.
Определенное директивой record имя типа (Trec, TData) используется далее как директива для описания переменных –записей такого типа.
имя записи имя типа записи <начальные значения>,
угловые скобки здесь не метасимволы, а символы языка, внутри которых через запятую указываются начальные значения полей.
Начальными значениями могут быть:
1) константное выражение, 2) знак ?, 3) пусто
В отличие от структуры, знак ? определяет нулевое начальное значение, а «пусто», как и в структуре, определяет начальное значение равным значению по умолчанию. Например:
Rec1 TRec < 3, > ; 7 6 A B 0
0 0 3 7
Rec2 TRec < , ? > 0 0 0 0
Dat1 TData < 46, 9, 4 > ; 15 Y M D 0
46 9 4
также , как и для структур:
Dat1 TData < 00, , > == Dat1 TData < 00 >
Dat2 TData < , , > == Dat2 TData < >
Одной директивой можно описать массив записей, используя несколько параметров в поле операндов или конструкцию повторения, например,
MDat TData 100 Dup ( < > )
Описали 100 записей с начальными значениями, равными принятыми по умолчанию.
Со всей записью в целом можно работать как обычно с байтами или со словами, т.е. можно реализовать присваивание Rec1 = Rec2 :
Mov AL, Rec2
Mov Rec1, AL
Для работы с отдельными полями записи существуют специальные операторы width и mask.
width <имя поля записи>
width <имя записи или имя типа записи>
Значением оператора width является размер в битах поля или всей записи в зависимости от операнда.
оператор mask имеет вид:
Mask <имя поля записи>
Mask <имя записи или имя типа записи>
Значением этого оператора является «маска» - это байт или слово, в зависимости от размера записи, содержащее единицы в тех разрядах, которые принадлежат полю или всей записи, указанных в качестве операнда, и нули в остальных, не используемых разрядах. Например:
mask A = 00111000b
mask B = 00000111b
mask Y = 1111111000000000b
mask Rec1 = mask TRec = 00111111b
Этот оператор используется для выделения полей записи.
Пример. Выявить всех родившихся 1-го числа, для этого придется выделять поле D и сравнивать
его значение с 1-ей.
m1: -------------------------
mov AX, Dat1
and AX, mask D
cmp AX, 1
je yes
no: ---------------------
---------------------
jmp m1
yes: ------------------------
При работе с записями, ассемблер имени любого поля приписывает в качестве значения число, на которое нужно сдвинуть вправо это поле, чтобы оно оказалось прижатым к правой границе ячейки, занимаемой записью. Так значением поля D для записи типа
TData является ноль,
для поля M – 5, для поля Y – 9.
Значения имен полей используются в командах сдвига, например, определить родившихся в апреле можно так:
m1: ----------------------------
mov AX, Dat ; AX = Y M D
and AX, mask M ; AX = 0 M 0
mov CL, M ; CL = 5
shr AX, CL ; AX = 0 0 M
cmp AX, 4 ; M = 4 ?
je yes
no: ------------------
jmp m1
----------------------
yes: --------------------------
28) Работа со строками переменной длины в Ассемблере.
Строка в языке Ассемблера может быть реализована по аналогии с тем, как это сделано в языке С/С++ и как в языке Паскаль. В С/С++ за последним символом строки располагают специальный символ, являющийся признаком конца строки. Изменение длины строки сопровождается переносом этого символа. Недостатком такого представления строк переменной длины является то, что, например, для сравнения строк S1 и S2, длиной 500 и 1000 символов необходимо выполнить может быть 500 сравнений, хотя зная, что длина их различна, их можно было совсем не сравнивать. В Паскале строка представляется так:
S n s1 s2 …….. Sn …
Где n – текущая длина. Сколько места необходимо отводить под значение длины строки n – зависит от максимально возможной длины. Если она может состоять не более, чем из 255 символов, то под n достаточно одного байта. Тогда текущая длина строки содержится по адресу S, а ее i-ый символ по адресу S + i. Строку из 200 символов можно описать так:
S DB 201 dup (?)
Пример 3. Удалить из строки S первое вхождение символа звездочка.
--------------------------------------------------
; поиск ‘ * ‘
push DS ;
pop ES ; (ES) = (DS)
lea DI, S + 1; ES:DI = адресу S[1]
CLD ; просмотр вперед
mov CL, S ; текущая длина строки
mov CH, 0 ; в CX
mov AL, ‘ * ‘
repne scasb ; поиск ‘ * ‘ в S
jne finish ; ‘ * ‘ в S нет на метку finish
; удаление ‘ * ‘ из S, сдвинуть S на 1 символ Si = Si+1
mov SI, DI ; DS:SI = адресу, откуда начинать пересылку
dec DI ; ES:DI = куда пересылать
rep movsb ; сдвиг «хвоста» S на 1 позицию влево
dec S ; уменьшить на 1 текущую длину
finish --------------------------
24) Макросредства в языке Ассемблере – блоки повторения.
Макросредства называют самым мощным средством прогрммирования в Ассемблере. Они позволяют изменять, модифицировать текст программы на Ассемблере в процессе выполнения программы, если используются директивы условного ассемблирования.
К макросредствам относят: блоки повторений, макросы, директивы условной генерации.
Программы, написанные на макроязыке, транслируются в два этапа. Сначала она переводится на «чистый» язык Ассемблера, т.е. преобразуется к виду, в котором нет никаких макросредств, этот этап называют макрогенерацией. Затем выполняется ассемблирование - перевод в машинные коды (т.е. на первом этапе раскрываются все макросредства, то есть текст становится без макросов, - макрогенерация или предпроцессорная обработка, а на втором этапе – перевод в коды машины). Макрогенерацию называют ещё препроцессорной обработкой.
Блоки повторения в процессе макрогенерации заменяются указанной последовательностью команд столько раз, сколько задано в заголовке блока, причем набор команд может повторяться в неизменном или модифицированном виде, в зависимости от вида заголовка блока. Набор команд повторяется n раз в том месте программы, где указан блок повторения.
Макросы более похожие на ПП. Аналогично ПП существует описание макроса и обращение к нему. Описание макроса называют макроопределением, а обращение - макрокомандой. Процесс замены макрокоманды на макрос - макроподстановкой, а результат этой подстановки - макрорасширением.
Макроопределение не порождает никаких машинных команд, оно должно предшествовать первой макрокоманде, использующей это макроопределение, и может располагаться как непосредственно в тексте программы, так и может быть подключено из другого файла с помощью директивы
INCLUDE <имя файла>.
Основное отличие макроса от процедуры заключается, во-первых, в том, что при обращении к ПП управление передаётся на участок памяти, в котором содержится описание ПП, а при обращении к макросу его тело (макроопределение) вставляется на место макрокоманды, т.е. сколько раз мы обратимся к макросу, сколько макрокоманд будет в программе, столько раз повторится макроопределение, вернее, макрорасширение. Макрос «размножается», увеличивая размер программы. Таким образом, применение процедур дает выигрыш по памяти, но использование макросов дает выигрыш по времени, т.к. нет необходимости передавать управление в ПП и обратно (CALL и RET), а также организовывать передачу параметров.
Рекомендация: если повторяются большие фрагменты программ, лучше использовать процедуры, если относительно небольшие, то макросы.
Второе отличие заключается в том, что текст процедуры неизменен, а содержание макрорасширения зависит от параметров макрокоманды, если используются директивы условной генерации, и тогда это существенно.
Общий вид блока повторений:
<заголовок>
<тело>
Endm
<тело> - любое количество любых операторов, предложений, в том числе и блоков повторений.
endm определяет конец тела блока. Количество повторений тела и способ модификаций тела блока зависит от заголовка.
Возможны следующие заголовки:
1) REPT n ; n - константное выражение
Оно может быть вычислено на этапе макрогенерации, в результате которого n копий тела блока записывается в данном месте программы на Ассемблере. Например:
В исходном тексте После макрогенерации на этом месте
N EQU 8 N EQU 8
REPT N-6 DB 0,1
DB 0,1 DW ?
DW ? DB 0,1
Еndm DW ?
Для создания массива с начальными значениями от 0 до OFFH
достаточно написать блок повторений:
n = 1
mas DB 0 ;имя массива mas
Rept 255 ;начало блока
DB n
n = n + 1
Endm
2) Второй вид заголовка:
IRP P , <V1,V2,…Vk> ;< и > обязательные символы
<тело> ;тело повторяется k раз так, что в i-той копии
Еndm
формальный параметр Р замещается фактическим параметром Vi.
Формальный параметр Р - это локальное имя, не имеющее
смысла вне блока. Если оно совпадает с именем другого какого-
либо объекта программы, то в теле блока это просто имя, а не
этот объект. Например:После макрогенерации
1) IRP reg, <AX, BX, CX, SI> push AX
Push reg push BX
Endm push CX
Push SI
2) IRP BX , <5,7,9> add AX , 5
add AX, BX → add AX , 7
Endm add AX , 9
Здесь ВХ - символическое имя, но не имя регистра ВХ.
Причём, замена формального параметра на фактический - это просто
текстовые замены, один участок программы Р заменяется на другой –
Vi , т.е. Р может обозначать любую часть предложения или все
предложение, лишь бы после замены Р на Vi получилось правильное
предложение языка Ассемблер.
3) IRP R , <dec word ptr, L: inc word ptr>
R W dec word ptr W
jmp M → jmp M