Обзорная лекция № 37 для студентов специальности «Прикладная математика»

advertisement
Обзорная лекция № 37
для студентов специальности «Прикладная математика»
доцента кафедры ИВТ, к.т.н. Ливак Е.Н.
МАШИННО-ОРИЕНТИРОВАННЫЙ ЯЗЫК ASSEMBLER
Основные понятия, факты
Назначение. Связь с языками высокого уровня. Директивы сегментации.
Простые типы данных. Команды обмена данными. Арифметические и
логические команды. Команды сдвига. Команды передачи управления. Работа
со стеком. Макросредства. Процедуры.
Навыки и умения
Разработка программ на языке Assembler с использованием пакета TASM.
Использование подпрограмм и макросредств. Отладка программ с помощью
автономного отладчика Turbo Debugger.
ПОДРОБНЕЕ см.
1. Юров В., Хорошенко С. Assembler: учебный курс. – СПб: Питер Ком, 1999.
– 665 с.
2. Пильщиков В.И. Программирование на языке ассемблера IBM PC. – М.:
«ДИАЛОГ-МИФИ», 1999. –288 с.
3. Юров В. Assembler: специальный справочник – СПб: Питер, 2000. – 496 с.
4. Пустоваров В.И. Ассемблер: программирование и анализ корректности
машинных программ: - К.: Издательская группа BHV, 2000. – 480 с.
«Интересно проследить, начиная со времени появления первых компьютеров и
заканчивая сегодняшним днем, за трансформациями представлений о языке
ассемблера у программистов.
Когда-то ассемблер был языком, без знания которого нельзя было
заставить компьютер сделать что-либо полезное. Постепенно ситуация
менялась. Появлялись более удобные средства общения с компьютером. Но, в
отличие от других языков, ассемблер не умирал, более того он не мог сделать
этого в принципе. Почему?
Если коротко, то язык ассемблера — это символическое представление
машинного языка. Все процессы в машине на самом низком, аппаратном уровне
приводятся в действие только командами (инструкциями) машинного языка.
Отсюда понятно, что, несмотря на общее название, язык ассемблера для
каждого типа компьютера свой. Это касается и внешнего вида программ,
написанных на ассемблере, и идей, отражением которых этот язык является. …
так как язык ассемблера для компьютера “родной”, то и самая эффективная
программа может быть написана только на нем (при условии, что ее пишет
квалифицированный программист). Здесь есть одно маленькое “но”: это очень
трудоемкий, требующий большого внимания и практического опыта процесс.
Поэтому реально на ассемблере пишут в основном программы, которые должны
обеспечить эффективную работу с аппаратной частью. Иногда на ассемблере
пишутся критичные по времени выполнения или расходованию памяти участки
программы.
Впоследствии они оформляются в виде подпрограмм и совмещаются с
кодом на языке высокого уровня.» [1]
Директивы сегментации
Напомним, что микропроцессор имеет шесть сегментных регистров,
посредством которых может одновременно работать:
•с одним сегментом кода;
•с одним сегментом стека;
•с одним сегментом данных;
•с тремя дополнительными сегментами данных.
Еще раз напомним, что физически сегмент представляет собой область памяти,
занятую командами и (или) данными, адреса которых вычисляются
относительно значения в соответствующем сегментном регистре.
Важно отметить, что функциональное назначение сегмента несколько шире, чем
простое разбиение программы на блоки кода, данных и стека. Сегментация
является частью более общего механизма, связанного с концепцией модульного
программирования. Она предполагает унификацию оформления объектных
модулей, создаваемых компилятором, в том числе с разных языков
программирования. Это позволяет объединять программы, написанные на
разных языках.
Сегменты на ассемблере описываются с помощью директивы SEGMENT.
Синтаксическое описание сегмента представляет собой следующую
конструкцию
<имя сегмента> SEGMENT [тип выравнивания] [ тип комбинирования]
[класс сегмента] [ тип размера сегмента]
<тело сегмента>
<имя сегмента> ENDS
У параметров много возможных значений.
А с помощью директивы ASSUME можно сообщить транслятору какой сегмент,
к какому сегментному регистру привязан.
Формат директивы:
ASSUME <сегментный регистр>: <имя сегмента>
Директивы SEGMENT и ASSUME - стандартные директивы сегментации.
Описание этих директив достаточно сложное и требует серьезного знания
ассемблера и архитектуры, поэтому
для простых программ, содержащих по одному сегменту для кода, данных и
стека обычно используются упрощенные описания соответствующих сегментов.
Трансляторы MASM и TASM предоставляют возможность использования
упрощенных директив сегментации (вместо SEGMENT).
! Стандартные и упрощенные директивы сегментации не исключают друг
друга.
! Стандартные директивы используются, когда программист желает получить
полный контроль над размещением сегментов в памяти и их комбинированием с
сегментами других модулей.
! Упрощенные директивы целесообразно использовать
1) для простых программ
2) программ, предназначенных для связывания с программными
модулями, написанными на языках высокого уровня (это позволяет
компоновщику эффективно связывать модули разных языков за счет
стандартизации связей и управления).
Упрощенные директивы определения сегмента
Формат директивы Формат директивы
Назначение
(режим MASM)
(режим IDEAL)
.CODE [имя]
CODESEG[имя]
Начало или продолжение сегмента
кода
.DATA
DATASEG
Начало или продолжение сегмента
инициализированных
данных. Также используется для определения
данных типа near
.STACK [размер] STACK [размер] Начало или продолжение сегмента стека
модуля. Параметр [размер] задает размер стека
______
.CONST
CONST Начало или продолжение сегмента постоянных
данных (констант) модуля
.DATA?
UDATASEG Начало или продолжение сегмента
неинициализированных данных. Также используется для определения данных
типа near
.FARDATA [имя] FARDATA [имя] Начало или продолжение сегмента
инициализированных данных типа far
.FARDATA? [имя] UFARDATA [имя] Начало или продолжение сегмента
неинициализированных данных типа far
Замеч. Наличие в некоторых директивах параметра [имя] говорит о том, что
возможно определение нескольких сегментов этого типа.
Совместно с упрощенными директивами сегментации используется директива
указания модели памяти MODEL, которая частично управляет размещением
сегментов и выполняет функции директивы ASSUME, т.е. связывает сегменты с
сегментными регистрами (поэтому при использовании упрощенных директив
сегментации директиву ASSUME можно не использовать).
! Замеч. Необходимо явно инициализировать только ds .
Упрощенный формат директивы MODEL
MODEL [ <модификатор>] <модель памяти> [ др. параметры ]
Обязательным параметром директивы MODEL является модель памяти. Этот
параметр определяет модель сегментации памяти для программного модуля.
Модели памяти
Модель - Назначение модели
TINY Код и данные объединены в одну группу с именем
DGROUP. Используется для создания программ формата .com.
SMALL Код занимает один сегмент, данные объединены в одну группу с
именем DGROUP. Эту модель обычно используют для большинства программ
на ассемблере
MEDIUM Код занимает несколько сегментов, по одному на каждый
объединяемый программный модуль. Все ссылки на передачу управления —
типа far. Данные объединены в одной группе; все ссылки на них — типа near
COMPACT Код в одном сегменте; ссылка на данные — типа far
LARGE
Код в нескольких сегментах, по одному на каждый объединяемый
программный модуль
Параметр модификатор директивы MODEL позволяет уточнить некоторые
особенности использования выбранной модели памяти.
Модификаторы модели памяти
Значение модификатора - Назначение
use16
Сегменты выбранной модели используются как 16-битные
use32
Сегменты выбранной модели используются как 32-битные
dos
Программа будет работать в MS-DOS
Другие параметры (используются при написании программ на разных языках
программирования) - пока использовать не будем.
Пример.
Для большинства программ на ассемблере используют
MODEL SMALL
директиву
При использовании директивы MODEL транслятор делает доступными
несколько идентификаторов, к которым можно обращаться во время работы
программы, с тем, чтобы получить информацию о тех или иных
характеристиках данной модели памяти. Перечислим эти идентификаторы и их
значения.
Идентификаторы, создаваемые директивой MODEL
Имя идентификатора
Значение переменной
@code
Физический адрес сегмента кода
@data
Физический адрес сегмента данных типа near
@fardata
Физический адрес сегмента данных типа far
@fardata?
Физический адрес сегмента неинициализированных данных
типа far
@curseg
Физический адрес сегмента неинициализированных данных типа
far
@stack
Физический адрес сегмента стека
Таким образом общая структура программы может выглядеть следующим
образом:
masm
;режим работы TASM: ideal или masm
model small
;модель памяти
.stack <размер> ;сегмент стека
.data
;сегмент данных
<описание данных>
BEGIN : .code
;сегмент кода
<команды>
end BEGIN
;конец программы с точкой входа BEGIN
Пример программы с использованием упрощенных директив сегментации:
masm
;режим работы TASM: ideal или masm
model small
;модель памяти
.data
;сегмент данных
message db 'Введите две шестнадцатеричные цифры,$'
.stack
;сегмент стека
db 256 dup ('?')
;сегмент стека
.code
;сегмент кода
main proc
;начало процедуры main
mov ax,@data
;заносим в сегментный регистр ds
mov ds,ax
; физический адрес сегмента данных
; замечание.
;далее текст программы
mov ax,4c00h
;пересылка 4c00h в регистр ax
int 21h
;вызов прерывания с номером 21h
main endp
;конец процедуры main
end main
;конец программы с точкой входа main
ТИПЫ ДАННЫХ АССЕМБЛЕРА
Любая программа предназначена для обработки некоторой информации,
поэтому вопрос о том, как описать данные с использованием средств языка
обычно встает одним из первых.
TASM предоставляет очень широкий набор средств описания и обработки
данных, который вполне сравним с аналогичными средствами некоторых
языков высокого уровня.
Простые типы данных
Для описания простых типов данных в программе используются специальные
директивы резервирования и инициализации данных, которые, по сути,
являются указаниями транслятору на выделение определенного объема памяти.
Если проводить аналогию с языками высокого уровня, то директивы
резервирования и инициализации данных являются определениями переменных.
Машинного эквивалента этим директивам нет; просто транслятор, обрабатывая
каждую такую директиву, выделяет необходимое количество байт памяти и при
необходимости инициализирует эту область некоторым значением.
Директивы резервирования и инициализации данных простых типов:
db — резервирование памяти для данных размером 1 байт.
dw — резервирование памяти для данных размером 2 байта.
dd — резервирование памяти для данных размером 4 байта.
df — резервирование памяти для данных размером 6 байт;
dp — резервирование памяти для данных размером 6 байт.
dq — резервирование памяти для данных размером 8 байт.
dt — резервирование памяти для данных размером 10 байт.
!!! Напомним порядок размещения данных в памяти. Он напрямую связан с
логикой работы микропроцессора с данными. Микропроцессоры Intel требуют
следования данных в памяти по принципу: младший байт по младшему
адресу.
Команды пересылки данных общего назначения
К этой группе относятся следующие команды:
mov <операнд назначения>,<операнд-источник>
xchg <операнд1>,<операнд2>
mov - это основная команда пересылки данных. Она реализует самые
разнообразные варианты пересылки.
Отметим особенности применения этой команды:
!!! командой mov нельзя осуществить пересылку из одной области памяти в
другую. Если такая необходимость возникает, то нужно использовать в качестве
промежуточного буфера любой доступный в данный момент регистр общего
назначения.
К примеру, рассмотрим фрагмент программы для пересылки байта из ячейки fls
в ячейку fld:
mov al,fls
mov fld,al
!!! нельзя загрузить в сегментный регистр значение непосредственно из памяти.
Поэтому для выполнения такой загрузки нужно использовать промежуточный
объект. Это может быть регистр общего назначения или стек.
!!! нельзя переслать содержимое одного сегментного регистра в другой
сегментный регистр. Это объясняется тем, что в системе команд нет
соответствующего кода операции. Но необходимость в таком действии часто
возникает. Выполнить такую пересылку можно, используя в качестве
промежуточных все те же регистры общего назначения. Вот пример
инициализации регистра es значением из регистра ds:
mov ax,ds
mov es,ax
Но есть и другой, более красивый способ выполнения данной операции —
использование стека и команд push и pop:
push ds ;поместить значение регистра ds в стек
pop es ;записать в es число из стека
!!! нельзя использовать сегментный регистр cs в качестве операнда назначения.
Причина здесь простая. Дело в том, что в архитектуре микропроцессора пара
cs:ip всегда содержит адрес команды, которая должна выполняться следующей.
Изменение командой mov содержимого регистра cs фактически означало бы
операцию перехода, а не пересылки, что недопустимо.
XCHG
(eXCHanGe) Обмен
Для двунаправленной пересылки данных применяют команду xchg. Для этой
операции можно, конечно, применить последовательность из нескольких
команд mov, но из-за того, что операция обмена используется довольно часто,
разработчики системы команд микропроцессора посчитали нужным ввести
отдельную команду обмена xchg.
Схема команды: xchg операнд_1,операнд_2
Назначение: обмен двух значений между регистрами или между регистрами и
памятью.
Алгоритм работы: обмен содержимого операнд_1 и операнд_2.
Применение:
Команду xchg можно использовать для выполнения операции обмена двух
операндов с целью изменения порядка следования байт, слов, двойных слов или
их временного сохранения в регистре или памяти. Альтернативой является
использование для этой цели стека.
!! операнды должны иметь один тип.
!! Не допускается (как и для всех команд ассемблера) обменивать между собой
содержимое двух ячеек памяти.
Пример 1
xchg ax,bx
;обменять содержимое регистров ax и bx
xchg ax,word ptr [si]
;обменять содержимое регистра ax
;и слова в памяти по адресу в [si]
Пример 2
;поменять порядок следования байт в слове
ch1
label byte
dw 0f85ch
...
mov al,ch1
xchg ch1+1,al
mov ch1,al
АРИФМЕТИЧЕСКИЕ КОМАНДЫ
Микропроцессор может выполнять целочисленные операции и операции с
плавающей точкой. Для этого в его архитектуре есть два отдельных блока:
•устройство для выполнения целочисленных операций;
•устройство с плавающей точкой.
Каждое из этих устройств имеет свою систему команд. В принципе,
целочисленное устройство может взять на себя многие функции устройства с
плавающей точкой, но это потребует больших вычислительных затрат.
!!! Для большинства задач, использующих язык ассемблера, достаточно
целочисленной арифметики.
Сложение беззнаковых чисел
Микропроцессор выполняет сложение операндов по правилам сложения
двоичных чисел.
• add операнд1,операнд2 — команда сложения с принципом действия:
операнд1 = операнд1 + операнд2
 xadd назначение,источник — обмен местами и сложение.
Команда позволяет выполнить последовательно два действия:
•обменять значения назначение и источник;
•поместить на место операнда назначение сумму:
назначение = назначение + источник.
!!! В операции сложения должны участвовать операнды одного формата (b-b, ww)
!!! Возможны сочетания
регистр - регистр
память - регистр
регистр - память
регистр - непосредственный операнд
неп. Операнд - регистр
!!! Операция память - память выполняется через промежуточный регистр
Пример1 Вычисление суммы двух чисел
...
slag1 dw 250
slag2 dw 125
rez dw ?
...
mov ax,slag1
add ax,slag2
mov rez,ax
...
• inc операнд — операция инкремента, то есть увеличения значения операнда
на 1;
операнд = операнд +1
!!! Операнд м.б. регистром или адресом памяти и !!! не м.б. непосредственным
операндом
Особые ситуации, которые могут возникать при сложении:
1) значение результата превышает размерности поля операнда
для фиксирования ситуации выхода за разрядную сетку результата в
микропроцессоре предусмотрено специальное средство: флаг переноса CF (curry
flag). Он располагается в бите 0 регистра флагов eflags/flags. Именно установкой
этого флага фиксируется факт переноса единицы из старшего разряда операнда.
(CF=1)
Например, при сложении операндов размером в байт результат не должен
превышать число 255. Если это происходит, то результат оказывается неверным.
Например, выполним сложение: 254 + 5 = 259 в двоичном виде. 11111110 +
0000101 = 1 00000011. Результат вышел за пределы восьми бит и правильное его
значение укладывается в 9 бит, а в 8-битовом поле операнда осталось значение
3, что, конечно, неверно.
Т.е. если при сложении двух 8-битовых чисел результат занимает 9 битов, то
значение старшего 9 бита запоминается во флажке CF.
!!! Программист должен предусматривать возможность такого исхода операции
сложения Необходимо включать участки кода после операции сложения, в
которых анализируется флаг cf.
Анализ этого флага можно провести различными способами.
Например, использовать команду условного перехода
jc <метка> ; Переход на метку если cf = 1
jnc <метка> ; Переход на метку если cf = 0
• adc операнд_1,операнд_2 — команда сложения с учетом флага переноса cf.
операнд_1 = операнд_1 + операнд_2 + значение_cf
это команда сложения, учитывающая перенос единицы из старшего разряда.
При сложении чисел со знаком может произойти особая ситуация
2) результат выходит из диапазона допустимых значений
Переполнение регистрируется с помощью флага переполнения of.
Дополнительно к флагу of при переносе из старшего разряда устанавливается в
1 и флаг переноса cf.
Проанализировать флаг of можно командами условного перехода jo \ jno .
учет особых ситуаций должен производиться самим программистом. !!!
Команды вычитания
К командам вычитания относятся следующие:
•sub операнд_1,операнд_2 — команда вычитания; ее принцип действия:
операнд_1 = операнд_1 – операнд_2
•sbb операнд_1,операнд_2 — команда вычитания с учетом заема (флага cf ):
(subtract with borrow -вычитание с заемом)
операнд_1 = операнд_1 – операнд_2 – значение_cf
флаг cf выполняет роль индикатора заема 1 из старшего разряда при вычитании
чисел.
!!! Таким образом, после команды вычитания чисел без знака нужно
анализировать состояние флага cf. Если он установлен в 1, то это говорит о том,
что произошел заем из старшего разряда и результат получился в
дополнительном коде.
•dec операнд — операция декремента, то есть уменьшения значения операнда на
1;
neg операнд — отрицание с дополнением до двух.
Команда выполняет инвертирование значения операнд.
операнд = 0 – операнд,
то есть вычитает операнд из
нуля.
Команду neg операнд можно применять:
•для смены знака;
•для выполнения вычитания из константы.
Резюме
1. Сложение и вычитание знаковых и беззнаковых чисел проводятся по одним
и тем же алгоритмам. ПК не знает какие числа (знаковые или беззнаковые) он
складывает и вычитает, поэтому фиксирует в флагах CF OF особенности
операций. Какие числа обрабатываются знает программист. Если
предполагается, что работа идет с беззнаковыми числами, необходимо
производить анализ флага CF, а OF не надо. Если предполагается, что работа
идет со знаковыми числами, необходимо производить анализ флага ОF, а СF
не надо.
2. Команды INC DEC занимают только один байт и работают быстрее, чем
команды ADD SUB, занимающие три байта.
3. Команды ADD SUB устанавливают флажок переноса, а INC DEC нет
4. Кроме флагов cf и of в регистре eflags есть еще несколько флагов, которые
можно использовать с двоичными арифметическими командами:
•zf — флаг нуля, который устанавливается в 1, если результат операции
равен 0, и в 1, если результат не равен 0;
•sf — флаг знака, значение которого после арифметических операций (и
не только) совпадает со значением старшего бита результата, то есть с битом 7,
15 или 31. Таким образом, этот флаг можно использовать для операций над
числами со знаком.
Если размеры операндов, участвующих в арифметических операциях,
разные следует использовать
так называемые команды преобразования типа. Эти команды расширяют
байты в слова, слова — в двойные слова и двойные слова — в учетверенные
слова (64-разрядные значения). Команды преобразования типа особенно
полезны при преобразовании целых со знаком, так как они автоматически
заполняют старшие биты вновь формируемого операнда значениями знакового
бита старого объекта. Эта операция приводит к целым значениям того же знака
и той же величины, что и исходная, но уже в более длинном формате. Подобное
преобразование называется операцией распространения знака.
Команды без операндов:
 cbw (Convert Byte to Word) — команда преобразования байта (в регистре al)
в слово (в регистре ax) путем распространения значения старшего бита al на
все биты регистра ah;
 cwd (Convert Word to Double) — команда преобразования слова (в регистре
ax) в двойное слово (в регистрах dx:ax) путем распространения значения
старшего бита ax на все биты регистра dx;
 cwde (Convert Word to Double) — команда преобразования слова (в регистре
ax) в двойное слово (в регистре eax) путем распространения значения
старшего бита ax на все биты старшей половины регистра eax;

cdq (Convert Double Word to Quarter Word) — команда преобразования
двойного слова (в регистре eax) в учетверенное слово (в регистрах edx:eax)
путем распространения значения старшего бита eax на все биты регистра edx.
Умножение и деление
Умножение чисел без знака
Для умножения чисел без знака предназначена команда
mul сомножитель_1
Второй операнд — сомножитель_2 задан неявно. Его местоположение
фиксировано и зависит от размера сомножителей. Так как в общем случае
результат умножения больше, чем любой из его сомножителей, то его размер и
местоположение должны быть тоже определены однозначно. Варианты
размеров сомножителей и размещения второго операнда и результата
приведены в табл.
Таблица. Расположение операндов и результата при умножении
сомножитель_1 сомножитель_2 Результат
Байт
al
16 бит в ax: al — младшая часть
результата;
ah — старшая часть результата
Слово
ax
32 бит в паре dx:ax: ax — младшая часть
результата;
dx — старшая часть
результата
Двойное слово
eax
64 бит в паре edx:eax: eax — младшая часть
результата;
edx — старшая часть
результата
Для того, чтобы узнать, что результат достаточно мал и уместился в одном
регистре или что он превысил размерность регистра и старшая часть оказалась в
другом регистре, привлекаются флаги переноса cf и переполнения of:
•если старшая часть результата нулевая, то после операции произведения флаги
cf = 0 и of = 0;
•если же эти флаги ненулевые, то это означает, что результат вышел за пределы
младшей части произведения и состоит из двух частей, что и нужно учитывать
при дальнейшей работе.
Умножение чисел со знаком
Для умножения чисел со знаком предназначена команда
imul операнд_1[,операнд_2,операнд_3]
Эта команда выполняется так же, как и команда mul. Отличительной
особенностью команды imul является только формирование знака. Если
результат мал и умещается в одном регистре (то есть если cf = of = 0), то
содержимое другого регистра (старшей части) является расширением знака —
все его биты равны старшему биту (знаковому разряду) младшей части
результата.
В противном случае (если cf = of = 1) знаком результата является знаковый бит
старшей части результата, а знаковый бит младшей части является значащим
битом двоичного кода результата.
Деление чисел без знака
Для деления чисел без знака предназначена команда
div делитель
Делитель может находиться в памяти или в регистре и иметь размер 8, 16 или 32
бит. Местонахождение делимого фиксировано и так же, как в команде
умножения, зависит от размера операндов. Результатом команды деления
являются значения частного и остатка.
Варианты местоположения и размеров операндов операции деления показаны в
таблице.
Таблица. Расположение операндов и результата при делении
Делимое
Делитель
Частное
Остаток
16 бит
Байт - регистр
Байт
в регистре ax
или ячейка памяти
в регистре al
Байт в
регистре ah
32 бит
dx — старшая часть
ax — младшая часть
64 бит
edx — старшая часть
eax — младшая часть
edx
16 бит
регистр
или ячейка памяти
Двойное слово
32 бит
регистр или
Слово 16 бит
регистре ax
Слово 16 бит
в регистре dx
Двойное слово
32 бит
в регистре eax
Двойное слово
32 бит
в регистре
ячейка памяти
После выполнения команды деления содержимое флагов неопределенно, но
возможно возникновение прерывания с номером 0, называемого “деление на
ноль” по одной из следующих причин:
•делитель равен нулю;
•частное не входит в отведенную под него разрядную сетку.
Деление чисел со знаком
Для деления чисел со знаком предназначена команда
idiv делитель
Для этой команды справедливы все рассмотренные положения, касающиеся
команд и чисел со знаком.
Логические команды
Наряду со средствами арифметических вычислений, система команд
микропроцессора имеет также средства логического преобразования данных.
Под логическими понимаются такие преобразования данных, в основе которых
лежат правила формальной логики.
Формальная логика работает на уровне утверждений истинно и ложно. Для
микропроцессора это, как правило, означает 1 и 0 соответственно.
Для компьютера язык нулей и единиц является родным, но минимальной
единицей данных, с которой работают машинные команды, является байт.
Однако, на системном уровне часто необходимо иметь возможность работать на
предельно низком уровне — на уровне бит.
К средствам логического преобразования данных относятся логические
команды и логические операции.
Система команд микропроцессора содержит пять команд, поддерживающие
логические операции. Эти команды выполняют логические операции над
битами операндов. Размерность операндов, естественно, должна быть
одинакова. Например, если размерность операндов равна слову (16 бит), то
логическая операция выполняется сначала над нулевыми битами операндов и ее
результат записывается на место бита 0 результата. Далее команда
последовательно повторяет эти действия над всеми битами с первого до
пятнадцатого.
Логические команды
and операнд_1,операнд_2 — операция логического умножения.
Команда выполняет поразрядно логическую операцию И (конъюнкцию) над
битами операндов операнд_1 и операнд_2. Результат записывается на место
операнд_1.
or операнд_1,операнд_2 — операция логического сложения.
Команда выполняет поразрядно логическую операцию ИЛИ (дизъюнкцию) над
битами операндов операнд_1 и операнд_2. Результат записывается на место
операнд_1.
xor операнд_1,операнд_2 — операция логического исключающего сложения.
Команда выполняет поразрядно логическую операцию исключающего ИЛИ над
битами операндов операнд_1 и операнд_2. Результат записывается на место
операнд_1.
test операнд_1,операнд_2 — операция “проверить” (способом логического
умножения).
Команда выполняет поразрядно логическую операцию И над битами операндов
операнд_1 и операнд_2. Состояние операндов остается прежним, изменяются
только флаги zf, sf, и pf, что дает возможность анализировать состояние
отдельных битов операнда без изменения их состояния.
not операнд — операция логического отрицания.
Команда выполняет поразрядное инвертирование (замену значения на обратное)
каждого бита операнда. Результат записывается на место операнда.
С помощью логических команд возможно выделение отдельных битов в
операнде с целью их установки, сброса, инвертирования или просто проверки на
определенное значение.
Для организации подобной работы с битами операнд_2 обычно играет роль
маски. С помощью установленных в 1 битов этой маски и определяются нужные
для конкретной операции биты операнд_1.
Команды сдвига
Команды этой группы также обеспечивают манипуляции над отдельными
битами операндов, но иным способом, чем логические команды, рассмотренные
выше.
Все команды сдвига перемещают биты в поле операнда влево или вправо в
зависимости от кода операции.
Все команды сдвига имеют одинаковую структуру:
коп операнд,счетчик_сдвигов
Количество сдвигаемых разрядов — счетчик_сдвигов — располагается на месте
второго операнда и может задаваться двумя способами:
•статически, что предполагает задание фиксированного значения с помощью
непосредственного операнда;
•динамически, что означает занесение значения счетчика сдвигов в регистр cl
перед выполнением команды сдвига.
Все команды сдвига устанавливают флаг переноса cf.
По мере сдвига битов за пределы операнда они сначала попадают на флаг
переноса, устанавливая его равным значению очередного бита, оказавшегося за
пределами операнда. Куда этот бит попадет дальше, зависит от типа команды
сдвига и алгоритма программы.
По принципу действия команды сдвига можно разделить на два типа:
•команды линейного сдвига;
•команды циклического сдвига.
Команды линейного сдвига
К командам этого типа относятся команды, осуществляющие сдвиг по
следующему алгоритму:
•очередной “выдвигаемый” бит устанавливает флаг cf;
•бит, вводимый в операнд с другого конца, имеет значение 0;
•при сдвиге очередного бита он переходит во флаг cf, при этом значение
предыдущего сдвинутого бита теряется!
Команды линейного сдвига делятся на два подтипа:
•команды логического линейного сдвига;
•команды арифметического линейного сдвига.
К командам логического линейного сдвига относятся следующие:
shl операнд,счетчик_сдвигов (Shift Logical Left) - логический сдвиг влево.
Содержимое операнда сдвигается влево на количество битов, определяемое
значением счетчик_сдвигов. Справа (в позицию младшего бита) вписываются
нули;
shr операнд,счетчик_сдвигов (Shift Logical Right) — логический сдвиг вправо.
Содержимое операнда сдвигается вправо на количество битов, определяемое
значением счетчик_сдвигов. Слева (в позицию старшего, знакового бита)
вписываются нули.
Команды арифметического линейного сдвига отличаются от команд
логического сдвига тем, что они особым образом работают со знаковым
разрядом операнда.
sal операнд,счетчик_сдвигов (Shift Arithmetic Left) — арифметический сдвиг
влево.
Содержимое операнда сдвигается влево на количество битов, определяемое
значением счетчик_сдвигов. Справа (в позицию младшего бита) вписываются
нули. Команда sal не сохраняет знака, но устанавливает флаг cf в случае смены
знака очередным выдвигаемым битом. В остальном команда sal полностью
аналогична команде shl;
sar операнд,счетчик_сдвигов (Shift Arithmetic Right) — арифметический сдвиг
вправо.
Содержимое операнда сдвигается вправо на количество битов, определяемое
значением счетчик_сдвигов. Слева в операнд вписываются нули. Команда sar
сохраняет знак, восстанавливая его после сдвига каждого очередного бита.
Команды циклического сдвига
К командам циклического сдвига относятся команды, сохраняющие значения
сдвигаемых бит. Есть два типа команд циклического сдвига:
•команды простого циклического сдвига;
•команды циклического сдвига через флаг переноса cf.
К командам простого циклического сдвига относятся:
rol операнд,счетчик_сдвигов (Rotate Left) — циклический сдвиг влево.
Содержимое операнда сдвигается влево на количество бит, определяемое
операндом счетчик_сдвигов. Сдвигаемые влево биты записываются в тот же
операнд справа.
ror операнд,счетчик_сдвигов (Rotate Right) — циклический сдвиг вправо.
Содержимое операнда сдвигается вправо на количество бит, определяемое
операндом счетчик_сдвигов. Сдвигаемые вправо биты записываются в тот же
операнд слева.
Команды простого циклического сдвига в процессе своей работы осуществляют
одно полезное действие, а именно: циклически сдвигаемый бит не только
вдвигается в операнд с другого конца, но и одновременно его значение
становиться значением флага cf.
Команды циклического сдвига через флаг переноса cf отличаются от команд
простого циклического сдвига тем, что сдвигаемый бит не сразу попадает в
операнд с другого его конца, а записывается сначала в флаг переноса cf. Лишь
следующее исполнение данной команды сдвига (при условии, что она
выполняется в цикле) приводит к помещению выдвинутого ранее бита с другого
конца операнда.
К командам циклического сдвига через флаг переноса cf относятся следующие:
rcl операнд,счетчик_сдвигов (Rotate through Carry Left) — циклический сдвиг
влево через перенос.
Содержимое операнда сдвигается влево на количество бит, определяемое
операндом счетчик_сдвигов. Сдвигаемые биты поочередно становятся
значением флага переноса cf.
rcr операнд,счетчик_сдвигов (Rotate through Carry Right) — циклический сдвиг
вправо через перенос.
Содержимое операнда сдвигается вправо на количество бит, определяемое
операндом счетчик_сдвигов. Сдвигаемые биты поочередно становятся
значением флага переноса cf.
Команды передачи управления
Команды условного перехода и регистр ecx/cx
Регистр ecx/cx имеет определенное функциональное назначение — он
выполняет роль счетчика в командах управления циклами и при работе с
цепочками символов.
Синтаксис этой команды условного перехода таков:
jcxz метка_перехода (Jump if cx is Zero) — переход, если cx ноль;
jecxz метка_перехода (Jump Equal ecx Zero) — переход, если ecx ноль.
Эти команды очень удобно использовать при организации цикла и при работе с
цепочками символов.
Нужно отметить ограничение, свойственное команде jcxz/jecxz. В отличие от
других команд условной передачи управления, команда jcxz/jecxz может
адресовать только короткие переходы — на –128 байт или на +127 байт от
следующей за ней команды.
Организация циклов
Цикл, как известно, представляет собой важную алгоритмическую структуру,
без использования которой не обходится, наверное, ни одна программа.
Организовать циклическое выполнение некоторого участка программы можно,
к примеру, используя команды условной передачи управления или команду
безусловного перехода jmp. При такой организации цикла все операции по его
организации выполняются “вручную”. Но, учитывая важность такого
алгоритмического элемента, как цикл, разработчики микропроцессора ввели в
систему команд группу из трех команд, облегчающую программирование
циклов. Эти команды также используют регистр ecx/cx как счетчик цикла.
Дадим краткую характеристику этим командам:
loop метка_перехода
(Loop) — повторить цикл.
Команда позволяет организовать циклы, подобные циклам for в языках
высокого уровня с автоматическим уменьшением счетчика цикла. Работа
команды заключается в выполнении следующих действий:
•декремента регистра ecx/cx;
•сравнения регистра ecx/cx с нулем:
•если (ecx/cx) > 0, то управление передается на метку перехода;
•если (ecx/cx) = 0, то управление передается на следующую после loop команду.
loope/loopz метка_перехода
(Loop till cx <> 0 or Zero Flag = 0) — повторить цикл, пока cx <> 0 или zf = 0.
Команды loope и loopz — абсолютные синонимы, поэтому используйте ту
команду, которая вам больше нравиться. Работа команд заключается в
выполнении следующих действий:
•декремента регистра ecx/cx;
•сравнения регистра ecx/cx с нулем;
•анализа состояния флага нуля zf:
•если (ecx/cx) > 0 и zf = 1, управление передается на метку перехода;
•если (ecx/cx) = 0 или zf = 0, управление передается на следующую после loop
команду.
loopne/loopnz метка_перехода
(Loop till cx <> 0 or Not Zero flag=0) — повторить цикл пока cx <> 0 или zf = 1.
Команды loopne и loopnz также абсолютные синонимы. Работа команд
заключается в выполнении следующих действий:
•декремента регистра ecx/cx;
•сравнения регистра ecx/cx с нулем;
•анализа состояния флага нуля zf:
•если (ecx/cx) > 0 и zf = 0, управление передается на метку перехода;
•если (ecx/cx)=0 или zf=1, управление передается на следующую после loop
команду.
Команды loope/loopz и loopne/loopnz по принципу своей работы являются
взаимообратными. Они расширяют действие команды loop тем, что
дополнительно анализируют флаг zf, что дает возможность организовать
досрочный выход из цикла, используя этот флаг в качестве индикатора.
Недостаток команд организации цикла loop, loope/loopz и loopne/loopnz в том,
что они реализуют только короткие переходы (от –128 до +127 байт). Для
работы с длинными циклами придется использовать команды условного
перехода и команду jmp.
Работа со стеком
Стек — это область оперативной памяти, специально выделяемая для
временного хранения данных программы.
Для стека в структуре программы предусмотрен отдельный сегмент (если
программист забыл описать сегмент стека, компоновщик tlink выдаст
предупреждающее сообщение).
Для стека можно отвести любую область памяти, но ее размер зависит от
режима работы микропроцессора и ограничивается 64 Кбайт (или 4 Гбайт в
защищенном режиме), а начальный адрес должен быть выровнен на границу
параграфа (т.е. д.б. кратным 16).
!!! Даже если сама программа не использует стек, сегмент стека в
программе все равно надо описывать. Так как стек программы использует ОС
при обработке прерываний, возникающих во время выполнения программы.
Рекомендуемый размер стека (с запасом) - 128 байтов.
В каждый момент времени доступен только один стек, адрес сегмента которого
содержится в регистре ss. Этот стек называется текущим.
Особенности организации стека.
1. Запись и чтение данных в стеке осуществляется в соответствии с
принципом LIFO (Last In First Out — “последним пришел, первым ушел”).
2. По мере записи данных стек растет в сторону младших адресов. Эта
особенность заложена в алгоритм команд работы со стеком; Др. словами, стек
заполняется снизу вверх: первый элемент записывается в самый конец стека
(в ячейку с наибольшим адресом), а следующий элемент записывается «над»
ним.
3. При чтении из стека первым всегда удаляется верхний элемент, поэтому низ
стека фиксирован, а вершина стека все время сдвигается. Текущее положение
этой вершины наз. вершиной стека. Адрес вершины стека храниться в
регистре SP (stack pointer, указатель стека). Т.е. в регистре SP хранится
смещение той ячейки, в которой находится элемент, записанный в стек
последним. Смещение относительно начала сегмента стека. Итак,
абсолютный адрес вершины стека задается парой сегментов SS:SP. Команды
работы со стеком неявно изменяют этот регистр так, чтобы он указывал
всегда на последний записанный в стек элемент. Если стек пуст, то значение
esp равно адресу последнего байта сегмента, выделенного под стек.
Замечание: Регистр ss автоматически используется процессором для
выполнения всех команд, работающих со стеком. Это означает, что при
использовании регистров esp/sp и ebp/bp для адресации памяти ассемблер
автоматически считает, что содержащиеся в нем значения представляют собой
смещения относительно сегментного регистра ss и поэтому префикс SS можно
не указывать.
4. Элементы стека могут иметь любой размер (байт, слово и т.д.). Но команды
записи и чтения, предназначенные для стека, работают только со словами, т.е.
информация, помещаемая в стек и извлекаемая из него, имеет длину 2 байта.
Для работы со стеком предназначены три регистра:
•ss — сегментный регистр стека;
•sp/esp — регистр указателя стека;
•bp/ebp — регистр указателя базы кадра стека.
Команды работы со стеком.
Запись слова в стек
push операнд
— запись значения операнд в вершину стека.
Операнд м. находиться в регистре общего назначения, в сегментном регистре, в
памяти
Нельзя непосредственный операнд !!!
При реализации этой команды выполняются следующие действия
1. (sp) = (sp) – 2; значение sp уменьшается на 2;
2. значение из операнд записывается по адресу ss:sp.
Чтение слова из стека
pop операнд
— запись значения из вершины стека по месту, указанному
операндом.
Алгоритм работы команды pop обратен алгоритму команды push
1. значение из вершины стека пересылается в операнд
2. (sp) = (sp) + 2; значение sp увеличивается на 2.
!!! Операндом не может быть регистр CS
!!! Команды PUSH и POP не осуществляют проверку на выход за пределы
стека.
Например, если стек пуст, а мы считываем слово из стека, будет считано слово
за сегментом стека.
Проверку обязан выполнять программист. Как?
 если стек полон, то смещение вершины стека =0 , т.е. проверить SP=0 ?
 если стек пуст, то смещение вершины стека = размеру стека в байтах.
Чтобы очистить стек можно выполнить определенное число раз команду POP,
но это неэффективно. Лучше
1) запомнить вначале то значение SP, до которого затем нужно будет очищать
стек, а затем его восстановить
mov ax,sp
...
mov sp,ax
2) еще лучше add sp,2*n
Для этого надо заметить, что после удаления из стека n слов значение SP
должно увеличиться на 2*n. А регистр SP явл. регистром общ. Назначения и его
м. исп-ть в любых командах
Запись в стек и чтение регистра флагов
pushf — записывает регистр флагов в стек
Работа этой команды зависит от атрибута размера сегмента:
•use16 — в стек записывается регистр flags размером 2 байта;
•use32 — в стек записывается регистр eflags размером 4 байта.
popf - считывает слово из стека и записывает в регистр флагов
Замечание. Команды PUSHF и POPF - единственные в системе команд
микропроцессора, которые позволяют получить доступ ко всему содержимому
регистра флагов.
Доступ к элементам стека
Команды PUSH и POP дают доступ только к вершине стека.
Для получения доступа к другим элементам стека необходимо использовать
модификацию адреса с помощью регистра BP следующим образом:
1) установить регистр BP на вершину стека
2) для обращения к элементу использовать конструкцию вида [BP+n], где
n - число байт, на которое смещен элемент относительно вершины стека.
Пример. Пусть в стек были помещены три слова А, В, С.
Необходимо обратиться к элементу A.
Адрес третьего слова стека равен адресу вершины стека плюс 4. Поэтому
mov bp,sp
mov ax,[bp+4]
Почему необходимо использовать именно BP?
1. Сам SP нельзя так как он не явл. регистром-модификатором
2. При использ. регистра BP сегментным регистром по умолчанию выбирается
SS
и тем самым мы сокращаем запись mov ax,ss:[bp+4] и считываем ячейку из
стека
Использование стека
 для временного сохранения значений регистров
например, push cx
. . . ; например во вложенных циклах
pop cx
 для сохранения текущих состояний флагов и последующего их
восстановления
pushf
popf
 для определения или изменения состояния любого флага
Пример. Записать в регистр AX значение флага трассировки TF (это 8-й бит),
не изменяя при этом никакие флаги (!)
pushf
; запомним для последующего восстановления
pushf
; запомним для пересылки в ax
pop ax
mov cl,8
; счетчик сдвигов
shr,cl
; сдвиг бита TF к правому краю AX
and ax,1b ; теперь AX:=TF
popf
; восстановить исходный Flags
 для пересылки данных
например, X:=Y
push Y
pop X
 при работе с процедурами;
 при определении локальных переменных.
Процедуры
Понятие
Процедура, часто называемая также подпрограммой, — это основная
функциональная единица декомпозиции (разделения на несколько частей)
некоторой задачи.
Процедура представляет собой группу команд для решения конкретной
подзадачи
Процедура - именованная, правильным образом оформленная группа команд,
которая, будучи однократно описана, при необходимости может быть вызвана
по имени любое количество раз из различных мест программы.
Описание процедуры
Для описания последовательности команд в виде процедуры в языке ассемблера
используются две директивы: PROC и ENDP.
Синтаксис описания процедуры таков
<имя> proc [тип]
<тело процедуры>
<имя> endp
<Тип> может принимать значения near или far и характеризует возможность
обращения к процедуре из другого сегмента кода. Тип near позволяет
обращаться к процедуре только из того сегмента кода, в котором она описана. К
процедуре типа FAR можно обращаться и из других сегментов тоже. По
умолчанию тип принимает значение near.
Замечание. Имя процедуры по сути является меткой, т.е. с помощью простых
команд перехода можно осуществить переход на первую команду процедуры.
Размещение в сегменте кода
Процедура может размещаться в любом месте программы, но так, чтобы на нее
случайным образом не попало управление.
Если процедуру просто вставить в общий поток команд, то микропроцессор
будет воспринимать команды процедуры как часть этого потока и
соответственно будет осуществлять выполнение команд процедуры.
Если имеется несколько процедур их обычно размещают рядом.
1. В начале сегмента кода
!!! Процедура должна быть расположена до первой выполняемой команды
Пример.
.code
name proc ; ближняя по умолчанию
...
name endp
start: . . .
end start
2. В конце сегмента кода
!!! Процедура должна быть размещена после команды, возвращающей
управление ОС
Пример
.code
start: . . .
mov ax,4C00h
int 21h
name proc near
..
name endp
end start
3. Можно разместить процедуру внутри сегмента кода
!!! Необходимо предусмотреть обход процедуры
Пример
.code
start: . . .
jmp metka
name proc
...
name endp
metka: . . .
end start
4. Процедура может быть описана в другом сегменте кода (об этом позже)
Вызов процедур
В принципе можно было бы обратиться к процедуре, просто переходом на
метку - имя процедуры. Но!!! Необходимо сохранить где-то адрес возврата, т.е.
адрес команды, следующей после процедуры.
В систему команд микропроцессора была введена специальная команда
Вызов процедуры, или переход с возвратом
CALL [модификатор] <имя процедуры>
Команда записывает адрес следующей за ней команды в стек, а затем
осуществляет переход по метке <имя процедуры>.
Вспомним, что адрес следующей выполняемой команды задается парой CS:IP.
Если процедура описана как
 дальняя, то в стек по очереди заносятся два значения CS, IP.
 короткая - только значение IP
И при этом соответственно переход считается коротким или дальним.
Поэтому в с-ме команд имеется два варианта команды CALL.
!!! Ассемблер выберет правильный вариант, если процедура была описана
до вызова. Если процедура будет описана позже транслятор считает процедуру
ближней и формирует команду ближнего вызова. Если процедура окажется
дальней, будет зафиксирована ошибка.
Для указания транслятору на то, что процедура, описанная ниже, будет дальней
можно использовать <модификатор> со значением FAR PTR
Возврат из процедур
Адрес возврата в вызывающую программу хранится в стеке.
Команда RET [число] возвращает управление вызывающей программе.
Она считывает адрес возврата из вершины стека, загружает его в регистры CS и
IP (теперь выполняться будет следующая за CALL команда в программе) адрес
возврата при этом удаляется из стека(!), затем стек очищается на указанное
число байт и выполняется переход по адресу возврата.
Команда RET соответствует команде RET 0
В зависимости от того, дальняя или ближняя была описана процедура,
ассемблер формирует одну из возможных команд RET. В первом случае из
стека извлекается два слова, которые загружаются в регистры CS и IP, во
втором - одно слово в регистр IP.
Команды CALL и RET действуют согласованно, за соответствием между
командами следит транслятор.
Необязательный параметр [число] задает значение, удаляемое из стека при
возврате из процедуры - в байтах (use16) или в словах (use32)
Параметры процедур
Передача значений в процедуру называется передачей параметров.
Результатом выполнения процедуры может быть некоторое значение, которое
необходимо передать вызывающей программе. Передача результата из
процедуры в программу тоже организовывается как передача параметров.
Передавать параметры можно
 через регистры
 через стек
 через общую область памяти
 с помощью директив extern и public
Существует два способа передачи данных:
1. Передача параметров по значению
В этом случае передаются сами данные, т.е. их значения
!!! При передаче параметров по значению в процедуре обрабатываются их
копии. Поэтому значения переменных в основной программе не изменяются !!!
2. Передача параметров по ссылке
В этом случае передаются адреса (ссылки) параметров
!!! Здесь обрабатываются не копии, а сами данные, поэтому при изменении
данных в процедуре они автоматически изменяются и в вызывающей программе
!!!
Т.Е. Передача параметра по ссылке означает передачу адреса ячейки,
соответствующей фактическому параметру.
Передача параметров через регистры
Самый простой способ передачи данных. Его суть заключается в следующем:
Основная программа записывает фактические параметры в какие-нибудь
регистры, а процедура берет их оттуда и обрабатывает.
Аналогично поступают с результатом. Процедура помещает результат в какойто регистр, а программа извлекает его оттуда.
!!! Если размер данных превышает 16 или 32 бит (размер регистра), то
передавать нужно
Передача параметров через стек
Суть: основная программа записывает параметры в стек, а процедура извлекает
их оттуда.
!!! Результат лучше передавать через регистр. Иначе нужно осторожно
чистить стек при выходе из процедуры.
Перед обращением к процедуре основная программа должна поместить в стек
фактические параметры. Модель передачи (т.е. в каком порядке) определяет
программист.
Процедура должна получить доступ к этим элементам. Т.к. они не на вершине
стека, будем пользоваться регистром BP.
Таким образом, обратиться к первому (сверху) (а на самом деле это N параметр
списку) элементу стека можно [BP+4], к следующему - [BP+6] и т.д.
!!! Если процедура является дальней, то следует еще +2, т.к. для адреса возврата
поместят два элемента в стек (CS, IP). Для доступа к параметру N - [BP+6] и т.д.
Перед завершением процедуры необходимо выполнить действия по
корректному возврату из процедуры.
Необходимо 1) поместить на вершину стека адрес возврата из процедуры
2) очистить стек от параметров (они уже стали ненужными)
(мы к ним только обращались, но не удаляли из стека)
1) mov SP,BP ; восстановить SP
pop BP
; восстановление старого BP
Теперь на вершине стека адрес возврата и можем выполнять RET
2) Очистить стек от параметров может основная программа ADD SP,2*N
Лучше очищать стек в самой процедуре.
Можно воспользоваться параметром [число] команды RET.
Этот параметр задает число байтов, на которое очищается стек.
Макросредства языка Ассемблер
Макросредства - инструменты (средства) модификации текста программы во
время ее трансляции.
Макросредства предназначены для облегчения написания программ на языке
Ассемблер и для улучшения понимания исходного текста программы.
Основная идея. Повторяющийся фрагмент программы специальным образом
описывается (макрос), именуется, а затем в нужных местах программы
указывается ссылка на него. При создании объектного кода вместо ссылки
подставляется сам фрагмент, т.е. происходит подстановка фрагмента вместо
ссылки.
Транслятор ассемблера состоит из двух частей: макрогенератора и
непосредственно транслятора. Транслятор при этом называют макроассеблером.
Именно таким макроассемблером мы пользуемся в системе MASM
(macroassembler language).
Обработка программы с использованием макросредств осуществляется
транслятором в два этапа. На первом этапе работает макрогенератор, который
производит замены для всех макросов, а на втором этапе уже преобразованный
текст программы транслируется в объектный код.
Макроопределение
Это описание макроса. Синтаксис макроопределения:
<имя макрокоманды> MACRO [формальные параметры]
тело макроопределения
ENDM
Директива MACRO - это заголовок макроопределения. В ней указывается имя и
через запятую перечисляются формальные параметры, если необходимо.
Формальные параметры позволяют копировать макрос не в неизменном виде, а
с изменениями. Те величины, которые необходимо будет изменить описываются
формальными параметрами.
Замечание. Имена формальных параметров локальны по отношению к макросу,
т.е. они могут совпадать с именами в основной программе, макрогенератор
понимает их как параметры.
Завершает макроопределение директива ENDM. !!! Не надо повторять имя
макроса.
Пример 1 ; настройка сегмента данных
initds macro
mov ax, @data
mov ds, ax
endm
Размещаться макроопределения могут :
1. В любом месте программы.
!!! Обязательно до первой ссылки на него.
2. В отдельном файле.
Чтобы сделать доступными макроопределения в программе, необходимо в
начале программы использовать директиву INCLUDE <имя файла>. При этом
на этапе работы макрогенератора текст указанного файла будет вставлен
полностью на место директивы.
Пример.
Masm
model small
include Mymacro.inc
...
Можно универсальные макрокоманды записать в один файл, в так называемую
макробиблиотеку. Подключать ее с помощью директивы include.
Чтобы в текст программы не включать лишние макроопределения, можно
воспользоваться директивой
PURGE <список через запятую имен макроопределений>
Директива указывает, какие макроопределения не должны включаться в текст
программы.
Пример.
...
include mymacro.inc
purge outstr, initds
...
Макрокоманды
Макрокоманда - обращение к макроопределению. Или указание
макрогенератору на то, что на указанном месте необходимо подставить тело
макроопределения. Итак, одна макрокоманда заменяется на группу команд,
поэтому она и наз. макро (большая).
Синтаксис макрокоманды:
<имя макроса> [<фактические параметры>]
Замечание. Фактические параметры можно разделять запятыми или пробелами.
Формальные параметры макроопределения заменяются соответствующими
фактическими параметрами макрокоманды.
!!! i-тый фактич. Параметр соответствует i-тому формальному параметру
!!! Число фактических параметров должно быть равно числу формальных
параметров,
если фактических параметров больше, то лишние игнорируются
если формальных больше, считается что в качестве недостающих
фактических указаны пустые тексты
Процесс замещения формальных параметров фактическими называется
макроподстановкой. Результат макроподстановки (т.е. полученный в
результате текст называется макрорасширением.
Действия макрогенератора:
1) макрогенератор находит макроопределение с указанным именем
2) в его теле заменяет все формальные параметры фактическими
3) полученное макрорасширение подставляет в программу вместо
макрокоманды
Сравнительный анализ процедур и макросредств
Повторяющиеся действия (фрагменты) в программе можно описать и как
процедуру, и как макроопределение. При этом в обоих случаях повторяющийся
участок кода описан только один раз, а обращаемся к нему с помощью одной
команды (вызов процедуры или макрокоманда). Но...
После трансляции процедура так и останется описанной один раз, а тело
макроопределения подставится во все места вызова и тем самым увеличит
размер программы.
Вывод 1. Применение процедур делает код более компактным, т.е. экономим
память
Но при обращении к процедуре
а) выполняется засылка параметров в регистры или стек,
б) запоминается адрес возврата
в) осуществляется переход,
г) по окончании работы процедуры восстанавливается адрес возврата,
д) очищаются регистры или стек и т.п.
Итак, при работе процедуры тратится время на переходы и передачу параметров
во время выполнения программы.
!!! При замене макрокоманд на макрорасширения тоже тратится время, но это
происходит на этапе трансляции, а не во время выполнения программы.
Вывод 2. Применение макросредств экономит время выполнения программы.
Поэтому в программах критических по времени следует применять
макросредства, а если необходимо экономить память следует применять
процедуры.
Если в повторяющемся участке кода много команд (т.е. большой фрагмент)
лучше описать его как процедуру. Если же небольшую группу команд описать
процедурой, то число вспомогательных команд по ее вызову и передаче
параметров станет сравнимым с числом команд самой процедуры, ее время
выполнения станет на много больше.
Вывод 3. Большие участки кода рекомендуется описывать как процедуры, а
маленькие - как макроопределения.
!!! Еще одно отличие использования макросредств и процедур заключается в
том, что параметрами процедур могут быть только операнды команд, а
параметрами макрокоманд могут быть любые последовательности символов, в
том числе и сами команды.
Download