Часто используемые в примерах имена
В примерах, приводимых в данной и последующих главах книги, многократно
повторяются имена переменных, подпрограмм для работы с окнами видеопамяти
и макроопределений, предназначенных для записи в стек или выталкивания
из него содержимого регистров. Для того чтобы каждый раз не объяснять
назначение и способы описания и определения этих имен, мы сделаем это
в данном разделе.
Подпрограммы для работы с окнами. Текст подпрограмм, выполняющих установку
текущего (setwin), следующего (Nxtwin) и предыдущего (prevwin) окна, приведен
в примере 2.8. Здесь мы обратим ваше внимание на следующие особенности,
связанные с их использованием в примерах.
Прежде всего, обычно Макроассемблер не различает заглавные и строчные
буквы в именах, независимо от их назначения. Поэтому, например, имена
setwin, setwin и setwin для него тождественны. Буквы разного размера используются
для того, чтобы человек мог визуально различить отдельные части составных
имен. Если же вы принудительно заставите Макроассемблер различать строчные
и заглавные буквы, что вполне возможно, то придется использовать только
одну форму записи имен.
Подпрограммы должны быть включены в текст основной программы, для того
чтобы Макроассемблер мог опознать их имена и сформировать команды вызова.
Возможны разные способы оформления текстов подпрограмм и их размещения
в теле задачи, подробно эти вопросы рассмотрены в приложении В. В примерах
основной части книги описаны подпрограммы, расположенные в кодовом сегменте,
т. е. там, где находятся команды, образующие тело задачи. Обычно в исходном
тексте он имеет имя Code. Место расположения подпрограмм в пределах кодового
сегмента выбирается по усмотрению разработчика.
Основные переменные
Переменная — это последовательность байтов оперативной памяти, которым
присвоено уникальное имя и в которых хранятся величины, применяемые при
вычислениях. В командах обычно используются переменные, содержащие 1,
2 или 4 байта. Переменные, содержащие большее количество байтов, принято
называть строками, массивами или таблицами.
Переменные обычно хранятся в отдельном сегменте оперативной памяти,
который принято называть сегментом данных. В исходных текстах программ
его наиболее распространенным именем является Data. Для большей наглядности
рекомендуется располагать сегмент данных перед кодовым сегментом. В примере
2.11 показан вариант оформления сегмента данных и описания в нем основных
переменных, используемых в последующих примерах. Способ определения (формирования
значений) большинства из них был описан в данной главе (см. примеры 2.1—2.6).
Пример 2.11. Описания основных переменных
в сегменте данных
Data |
SEGMENT |
; директива указывает начало сегмента |
OldMode |
db 0 |
; исходный видеорежим |
NevMode |
dw 101h |
; видеорежим VESA 101h |
Vbuff |
dw 0А000h |
; адрес видеобуфера |
Horsize |
dw 640 |
; количество точек в строке |
Bperline |
dw 640 |
; количество байтов в строке |
Versize |
dw 480 |
; количество строк на экране |
Cur win |
dw 0 |
; номер текущего окна |
Cur pos |
dw 0 |
; адрес (смещение) в текущем окне |
GrUnit |
dw 0 |
; единица приращения номера окна |
VMC |
dd 0 |
; адрес процедуры BIOS |
winB |
db 0 |
; параметры окна В |
; Далее до конца сегмента располагаются другие описания |
Data |
ENDS |
; директива указывает конец сегмента |
Описание сегмента открывает директива SEGMENT, а закрывает директива
ENDS. Перед обеими директивами указывается одно и то же имя, в данном
случае Data. Назначение и способы оформления сегментов описаны в приложении
Б данной книги.
В примере 2.11 описаны 11 основных переменных. Каждая из них имеет уникальное
имя. После имени расположены директивы db, dw или dd, указывающие тип
переменной, т. е. ее размер в байтах: db — байт (8 разрядов), dw — слово
(16 разрядов), dd — двойное слово (32 разряда).
После директивы указывается значение, которое Макроассемблер присваивает
переменной. В примере 2.11 переменным сразу присвоены конкретные значения,
но на практике они формируются в процессе выполнения задачи. Например,
код сегмента видеобуфера (значение переменной vbuff) обычно АОООЬ, но
возможны исключения, поэтому его значение надо выбирать из массива info.
В конце каждой строки примера 2.11 расположен комментарий, поясняющий
назначение переменных и директив. Признаком начала комментария является
символ "точка с запятой". Обнаружив его, Макроассемблер просто
пропускает весь текст до конца строки.
Обратите внимание, в тексте примера 2.11 после всех имен отсутствует
символ "двоеточие". Существует простое правило: двоеточие должно
указываться только после имен меток, расположенных перед командами.
Следует отметить, что после директив описания типа может указываться
не одно, а несколько значений, при этом Макроассемблер размещает эти значения
в последовательно расположенных байтах, словах или двойных словах. Имя
переменной в таком случае относится только к первому байту, слову или
двойному слову. В отдельных случаях оно может вообще отсутствовать (см.
пример 6.3). Существует специальный оператор повторения, который позволяет
связать с именем переменной требуемое количество байтов, например:
Buffpal dd 256 DUP (0) ; резервирование и очистка памяти
В этом примере имя buff pal соответствует буферу размером в 256 двойных
слов, содержимое которых принудительно очищается.
И последнее замечание, директива db может использоваться для описания
текстовых строк, предназначенных для вывода на экран подсказок, предупреждений,
аварийных и других сообщений. В этом случае расположенный после директивы
текст заключается в одиночные или двойные кавычки. Вот пример оформления
спецификации файла:
filspc db 'c:\tmp\current.pal', 00; описание спецификации
файла
В этом примере описана спецификация файла current.pal, который находится
на диске с в каталоге ТМР. Спецификация предназначена для процедуры BIOS,
выполняющей открытие файла для чтения или записи, поэтому ее текст заканчивается
пустым байтом.
Макросы PushReg и PopReg. Подпрограммы во время выполнения не должны изменять
чужие или исходные данные. Для этого перед началом основных действий в
стеке сохраняется содержимое используемых регистров или переменных, а
перед выходом из подпрограммы восстанавливаются исходные значения сохраненных
величин. Сохранение в стеке одной величины выполняет одна команда, это
же относится и к восстановлению. Существует специальное средство, позволяющее
сократить запись повторяющихся однотипных действий и сделать ее более
наглядной. Таким средством являются макросы. Подчеркнем, что сокращается
только исходный текст, а не количество команд в теле задачи.
Понятие макрос (macro) распространяется на макроопределение (macro definition)
и макроподстановку или макровызов (macro substitution). Макроопределение
описывает некую заготовку текста программы, а макровызов — способ ее использования.
Макроопределение существует только в исходном тексте, оно модифицируется
в зависимости от указанных при вызове параметров и в измененном виде включается
в тело задачи на месте каждого макровызова.
Ниже приводится пример двух простых макроопределений (пример 2.12).
Их вызовы уже использовались в примерах 2.8 и 2.9, и будут неоднократно
встречаться во многих примерах. Первое из них PushReg предназначено для
сохранения в стеке содержимого регистров, a PopReg — для восстановления
из стека ранее сохраненных регистров.
Пример 2.12. Описание макроопределений
PushReg и PopReg
; Сохранение в стеке регистров, перечисленных в списке
гeg |
PushReg |
macro reg |
; заголовок макроопределения |
|
irp r,<reg> |
; начало оператора повторения |
|
|
|
|
|
заготовка повторяемой команды |
|
|
конец оператора повторения |
|
|
|
|
|
стека регистров, перечисленных в списке reg. |
|
|
заголовок макроопределения |
|
|
|
|
|
заготовка повторяемой команды |
|
|
конец оператора повторения |
|
|
|
|
|
|
Макросы примера 2.12 различаются только заготовкой повторяемой команды.
В одном случае это запись в стек, а в другом — выталкивание из него.
При оформлении макросов используются специальные директивы. Текст любого
макроопределения начинается директивой macro, перед ней указывается имя
макроса, а после нее, в той же строке, список аргументов, если таковые
имеются, в данном примере это reg.
Другая, часто используемая директива — endm. В зависимости от контекста
она указывает конец макроопределения или оператора, что и показано в примере
2.12.
Тела макроопределений примера 2.12 состоят из директивы повторения irp.
После нее, в той же строке, указываются параметр г и имя списка аргументов,
заключенное в угловые скобки. Оно должно совпадать с именем, указанным
в директиве macro. В следующей строке записывается повторяемая команда,
один из операндов которой г соответствует параметру директивы irp.
В общем случае тело директивы irp может состоять из нескольких команд
или содержать другие директивы, поэтому нужен признак конца директивы
endm.
Обнаружив в тексте программы макроопределение, Макроассемблер проверяет
его синтаксис и запоминает имя и текст, не включая его в тело задачи.
Исполнение макроопределения (вставка команд в тело задачи) будет производиться
при каждом макровызове.
Параметры макровызова
Макровызовы или макрокоманды — это вставка текста макроопределения в
нужных местах программы с подстановкой конкретных параметров, если они
имеются. Для макроопределений примера 2.12 макровызов состоит из имени
PushReg или PopReg и списка регистров, заключенного в угловые скобки.
Угловые скобки позволяют использовать запятые между именами регистров,
входящих в список.
Обнаружив макровызов, Макроассемблер находит и обрабатывает соответствующее
определение, в результате чего формируются обычные команды, которые сразу
компилируются и полученный код вставляется в тело задачи.
В частности, при вызове макроопределений примера 2.12 директива irp
повторяется столько раз, сколько имен содержит список reg. Название каждого
имени выбирается из списка и подставляется в команду push или pop вместо
параметра r.
В макроопределениях примера 2.12 разнообразие имен не ограничено явно,
допустимо указание любых величин, которые могут быть операндами команд
push и pop. Ими могут быть не только имена 16- и 32-разрядных регистров,
но и имена переменных, кроме того, команда push может сохранять в стеке
значения констант (непосредственное указание сохраняемой величины). Все
эти величины можно использовать в макровызове. Например, во многих подпрограммах
будет использоваться такой вариант макровызовов:
PushReg <fs, gs, Cur_win> И PopReg <Cur_win, gs,
fs>
Обратите внимание, имена в списках PushReg и PopReg расположены в обратном
порядке, поскольку работа со стеком организована по принципу "последнее
записанное — первое считанное".
Альтернативным способом сохранения группы регистров в стеке и восстановления
их оттуда одной командой являются операции pusha и рора, не имеющие параметров.
Их исполняют все модели микропроцессоров, кроме Intel 8086. Эти операции
сохраняют в стеке или восстанавливают из него регистры в таком порядке:
PUSHA -> ах, ex, dx, bx, sp, bp, si, di.
РОРА -> di, si, bp, sp, bx, dx, ex, ax.
Начиная с модели Intel 80386, микропроцессоры поддерживают операции
pushad и popad, также не имеющие параметров. Они сохраняют в стеке или
восстанавливают из него 32-разрядные регистры в таком порядке:
PUSHAD-> eax, ecx, edx, ebx, esp, ebp, esi, edi.
POPAD -> edi, esi, ebp, esp, ebx, edx, ecx, eax.
В отличие от макрокоманд, четыре описанные операции сокращают не только
исходный текст программы, но и размер задачи и время выполнения операции,
поскольку вместо восьми команд используется одна. Их недостаток в том,
что обязательно сохраняются все восемь указанных регистров, но не сохраняются
сегментные регистры. Поэтому в каждом конкретном случае надо решать, что
лучше использовать — указанные операции или макрокоманды.
|