|
Часть 3
Выбирайте
=================================================================
Авторский коллектив "*.*"
под руководством Орлова С.Б.
ПРОГРАММА-СПРАВОЧНИК по системе
программирования ТУРБО АССЕМБЛЕР 2.0
РУКОВОДСТВО ПОЛЬЗОВАТЕЛЯ
#3/5 (Главы 9-11, приложения)
г.Москва, 1990 г.
=================================================================
Оглавление
TASM2 #3-5/Док = 1 =
Глава 9. Развитое программирование на Турбо Ассемблере..........6
Префиксы переопределения сегментов............................6
Альтернативная форма.........................................10
Когда префиксы переопределения сегментов не работают.........10
Доступ к нескольким сегментам................................12
Локальные метки..............................................13
Автоматическое назначение размера перехода...................18
Ссылки вперед на код и данные................................23
Использование блоков повторения и макрокоманд................28
Блоки повторения.............................................28
Блоки повторения и параметры-переменные......................31
Макрокоманды.................................................32
Вложенные макрокоманды.......................................39
Макрокоманды и условия.......................................40
Завершение макрорасширения с помощью директивы EXITM.........42
Определение меток с помощью макрокоманд......................43
Развитые структуры данных....................................45
Директива STRUC..............................................46
Недoстатки и преимущества использования директивы STRUC......51
Уникальные имена полей структур..............................52
Вложенные структуры..........................................53
Инициализация структур.......................................54
Директива RECORD.............................................56
Доступ к записям.............................................58
Операция WITH................................................61
Операция MASK................................................62
Для чего используются записи?................................64
Директива UNION..............................................67
Директивы определения сегментов..............................71
Директива SEGMENT............................................72
Поля "имя" и "выравнивание"..................................72
Поле "комбинирование"........................................73
Назначение полей "использование" и "класс"...................75
Размер, тип, имя и уровень вложенности сегмента..............75
Порядок сегментов............................................78
Директива GROUP..............................................80
Директива ASSUME.............................................83
Упрощенные директивы определения сегментов...................87
Пример программы, состоящей из нескольких сегментов..........93
Глава 10. Процессор 80386 и другие процессоры..................98
Выбор в программе на Ассемблере типа процессора..............98
Процессоры 80186 и 80188.....................................99
Разрешение ассемблирования для процессора 80186.............101
Новые инструкции............................................101
Инструкции PUSHA и POPA.....................................101
Инструкции ENTER и LEAVE....................................103
TASM2 #3-5/Док = 2 =
Инструкция BOUND............................................105
Инструкции INS и OUTS.......................................107
Расширенные версии инструкций процессора 8086...............109
Занесение в стек промежуточных значений.....................109
Сдвиги и циклические сдвиги c непосредственными значениями..110
Умножение на непосредственное значение......................110
Процессор 80286.............................................113
Ассемблирования с использованием инструкций процессора
80286......................................................115
Процессор 80386.............................................116
Выбор режима ассемблирования для процессора 80386...........116
Новые типы сегментов........................................118
Упрощенные сегментные директивы и типы сегментов для 80386..123
48-битовый тип данных FWORD.................................123
Новые регистры..............................................127
32-разрядные общие регистры.................................130
32-разрядный регистр флагов.................................132
32-разрядный указатель инструкций...........................132
Новые сегментные регистры...................................134
Новые режимы адресации......................................135
Новые инструкции............................................142
Проверка бит................................................142
Просмотр битов..............................................144
Перемещение данных с расширением по знаку или нулю..........146
Преобразование данных типа DWORD или QWORD..................148
Сдвиг нескольких слов.......................................149
Условная установка бит......................................151
Загрузка регистров SS, FS и GS..............................153
Расширенные инструкции......................................155
Специальные версии инструкции MOV...........................155
32-разрядные версии инструкций процессора 8086..............157
Новые версии инструкций LOOP и JCXZ.........................157
Новые версии строковых инструкций...........................160
Инструкция IRETD............................................162
Инструкции PUSHFD и POPFD...................................162
Инструкции PUSHAD и POPAD...................................162
Новые версии инструкции IMUL................................163
Чередование 16-разрядных и 32-разрядных инструкций и
сегментов..................................................164
Пример функции для процессора 80386.........................167
Сопроцессор 80287...........................................173
Сопроцессор 80387...........................................173
Глава 11. Улучшенный режим Турбо Ассемблера...................175
Что такое улучшенный режим?.................................175
Для чего используется улучшенный режим?.....................176
TASM2 #3-5/Док = 3 =
Переключение в улучшенный режим и выход из него.............177
Отличия улучшенного режима и режима MASM....................179
Лексемы улучшенного режима..................................179
Лексемы-идентификаторы......................................179
Дублирование имен элементов.................................180
Лексемы, представляющие собой числа с плавающей точкой......182
Текстовые и числовые присваивания (директивы EQU и =).......182
Выражения и операнды........................................183
Операция квадратных скобок []...............................183
Примеры операндов...........................................184
Операции....................................................186
Точки в элементах структуры.................................186
Указатели на структуры......................................188
Операция SYMTYPE............................................189
Операции HIGH и LOW.........................................189
Необязательная операция PTR.................................190
Операция SIZE...............................................190
Директивы...................................................192
Управление листингом........................................192
Директивы, начинающиеся с точки.............................193
Обратный порядок имени директивы и идентификатора...........194
Заключенные в кавычки строки, являющиеся аргументами
директив...................................................196
Сегменты и группы...........................................196
Доступ к данным в сегменте, принадлежащем группе............196
Определение в коде ближних и дальних меток..................199
Внешние, общедоступные и глобальные идентификаторы..........200
Другие отличия..............................................202
Подавление корректировок....................................202
Операнд инструкции BOUND....................................202
Комментарии в макрокомандах.................................203
Локальные идентификаторы....................................204
Сравнение программирования в режиме MASM и в улучшенном
режиме.....................................................204
Пример программы в режиме MASM..............................204
Пример программы в улучшенном режиме........................206
Анализ режима MASM и улучшенного режима.....................208
Литература..................................................211
Приложение A. Интерфейс Турбо Ассемблера и Турбо Бейсика......212
Передача параметров.........................................213
Переменные, находящиеся вне текущего сегмента данных........216
Тип оператора CALL..........................................216
Извлечение из стека.........................................217
Создание программы на Ассемблере для Турбо Бейсика..........219
Вызов встроенной процедуры Ассемблера.......................219
Размещение подпрограммы Турбо Бейсика в памяти..............223
TASM2 #3-5/Док = 4 =
"Скрытые" строки............................................224
Оператор CALL ABSOLUTE......................................226
Обращение CALL ABSOLUTE к фиксированным адресам памяти......228
Обращение CALL ABSOLUTE к другим адресам памяти.............229
Другие проблемы, возникающие при использовании CALL
ABSOLUTE...................................................231
CALL INTERRUPT..............................................231
Пример программы............................................234
Приложение B. Интерфейс Турбо Ассемблера с Турбо Прологом.....237
Объявление внешних предикатов...............................237
Соглашения по вызову и параметры............................237
Соглашения по именам........................................239
Разработка предикатов на Ассемблере.........................240
Реализация предиката double.................................243
Реализация предикатов с несколькими потоками ввода-вывода...247
Вызов предикатов Турбо Пролога из функций Ассемблера........249
Списки и функторы...........................................252
Приложение C. Ответы на общие вопросы.........................256
TASM2 #3-5/Док = 5 =
=================================================================
Авторский коллектив "*.*"
под руководством Орлова С.Б.
ПРОГРАММА-СПРАВОЧНИК по системе
программирования ТУРБО АССЕМБЛЕР 2.0
РУКОВОДСТВО ПОЛЬЗОВАТЕЛЯ
#3/5 (Главы 9-11, приложения)
г.Москва, 1990 г.
=================================================================
TASM2 #3-5/Док = 6 =
Глава 9. Развитое программирование на Турбо Ассемблере
-----------------------------------------------------------------
В предыдущих восьми главах данного руководства мы рассказали
о сущности программирования на Ассемблере. Теперь вы готовы к
тому, чтобы ознакомиться с некоторыми развитыми средствами Турбо
Ассемблера.
В данной главе мы исследуем несколько аспектов программиро-
вания на Ассемблере, которые мы ранее только слегка затронули. К
ним относятся префиксы переопределения сегментов, макрокоманды,
директивы определения сегментов и разработка программ, содержащих
несколько сегментов кода и данных. Мы рассмотрим также некоторые
полезные средства, с которыми вы ранее не встречались, включая
локальные метки, автоматическое назначение размеров переходов,
опережающие ссылки и директивы определения структур данных.
Хотя мы познакомили вас с основами программирования на Ас-
семблере и рассмотрим в данной главе некоторые темы развитого
("продвинутого") программирования, мы, конечно, не охватили этим
все аспекты программирования на языке Ассемблера. В конце данной
книги мы предлагаем несколько книг по программированию на Ассемб-
лере и надеемся, что вы достанете одну из них и продолжите самос-
тоятельное изучение Ассемблера.
Желаем успеха!
Префиксы переопределения сегментов
-----------------------------------------------------------------
Чаще всего операнды в памяти определяют ячейки памяти в сег-
менте, на который указывает регистр DX. Например, последователь-
ность инструкций:
.
.
.
mov bx,10h
mov si,5
mov ax,[bx+si+1]
.
.
.
загружает в регистр AX слово, записанное по смещению 16 в сегмен-
TASM2 #3-5/Док = 7 =
те, на который указывает регистр DS. Другой способ сделать это
состоит в том, чтобы указать, что AX загружается из ячейки памяти
по адресу DS:0016.
Одно из исключений из правила загрузки из сегмента, на кото-
рый указывает регистр DS, состоит в том, что строковые инструкции
STOS и MOVS выполняют запись в сегмент, на который указывает ре-
гистр ES, а строковые инструкции SCAS и CMPS используют в качест-
ве источника операнд, на который указывает регистр ES. (Один из
операндов-источников инструкции CMPS находится в сегменте данных,
а другой - в дополнительной сегменте.)
Другое исключение заключается в том, что любой операнд в па-
мяти, использующий регистр BP, обращается к сегменту, на который
указывает регистр SS. Например, инструкции:
.
.
.
mov bp,100h
mov al,[bp+6]
.
.
.
загружают регистр AL содержимым ячейки памяти SS:1006.
Предположим, однако, что вы хотите обратиться, как к операн-
ду в памяти, к ячейке в сегменте, определяемом регистром CS (это
полезно использовать для таблиц перехода, особенно в программах,
состоящих из нескольких сегментов). Или предположим, что хотели
бы получить доступ к ячейке стека с помощью регистра BX, или к
ячейке сегмента, определяемого регистром DS, с помощью BP, либо к
ячейке сегмента, задаваемого регистром ES, с помощью нестроковой
инструкции. Можно ли это сделать?
Ответить можно положительно. Для доступа во многих инструк-
циях к нужным вам сегментам вы можете использовать префиксы пере-
определения сегментов. Например, инструкции:
.
.
.
mov bx,100h
mov cl,ss:[bx+10h]
.
TASM2 #3-5/Док = 8 =
.
.
загружают регистр CL содержимым по смещению 110h в сегменте сте-
ка, а инструкции:
.
.
.
mov bp,200h
mov si,cs:[bp+1]
.
.
.
загружают регистр SI содержимым по смещению 201h в сегменте кода.
В основном все, что требуется, чтобы данная инструкция обра-
щалась к сегменту, отличному от используемого по умолчанию сег-
мента, - это указание перед операндом памяти в этой инструкции
префикса переопределения сегмента: CS:, DS:, ES: или SS:.
Между прочим, префиксы переопределения сегментов на самом
деле не являются "префиксами", так как они предшествуют в строке
инструкции операндам в памяти. Префикс переопределения сегмента в
действительности представляет собой префиксный байт инструкции,
который модифицирует работу инструкции (как и префикс REP, о ко-
тором мы рассказывали в Главе 6). Поэтому, например, когда про-
цессор 8086 встречает байты:
A0 00 00
которые образуют инструкцию:
mov al,[0]
он загружает в регистр AL содержимое, расположенное по смещению 0
в сегмента данных. Однако, поскольку значением префикса переопре-
деления сегмента ES: является 26h, когда процессор 8086 встречает
последовательность:
26 A0 00 00
которая образует инструкцию:
mov al,es:[0]
TASM2 #3-5/Док = 9 =
он загружает в регистр AL содержимое по смещению 0 в дополнитель-
ном сегменте (extra segment), а не в сегменте данных.
TASM2 #3-5/Док = 10 =
Альтернативная форма
-----------------------------------------------------------------
Турбо Ассемблер поддерживает альтернативную форму префикса
переопределения сегмента, когда этот префикс размещается на от-
дельной строке. Эта форма имеет вид SEGCS для префикса переопре-
деления сегмента CS:, SEGDS - для префикса переопределения сег-
мента DS:, SEGES - для префикса переопределения сегмента ES: и
SEGSS - для префикса переопределения сегмента SS:. Каждый из них
действует только на следующую строку кода, а не на все последую-
щие строки. Например, следующие инструкции записывают содержимое
регистра DX по смещению 999h в дополнительном сегменте:
.
.
.
mov si,999h
seges
mov [si],dx
.
.
.
Эту альтернативную форму полезно использовать для указания
префиксов переопределения сегментов в инструкциях, не имеющих
операнда (например, LODSB). Например, далее регистр AL загружает-
ся из SS:SI:
.
.
.
segss
lodsb
.
.
.
Когда префиксы переопределения сегментов не работают
-----------------------------------------------------------------
Префиксы переопределения сегментов работают не во всех инст-
рукциях. Например, их нельзя использовать в строковых инструкциях
при обращении к дополнительному сегменту, то есть инструкция:
lods es:[ByteVar]
TASM2 #3-5/Док = 11 =
допустима, а инструкция:
stos ds:[ByteVar]
работать не будет. Если вы пытаетесь переопределить указанным об-
разом обращение в строковой инструкции к дополнительному сегмен-
ту, то Турбо Ассемблер сообщит вам, что это недопустимо. Однако,
если вы для переопределения сегмента используете префикс SEGCS
или аналогичный, то Турбо Ассемблер не знает, какую инструкцию вы
хотите переопределить, и в этом случае ошибку не генерирует. Нап-
ример, инструкции:
.
.
.
segds
stosb
.
.
.
не приведут к генерации ошибки, но инструкция STOSB будет выпол-
нять запись в дополнительный сегмент, а не в сегмент данных.
Необходимо также иметь в виду, что префиксы переопределения
сегментов не влияют на доступ к стеку. Занесение в стек и извле-
чение из стека всегда выполняется для сегмента стека. Например,
инструкция:
.
.
.
segcs
push [bx]
.
.
.
использует префикс переопределения сегмента для выбора того сег-
мента, из которого нужно извлечь заносимое в стек значение. Это
значение записывается по смещению SP-2 в сегменте стека, как
обычно.
В общем случае следует избегать смешивать префиксы переопре-
деления сегментов с префиксами REP, поскольку могут возникнуть
TASM2 #3-5/Док = 12 =
проблемы в случае прерывания повторений такой инструкции (подроб-
нее об этом рассказано в Главе 6).
Доступ к нескольким сегментам
-----------------------------------------------------------------
Префиксы переопределения сегментов полезно использовать,
если вы хотите обращаться к нескольким сегментам. Такая необходи-
мость неизбежно возникает, если вам необходимо выполнить запись и
в стек, и в сегмент данных (что обычно происходит, если стек ис-
пользуется для динамически распределяемых переменных, а сегмент
данных - для статических переменных). Еще один случай - когда
программа содержит более 64К данных, из-за чего также может пот-
ребоваться доступ к нескольким сегментам.
Одно из особенно полезных применений префиксов переопределе-
ния сегментов - это смешанное использование строковых и нестроко-
вых инструкций. Например, предположим, что все символы данной
строки со значениями, меньшими 20h, вы хотите преобразовать в
пробелы. В следующем кода с целью повышения эффективности исполь-
зуются префиксы переопределения сегментов:
.
.
.
mov ax,SEG StringToConvert
mov es,ax
mov di,OFFSET StringToConvert ; ES:DI указывают на
; преобразуемую строку
cld ; сделать так, чтобы
; инструкция STOSB
; увеличивала DI
ConvertLoop:
mov al,es:[di] ; получить следующий
; символ
and al,al ; это конец строки?
jz ConvertLoopDone ; да, выполнено
cmp al,20h ; нужно ли нам
; преобразовать его?
jnb SaveChar ; нет, сохранить его
mov al,' ' ; преобразовать в
; пробел
SaveChar:
stosb ; сохранить этот символ
; и получить ссылку на
TASM2 #3-5/Док = 13 =
; следующий
jmp ConvertLoop ; проверить следующий
; символ
ConvertLoopDone:
stosb ; конец строки - 0
.
.
.
Локальные метки
-----------------------------------------------------------------
Локальные метки (метки с ограниченной областью действия) -
это одно из положительных качеств Турбо Ассемблера. Давайте пос-
мотрим, как можно их использовать.
Предположим, в исходном модуле у вас есть несколько частей
кода, которые выполняют аналогичные функции. Рассмотрим следующий
пример:
.
.
.
Sub1 PROC
sub ax,ax
IntCountLoop:
add ax,[bx]
inc bx
inc bx
loop IntCountLoop
ret
Sub1 ENDP
.
.
.
Sub2 PROC
sub ax,ax
mov dx,ax
LongCountLoop:
add ax,[bx]
adc dx,[bx+2]
add bx,4
loop LongCountLoop
ret
Sub2 ENDP
TASM2 #3-5/Док = 14 =
.
.
.
Когда две части кода выполняют аналогичные функции, то из
этого часто следует, что они содержат одинаковые метки. Например,
Sub1 и Sub2 содержат метки, отмечающие начало цикла.
Когда в программе имеется только несколько меток, вы легко
можете обеспечить, чтобы все метки были различными. Однако в
больших программах избегать идентичных меток становится затрудни-
тельно. При этом обычно берут работающую подпрограмму, копируют
блок и переименовывают его, модифицируя в новую подпрограмму. Од-
нако при такой процедуре легко забыть изменить ту или иную метку,
что приведет к тому, что новая подпрограмма будет выполнять пере-
ход на метку старой подпрограммы. Например, если вы скопируете и
модифицируете Sub1, чтобы получить Sub2, можно случайно получить
следующее:
.
.
.
Sub2 PROC
sub ax,ax
LongCountLoop:
add ax,[bx]
adc dx,[bx+2]
add bx,4
loop IntCountLoop
ret
Sub2 ENDP
.
.
.
что вызовет переход в середину подпрограммы Sub1 возможно с очень
неприятными последствиями.
Здесь возникает необходимость иметь такой тип метки, который
ограничен по области действия отдельной подпрограммой, что позво-
лит избежать конфликта (по меткам) с другими подпрограммами. Та-
кой тип меток называется локальными метками.
Что же такое локальные метки? Локальные метки, которые по
умолчанию обычно начинаются с двух символов @@, ограничены по об-
ласти действия инструкциями, заключенными между двумя нелокальны-
TASM2 #3-5/Док = 15 =
ми метками. (Нелокальные метки - это те метки, которые определены
с помощью директивы PROC и метки, завершающиеся двоеточием, кото-
рые не начинаются с двух символов @@.) Что касается Турбо Ассем-
блера, то для него локальные метки просто не существуют вне диа-
пазона, ограниченного ближайшими нелокальными метками.
Идентификаторы, которые определяются с помощью директивы
LABEL, не приводят к началу нового блока локальных идентификато-
ров.
Например, вы можете использовать локальные метки для того,
чтобы модифицировать пример, приведенный в начале данного разде-
ла, следующим образом:
.
.
.
Sub1 PROC
sub ax,ax
@@CountLoop:
add ax,[bx]
inc bx
inc bx
loop @@CountLoop
ret
Sub1 ENDP
.
.
.
Sub2 PROC
sub ax,ax
mov dx,ax
@@CountLoop:
add ax,[bx]
adc dx,[bx+2]
add bx,4
loop @@CountLoop
ret
Sub2 ENDP
.
.
.
Здесь вам не нужно заботиться о том, чтобы метки цикла одной
подпрограммы не входили в конфликт с метками цикла другой под-
программы, и нет вероятности, что одна подпрограмма случайно пе-
TASM2 #3-5/Док = 16 =
рейдет на метку другой подпрограммы.
Как можно заметить, перед использованием локальных меток мы
указали директиву LOCALS. В режиме MASM локальные метки по умол-
чанию запрещены и перед тем, как вы сможете их использовать,
должны быть разрешены с помощью директивы LOCALS. В улучшенном
режиме локальные метки по умолчанию разрешены, хотя вы можете их
запретить с помощью директивы NOLOCALS.
Локальные метки полезны также, когда вы хотите получить в
подпрограмме несколько коротких переходов и не хотите терять вре-
мя, придумывая для них уникальные имена. Например, вы можете ис-
пользовать локальные метки, когда проверяете одно из нескольких
значений:
.
.
.
LOCALS
cmp al,'A'
jnz @@P1
jmp HandleA
@@P1:
cmp al,'B'
jnz @@P2
jnz HandleB
@@P2:
cmp al,'C'
jnz @@P3
jmp HandleC
@@P3:
.
.
.
При использовании локальных меток вам не придется заботиться
о том, содержатся ли метки типа P1 и P2 где-либо еще в программе.
Нужно помнить о том, что любая нелокальная метка ограничива-
ет область действия локальной метки. Например, следующая последо-
вательность инструкций ассемблироваться не будет:
.
.
.
Sub1 PROC NEAR
TASM2 #3-5/Док = 17 =
.
.
.
LOCALS
@@CountLoop:
add ax,[bx]
jnz NotZero
inc dx
NotZero:
inc bx
inc bx
loop @@CountLoop
.
.
.
Проблема здесь состоит в том, что между ссылкой на локальную
метку @@CountLoop в инструкции LOOP и определением @@CountLoop
находится нелокальная метка NotZero. Область действия локальной
переменной распространяется только до ближайшей нелокальной мет-
ки, поэтому когда Турбо Ассемблер ассемблирует инструкцию LOOP,
локальная метка @@CountLoop обнаружена не будет.
Форму префикса локального идентификатора (символы @@) можно
изменить на любые два другие символа, которые могут использовать-
ся в начале имени идентификатора. Это можно сделать, указав новые
символы префикса в качестве аргументов директивы LOCAL:
LOCALS__
Здесь в качестве префикса локального идентификатора будут
использоваться два символа подчеркивания. Это может оказаться по-
лезным, когда вы хотите начать использование локальных идентифи-
каторов в модуле, где уже имеются идентификаторы, начинающиеся с
используемого по умолчанию префикса идентификатора.
Когда вы таким образом меняете префикс локального идентифи-
катора, то локальные идентификаторы автоматически разрешаются на
той же строке, точно также, как если бы вы использовали директиву
LOCALS без каких-либо аргументов. Если вы далее для запрещения
локальных идентификаторов укажете директиву NOLOCALS, то Турбо
Ассемблер "вспомнит" символы префикса, которые вы задавали. Это
позволяет вам для восстановления локальных идентификаторов с за-
данными ранее префиксами использовать директиву LOCALCS без аргу-
ментов.
TASM2 #3-5/Док = 18 =
Автоматическое назначение размера перехода
-----------------------------------------------------------------
Много лет назад разработчики процессора 8086 решили, что в
инструкциях перехода могли бы поддерживаться только однобайтовые
смещения переходов. Это означает, что каждый условный переход мог
бы передавать управление только в пределах 128 байт от самой инс-
трукции перехода.
Сегодня, конечно, эти инструкции перехода тоже применяются.
Условные переходы с успехом можно использовать с программах, соз-
давая компактный код (так как инструкция условного перехода имеет
размер только 2 байта). Однако инструкции условного перехода про-
цессора 8086 иногда принуждают писать неэффективный код, посколь-
ку, когда адрес условного переход отстоит слишком далеко и недос-
тижим с помощью однобайтового смещения, приходится писать
5-байтовые последовательности инструкций типа:
.
.
.
jnz NotZero
jmp IsZero
NotZero:
.
.
.
Что еще хуже, невозможно заранее узнать, достижима ли ука-
занная метка с помощью данного условного перехода, поэтому вы
оказываетесь перед выбором: либо попытаться перейти на метку не-
посредственно (что возможно вызовет ошибку ассемблирования), либо
использовать для такого перехода безусловный переход, потеряв при
этом 3 байта и получив проигрыш во времени выполнения. Кроме того
здесь есть еще одна неприятность: добавив внутри цикла одну-две
инструкции, вы можете получить ошибку Relative jump out of range
(переход за допустимые границы).
Хотя Турбо Ассемблер и не может решить всех проблем, связан-
ных с условными переходами, он значительно приближается к этому,
если использовать директиву JUMPS. Если вы укажете эту директиву,
Турбо Ассемблер автоматически преобразует обычные условные пере-
ходы в условные переходы, использующие безусловные переходы, ког-
да это нужно, чтобы достичь целевой метки.
Как выполняется это автоматическое преобразование? Рассмот-
TASM2 #3-5/Док = 19 =
рим следующий фрагмент программы:
.
.
.
JUMPS
RepeatLoop:
jmp SkipOverData
DB 100h DUP (?)
SkipOverData:
.
.
.
dec dx
jnz RepeatLoop
.
.
.
Ясно, что метка RepeatLoop недостижима из инструкции JNZ,
поскольку между ними более 256 байт. Однако, так как была указана
директива JUMPS, ошибки ассемблирования не возникнет. Вместо это-
го Турбо Ассемблер ассемблирует данный код в следующий эквива-
лент:
.
.
.
RepeatLoop:
jmp SkipOverData
DB 100h DUP (?) ; временная память для данных в CS
SkipOverData:
.
.
.
dec dx
jz $+5
jmp RepeatLoop
.
.
.
где вместо инструкции JZ автоматически используются инструкции JZ
и JMP.
Не следует думать, что при использовании директивы JUMPS
TASM2 #3-5/Док = 20 =
Турбо Ассемблер всегда генерирует пару условный/безусловные пере-
ход: когда целевую метку можно достичь с помощью условного пере-
хода, то всегда используется условный переход (преобразование не
выполняется). Например, в следующем примере переход будет выпол-
няться с помощью инструкции JNZ, так как метка находится доста-
точно близко и достижима, если использовать 1-байтовое смещение:
.
.
.
JUMPS
RepeatLoop:
add BYTE PTR [bx],1
inc bx
dec dx
jnz RepeatLoop
.
.
.
Как мы уже упоминали, автоматическое определение Турбо Ас-
семблером размера операций перехода не решает всех проблем, свя-
занных с условными переходами. Турбо Ассемблер прекрасно обраба-
тывает автоматическое определение размера при обратных переходах
(переходах на метки, которые содержатся в коде, предшествующем
инструкции перехода).
Так как Турбо Ассемблер работает обычно, как однопроходный
ассемблер, при автоматическом назначении размера перехода на мет-
ку вперед требуется разумный компромисс. Здесь не все проходит
так гладко, как при обратных переходах. Однако неплохо, что ус-
ловные переходы вперед на ближние метки всегда будут ассемблиро-
ваться при разрешении автоматического определения размера, хуже,
что если окажется, что следующую за ним метку можно достичь с по-
мощью условного перехода, а в текст будут включены несколько до-
полнительных инструкций NOP (пустая операция). Данной проблемы
можно избежать, если указать с помощью параметра /m Турбо Ассемб-
леру на необходимость выполнения нескольких проходов (хотя это
средство несколько замедляет процесс ассемблирования).
Если немного подумать, то станет понятно, почему при автома-
тическом определении размера переходов при ссылках вперед не
всегда генерируется оптимальный код. Когда Турбо Ассемблер дости-
гает инструкции условного перехода, в которой имеется ссылка впе-
ред, то невозможно определить, как далеко находится эта метка
(ведь Турбо Ассемблер этой метки еще не обнаружил). Если автома-
TASM2 #3-5/Док = 21 =
тическое определение размеров условного перехода разрешено, то
Турбо Ассемблер мог бы сгенерировать условный переход (инструкция
размером 2 байта), если целевая метка находится достаточно близ-
ко, и условный переход через безусловный переход (2-байтовая инс-
трукция, за которой следует инструкция размером 3 байта) в про-
тивном случае. К сожалению, когда Турбо Ассемблер обнаруживает
переход со ссылкой вперед, он еще не знает, необходима ли 2-бай-
товая инструкция или пара инструкций размером в 5 байт.
Тем не менее Турбо Ассемблер может выбрать некоторые размеры
правильно. При этом у него нет выбора и для надежности он резер-
вирует 5 байт для пары инструкций безусловного/условного пере-
хода. После этого, когда Турбо Ассемблер затем достигает целевой
метки и видит, что можно обойтись 2-байтовой инструкцией, он ас-
семблирует условный переход, за которым следует три инструкции
NOP (чтобы заполнить резерв в 5 байт).
Предположим, Турбо Ассемблер резервирует следующие инструк-
ции:
.
.
.
JUMPS
jz DestLabel
inc ax
.
.
.
Если инструкция JZ не может достичь целевой метки непосредс-
твенно, Турбо Ассемблер ассемблирует следующий эквивалент:
.
.
.
jnz $+5 ; два байта
jmp DestLabel ; три байта
inc ax
.
.
.
Если, с другой стороны, инструкция JZ может достичь целевой
метки непосредственно, то Турбо Ассемблер ассемблирует следующие
инструкции:
TASM2 #3-5/Док = 22 =
.
.
.
jz DestLabel ; два байта
nop ; каждая операция NOP
; имеет размер 1 байт
nop
nop
inc ax
.
.
.
Турбо Ассемблер должен резервировать при каждом автоматичес-
ком определении размера условного перехода со ссылкой вперед 5
байт, поэтому условные переходы с автоматическим определением
размера, которые могут достичь своих меток, дополняются тремя ин-
струкциями NOP. Эти три дополнительные инструкции NOP приводят к
потере скорости работы (каждая из них требует для выполнения 3
циклов на процессоре 8086) и увеличивают объем программы. Таким
образом, мы можем вам посоветовать в тех случаях, когда размер
кода и скорость выполнения имеют решающее значение, использовать
автоматическое определение размеров условных переходов со ссылка-
ми вперед достаточно редко.
Если вы пишете программу, содержащую высокопроизводительный
код, то можно разрешить автоматическое определение размера услов-
ных переходов для некритических участков вашей программы и запре-
тить его для критических участков. Либо можно разрешить автома-
тическое определение размеров переходов с обратными ссылками и
запретить его для переходов со ссылками вперед. Это можно сде-
лать, сочетая директиву JUMPS с директивой NOJUMPS (которая зап-
рещает автоматическое определение размеров переходов).
Например, в следующем фрагменте программы автоматическое оп-
ределение размеров переходов используется только для обратных пе-
реходов, но не для переходов со ссылками вперед:
.
.
.
LoopTop:
.
.
.
TASM2 #3-5/Док = 23 =
lodsb
cmp al,80h
NOJUMPS
jb SaveByteValue
neg al
SaveByteValue:
stosb
.
.
.
dec dx
JUMPS
jnz LoopTop
.
.
.
Здесь мы непосредственно задали 2-байтовый условный переход
для перехода со ссылкой на метку SaveByteValue, а для обратного
перехода на LoopTop Турбо Ассемблер сам выберет лучший код.
Кстати, в начале ассемблирования всегда (по умолчанию) выби-
рается директива NOJUMPS. Если вы хотите использовать автомати-
ческое определение размеров переходов, нужно явно разрешить это с
помощью директивы JUMPS.
Ссылки вперед на код и данные
-----------------------------------------------------------------
В последнем разделе мы видели пример того, как условные пе-
реходы со ссылками вперед могут привести к тому, что при разреше-
нии автоматического определения размера переходов Турбо Ассемблер
генерирует менее эффективный код. Однако суть дела состоит в том,
что проблемы в Турбо Ассемблере могут вызвать все виды ссылок
вперед (или опережающих ссылок), поэтому опережающих ссылок (то
есть ссылок на метки, которые находятся где-то дальше в програм-
ме) по возможности следует избегать.
Почему это так? Когда Турбо Ассемблер ассемблирует исходный
модуль, он выполняет один проход, продвигаясь от первой строки
исходного модуля к последней. Это означает, что Турбо Ассемблер
транслирует первую строку в модуле, затем вторую строку, третью
строку и т.д. Хотя это может показаться достаточно очевидным, но
с этим могут быть связаны менее очевидные вещи: Турбо Ассемблер
не знает заранее о строке кода ничего, пока он не достиг ее, поэ-
TASM2 #3-5/Док = 24 =
тому ссылки вперед заставляют Турбо Ассемблер делать предположе-
ния, которые могут оказаться некорректными. Если эти предположе-
ния в самом деле оказались некорректны, Турбо Ассемблер может
генерировать код, эффективность которого ниже максимальной. Даже
если Турбо Ассемблер сгенерирует эффективный код, может оказаться
необходимым вернуться не предыдущие строки для выполнения коррек-
тировки. Из-за этого для ассемблирования потребуется больше вре-
мени.
Рассмотрим следующий пример:
.
.
.
jmp DestLabel
.
.
.
DestLabel:
.
.
.
Когда Турбо Ассемблер встречает строку:
jmp DestLabel
он еще не дошел до определения метки DestLabel. В итоге Турбо Ас-
семблер не может определить, является ли эта метка дальней или
ближней, и, если она ближняя (SHORT или NEAR), можно ли достичь
ее с помощью 1-байтового смещения, или для этого требуется полное
2-байтовое смещение. В итоге, чтобы продолжить ассемблирование,
Турбо Ассемблеру приходится делать предположения о метке
DestLabel.
Турбо Ассемблер может предположить, что эта метка является
дальней (FAR), и зарезервировать для дальней инструкции JMP 5
байтов. Однако большинство переходов представляют собой 3-байто-
вые ближние переходы, и было бы расточительным тратить лишние 2
байта для каждого ближнего перехода со ссылкой вперед. С дру-
гой стороны, Турбо Ассемблер может предположить, что метку
DestLabel можно достичь с помощью однобайтового смещения и инс-
трукции JUMP SHORT. Здесь проблема состоит в том, что многие пе-
реходы не являются короткими, и если Турбо Ассемблер зарезервиро-
вал только 2 байта, может возникнуть ошибка.
TASM2 #3-5/Док = 25 =
В качестве компромиссного решения Турбо Ассемблер предпола-
гает, что все переходы со ссылками вперед являются ближними, если
вы не зададите обратное с помощью операций SHORT или FAR PTR. Для
переходов со ссылками вперед всегда резервируются три байта. Если
окажется, что переход дальний, то возникает ошибка. Поэтому вы
всегда должны использовать для возможности ассемблирования пере-
ходов вперед на дальние метки операцию FAR PRT.
С другой стороны, если переход со ссылкой вперед оказывается
коротким, Турбо Ассемблер транслирует короткий переход, но для
заполнения оставшихся 3 байт, которые были зарезервированы для
перехода, включает 3 операции NOP. Например, Турбо Ассемблер ас-
семблирует инструкции:
.
.
.
jmp DestLabel
DestLabel:
.
.
.
в код:
.
.
.
jmp SHORT DestLabel
nop
DestLabel:
.
.
.
Хотя такой переход работает прекрасно и выполняется быстро,
он длиннее, чем требуется. Конечно, чтобы превратить любой пере-
ход на метку вперед в 2-байтовую инструкцию, вы можете использо-
вать операцию SHORT. Но это не так удобно, как если бы Турбо Ас-
семблер имел возможность генерировать соответствующий переход
автоматически.
Важно понимать, что опережающая ссылка не является здесь
непреодолимым препятствием. Если бы Турбо Ассемблер знал расстоя-
ние до целевой метки, то можно было бы ассемблировать наиболее
эффективный переход. Но при использовании таких ссылок Турбо Ас-
TASM2 #3-5/Док = 26 =
семблер не может знать расстояние до целевой метки, пока он не
дошел до нее, и не может достичь этой метки, пока он не ассембли-
ровал операцию условного перехода. Турбо Ассемблер разрешает эту
дилемму, сделав упрощенное предположение, что дает ему возмож-
ность продолжать работу, но возможно ценой получения кода больше-
го объема, чем это необходимо.
Когда Турбо Ассемблеру известен тип перехода (SHORT, NEAR
или FAR), то будет генерироваться максимально эффективный код.
Можно подвести итог, то полезно для короткий переходов со ссылкой
вперед использовать операцию SHORT (и, конечно, операцию FAR PTR
для дальних переходов).
Переходы - это не единственные инструкции, в которых следует
избегать использования опережающих ссылок. Неэффективный код мо-
жет также генерироваться при использовании ссылок вперед на дан-
ные. Рассмотрим следующий пример:
.
.
.
.CODE
.
.
.
mov bl,Value
.
.
.
Value EQU 1
.
.
.
Когда Турбо Ассемблер достигает инструкции MOV, то невозмож-
но определить, представляет ли собой Value приравненную метку или
переменную в памяти. Если Value - это переменная в памяти, то
потребуется 4-байтовая инструкция, а если Value - это приравнен-
ная метка (например, которая используется, как константа), то
нужна 2-байтовая инструкция.
Как обычно, чтобы продолжить ассемблирование, Турбо Ассемб-
лер должен предположить худший случай, поэтому для инструкции
MOV резервируется 4 байта. Когда затем будет достигнута метка
Value и окажется, что это приравненная метка, а не переменная в
памяти, то Турбо Ассемблер возвращается к инструкции MOV и форми-
TASM2 #3-5/Док = 27 =
рует 2-байтовую инструкцию с операндом-константой и, чтобы запол-
нить зарезервированные третий и четвертый байты, должен включить
две инструкции NOP. Заметим, что ничего этого не случилось бы,
если бы Value была определена перед инструкцией MOV, так как Тур-
бо Ассемблер знал бы, что Value - это не переменная в памяти.
Фактически, при использовании обратных ссылок ни одна из
указанных проблем не возникает, так как Турбо Ассемблер всегда
знает о таких метках все, что ему нужно. В результате для тех ин-
струкций, где используются обратные ссылки, он ассемблирует код с
максимально возможной эффективностью. Отсюда можно заключить, что
желательно там, где это возможно, избегать использования опережа-
ющих ссылок.
Вы можете подумать, не является ли проблема опережающих ссы-
лок столь же серьезной для вызовов, как для переходов? Ответ
здесь отрицательный. Дальние вызовы со ссылками вперед должны
иметь переопределения типа FAR PTR, так как Турбо Ассемблер пред-
полагает, что вызовы с опережающими ссылками являются ближними.
Поскольку отсутствуют такие вещи, как короткий вызов, то неэффек-
тивный код для вызовов никогда не генерируется.
Многие опережающие ссылки приводят к ошибкам ассемблирова-
ния, а не к неэффективному коду. Например, опережающие ссылки на
приравненную метку не могут ассемблироваться, а опережающие ссыл-
ки на дальние метки не могут ассемблироваться без переопределения
типа.
Даже если Турбо Ассемблер может генерировать эффективный код
для опережающих ссылок, ассемблирование выполняется медленнее,
чем для обратных ссылок. Это происходит из-за того, что Турбо Ас-
семблер должен возвращаться к каждой инструкции с опережающей
ссылкой и должным образом ее ассемблировать, после того как зна-
чение и тип метки стали известны.
Вывод ясен: насколько это возможно, в программе нужно избе-
гать ссылок вперед, что позволит Турбо Ассемблеру сгенерировать
наиболее эффективный код за минимальное время. Например, полезно
размещать определения данных в начале исходного модуля, до того,
как на них ссылается код. Когда вы не можете избежать опережающих
ссылок, то всегда используйте операцию определения типа, что поз-
волит Турбо Ассемблеру точно знать, с каким типом метки вы рабо-
таете. Если вы с помощью параметра /m задаете несколько проходов,
то будет сгенерирован оптимальный код, но процесс ассемблирования
потребует большего времени, чем при одном проходе.
TASM2 #3-5/Док = 28 =
Использование блоков повторения и макрокоманд
---------------------------------------------------------------
Одна из работ, которую компьютер прекрасно выполняет - это
повторяющиеся действия. Вы можете утомиться при вводе десятков
значений для директив DB или мало отличающихся вариантов одного и
того же исходного кода, но компьютер никогда не устанет от та-
кой работы. Чтобы освободить вас от такого рода монотонных опера-
ций, в Турбо Ассемблере предусмотрены макрокоманды и блоки повто-
рения.
Блоки повторения
-----------------------------------------------------------------
Блок повторения начинается с директивы REPT и заканчивается
директивой ENDM. Код блока повторения ассемблируется то число
раз, которое задается операндом директивы REPT. Например, при ас-
семблировании инструкций:
.
.
.
REPT 10
DW 0
ENDM
.
.
.
генерируется тот же код, что и при ассемблировании инструкций:
.
.
.
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
.
TASM2 #3-5/Док = 29 =
.
.
Это не должно вас удивлять, поскольку тоже самое можно сде-
лать с помощью:
DW 10 DUP (0)
Скомбинируем блоки повторения и директиву = для создания
таблицы первых десяти целых чисел:
.
.
.
IntVal = 0
REPT 10
DW IntVal
IntVal = IntVal+1
ENDM
.
.
.
При этом генерируется следующий эквивалент:
.
.
.
DW 0
DW 1
DW 2
DW 3
DW 4
DW 5
DW 6
DW 7
DW 8
DW 9
.
.
.
Попробуйте сделать это с помощью директивы DUP! Кроме того,
если вы захотите получить 100 первых целых чисел, вам потребуется
только изменить значение операнда директивы REPT на 100: это, ко-
нечно, легче, чем вводить 100 строк.
TASM2 #3-5/Док = 30 =
Одно из прекрасных применений директивы REPT состоит в гене-
рации таблиц, использующихся для быстрого умножения и деления.
Например, далее перемножаются числа от 0 до 99 (которые хранятся
в BX) на 10 (очень быстро), а результат помещается в регистр AX.
.DATA
TableOfMultipleOf10 LABEL WORD
BaseVal = 0
REPT 100
DW BaseVal
BaseVal = BaseVal+10
ENDM
.
.
.
.CODE
.
.
.
shl bx,1 ; подготовка для поиска в таблице
; записей размером в слово
mov ax,[TableOfMultiplesOf10+bx] ; поиск результата
; умножения на 10
.
.
.
Имейте в виду, что текст в блоке повторения просто ассембли-
руется столько раз, сколько указано в операнде директивы REPT.
Нет разницы между 10-кратным выполнением блока повторения и соз-
данием 9 дополнительных копий кода в этом блоке и ассемблировани-
ем 10 вхождений этого кода.
Это означает, что внутри блока повторения можно разместить
любой допустимый код, включая инструкции. Например, далее генери-
руется код для деления 32-битового беззнакового значения в ре-
гистрах DX:AX на 16:
.
.
.
REPT 4
shr dx,1
rcr ax,1
TASM2 #3-5/Док = 31 =
ENDM
.
.
.
Блоки повторения могут быть вложенными. Например, далее ге-
нерируется 10 инструкций NOP:
.
.
.
REPT 5
REPT 2
nop
ENDM
ENDM
.
.
.
Блоки повторения и параметры-переменные
-----------------------------------------------------------------
Параметр-переменная блока повторения может задаваться с по-
мощью директив IRP и IRPC.
Директива IRP подставляет первую запись в списке параметра
при первом повторении блока, вторую запись - при втором повторе-
нии блока и т.д. до тех пор, пока список не будет исчерпан. Нап-
ример, инструкции:
.
.
.
IRP PARM,<0,1,4,9,16,25>
DB PARM
ENDM
.
.
.
генерируют:
.
.
TASM2 #3-5/Док = 32 =
.
DB 0
DB 1
DB 4
DB 9
DB 16
DB 25
.
.
.
Директива IRPC работает аналогично, только она подставляет
для каждого повторения блока один символ из строки. Следующий код
устанавливает флаг нуля, если регистр AL равен одному из символов
в строке, являющейся вторым аргументом IRPC:
.
.
.
IRPC TEST_CHAR,azklg
cmp al,'&TEST_CHAR&'
jz EndCompare
ENDM
EndCompare:
.
.
.
Амперсанд (&) в последнем примере используется для того,
чтобы задать вычисление параметра блока повторения даже внутри
кавычек. Амперсанд - это макрооперация, которая работает в блоке
повторения, поскольку блоки повторения представляют собой один из
типов макрокоманды. Здесь допускается применять также другие мак-
росредства, такие, как директивы LOCAL и EXITM. Об этом мы рас-
скажем позднее.
Макрокоманды
-----------------------------------------------------------------
Суть работы макрокоманды весьма проста: вы присваиваете имя
блоку текста (макрокоманде), затем, когда Турбо Ассемблер обнару-
живает это имя в исходном коде, то ассемблируется связанных с
этим именем текст. Можно считать, что имя макрокоманды расширяет-
ся до полного текста этой макрокоманды, поэтому для описания
подстановки текста макрокоманды вместо ее имени часто использует-
TASM2 #3-5/Док = 33 =
ся термин "макрорасширение".
Полезной аналогией здесь может служить включаемый файл. Ког-
да Турбо Ассемблер встречает директиву INCLUDE, то ассемблируется
текст, содержащийся в заданном файле (как если бы он содержался в
исходном модуле, где используется директива INCLUDE). Если обна-
руживается вторая директива INCLUDE с тем же именем файла, то
Турбо Ассемблер снова ассемблирует этот текст.
Макрокоманды аналогичны включаемым файлам в том плане, что
текст или тело макрокоманды ассемблируется каждый раз, когда об-
наруживается имя макрокоманды. Однако макрокоманды отличаются
значительно большей гибкостью, чем включаемые файлы, так как им
могут передаваться параметры и они могут содержать локальные мет-
ки. Они обрабатываются значительно быстрее, чем включаемые файлы,
так как текст макрокоманды не требуется читать с диска (как
файл). Давайте рассмотрим основы работы макрокоманд.
В следующем фрагменте программы макрокоманда MULTIPLY_BY_4
используется для умножения значения в регистре AX на 4 и сохране-
ния результата в регистрах DX:AX:
.
.
.
MULTIPLY_BY_4 MACRO
sub dx,dx
shl ax,1
rcl dx,1
shl ax,1
rcl dx,1
ENDM
.
.
.
mov ax,[MemVar]
MULTIPLY_BY_4
mov WORD PTR [Result],ax
mov WORD PTR [Result+2],dx
.
.
.
Когда Турбо Ассемблер встречает имя макрокоманды MULTIPLY_BY
_4, он ассемблирует в этой точке 4 инструкции, составляющие тело
макрокоманды. Это почти тоже самое, как если была определена но-
TASM2 #3-5/Док = 34 =
вая инструкция MULTIPLY_BY_4, которая может использоваться точно
также, как вы используете инструкции MOV или MUL. Конечно, новая
макроинструкция состоит из 5 инструкций процессора 8086, но при-
веденный выше код гораздо легче читается, если использовать мак-
рокоманду, чем без нее.
Вместо макрокоманды в данном примере вы могли бы с равным
успехом использовать подпрограмму с именем MyltiplyBy4:
.
.
.
MultiplyBy4 PROC
sub dx,dx
shl ax,1
rcl dx,1
shl ax,1
rcl dx,1
ret
MultiplyBy4 ENDP
.
.
.
mov ax,[MemVar]
call MultiplyBy4
mov WORD PTR [Result],ax
mov WORD PTR [Result+2],dx
.
.
.
Что лучше выбрать, подпрограмму или макрокоманду? В общем
случае код меньшего объема можно получить, если использовать
подпрограмму, поскольку ее код ассемблируется только один раз, а
в теле программы содержатся вызовы. Однако более быстрый код мож-
но получить с помощью макрокоманд, поскольку здесь не теряется
время на отработку инструкций CALL и RET. Кроме того одну и ту же
макрокоманду можно использовать для генерации для аналогичных за-
дач слегка отличающегося кода, а подпрограмму нет.
В общем случае лучше использовать подпрограммы для минимиза-
ции объема кода и макрокоманды для увеличения скорости и гибкос-
ти.
Какого рода гибкость предоставляет макрокоманда? Эта гиб-
кость ограничивается только вашим воображением, так как макроко-
TASM2 #3-5/Док = 35 =
манды могут воспринимать параметры и содержать директивы условно-
го ассемблирования. Параметры макрокоманды указываются, как
операнды директивы MACRO. Например, VALUE и LENGTH - это парамет-
ры макрокоманды FILL_ARRAY, которая определена следующим обра-
зом:
.
.
.
FILL_ARRAY MACRO VALUE,LENGTH
REPT LENGTH
DB VALUE
ENDM
ENDM
.
.
.
При вызове макрокоманды ее параметры можно указывать, как
операнды вызова макрокоманды. Например, макрокоманду FILL_ARRAY
можно вызывать, как:
.
.
.
ByteArray LABEL BYTE
FILL_ARRAY 2,9
.
.
.
Параметры, которые указываются в вызове макрокоманды (2 и 9
в приведенном примере), называются фактическими параметрами. Па-
раметры, которые указываются в определении макрокоманды (VALUE и
LENGTH в данном примере) называются формальными параметрами. При
каждом вызове макрокоманды перед ее расширением формальные пара-
метры устанавливаются в значения соответствующих фактических па-
раметров. Поэтому:
.
.
.
ByteArray LABEL BYTE
FILL_ARRAY 2,9
.
.
TASM2 #3-5/Док = 36 =
.
приведет к следующему коду на Ассемблере:
.
.
.
ByteArray LABEL BYTE
REPT 9
DB 2
ENDM
.
.
.
При макровызове значения фактических параметров подставляют-
ся вместо формальных параметров в макроопределении, поэтому вы
можете генерировать различный макрокод, просто изменив в макровы-
зыве фактические параметры. Например, если вы хотите инициализи-
ровать массив ByteArray таким образом, чтобы он имел длину 8 бай-
тов и значение 0FFh, а ByteArray2 - чтобы он имел длину 100 бай-
тов и значение 0, все, что нужно для этого сделать, это:
.
.
.
ByteArray LABEL BYTE
FILL_ARRAY 0ffh,8
ByteArray2 LABEL BYTE
FILL_ARRAY 0,100h
.
.
.
Формальные параметры могут использоваться в любом месте мак-
рокоманды. Однако возникает проблема, когда формальные параметры
смешиваются с другим текстом. Например, в макрокоманде:
.
.
.
PUSH_WORD_REG MACRO RLETTER
push RLETTERx
ENDM
.
.
TASM2 #3-5/Док = 37 =
.
Турбо Ассемблер не знает, является ли строка RLETTER, вклю-
ченная в RLETTERx, именем формального параметра, или частью опе-
ранда инструкции PUSH, поэтому он предполагает, что это часть
операнда. Однако маловероятно, что занесение в стек RLETTERx при-
ведет к успеху, так как может оказаться, что у вас есть перемен-
ная с тем же именем. В любом случае желаемый результат получен не
будет.
Решением является заключение формального параметра в пару
символов &. Когда Турбо Ассемблер обнаруживает текст макрокоман-
ды, заключенный в символы &, он проверяет сначала, является ли
этот текст именем формального параметра. Если это так, то он
подставляет значение данного параметра. Если такой текст не явля-
ется именем формального параметра, то Турбо Ассемблер игнорирует
амперсанды (&).
Например, следующее расширение макрокоманды PUSH_WORD_REG:
.
.
.
PUSH_WORD_REG MACRO RLETTER
push &RLETTER&x
ENDM
.
.
.
PUSH_WORD_REG
ассемблируется в push bp.
Амперсанды требуются только там, где ссылка на формальный
параметр находится под вопросом. Например, в следующем тексте они
не нужны:
.
.
.
PUSH_WORD_REG MACRO REGISTER
push REGISTER
ENDM
.
.
.
TASM2 #3-5/Док = 38 =
Однако использование амперсандов никакого вреда не принесет,
поэтому лучше используйте их в том случае, если вы сомневаетесь,
нужны они, или нет.
TASM2 #3-5/Док = 39 =
Вложенные макрокоманды
-----------------------------------------------------------------
Как вы уже видели, макрокоманды могут содержать блоки повто-
рения. Макрокоманды могут также вызывать другие макрокоманды.
Например:
.
.
.
PUSH_WORD_REG MACRO REGISTER
push REGISTER
ENDM
.
.
.
PUSH_ALL_REG MACRO
IRP REG,
PUSH_WORD_REG REG
ENDM
.
.
.
Здесь макрокоманда PUSH_ALL_REG содержит блок повторения,
который, в свою очередь, содержит вызов макрокоманды
PUSH_ALL_REG.
TASM2 #3-5/Док = 40 =
Макрокоманды и условия
-----------------------------------------------------------------
Возможно, наиболее мощное средство, доступное в макрокоман-
дах, - это возможность включать в них директивы условного ассемб-
лирования. Это позволяет одной макрокоманде ассемблировать раз-
личные виды кода, в зависимости от состояния приравненных меток и
параметров каждого вызова макрокоманды.
Давайте вернемся к показанному ранее примеру макрокоманды,
выполняющей умножение. В данном случае, если множитель передавае-
мый макрокоманде, представляет собой степень числа 2, то для бо-
лее быстрого выполнения умножения мы будем использовать инструк-
ции сдвига и циклического сдвига, а в противном случае используем
инструкцию MUL. Макрокоманда выглядит следующим образом:
.
.
.
MULTIPLY MACRO FACTOR
;
; Проверить, является ли FACTOR степенью 2
;
IS_POWER_OF_TWO = 0
COUNT = 15
POWER_OF_TWO = 800h
REPT 16
IF POWER_OF_TWO EQ FACTOR
IS_POWER_OF_TWO = 1 ; множитель представляет
; собой степень числа 2
EXITM
ENDIF
COUNT = COUNT - 1
POWER_OF_TWO = POWER_OF_TWO SHR 1
ENDM
IF IS_POWER_OF_TWO
sub dx,dx
REPT COUNT
shl al,1
rcl dx,1
ENDM
ELSE
mov dx,FACTOR
mul dx
ENDIF
TASM2 #3-5/Док = 41 =
ENDM
.
.
.
MULTIPLY в ходе работы проверяет, выполняется ли умножение
на степень 2, и ассемблирует соответствующий код. Поэтому код:
MULTIPLY 10
ассемблируется в:
.
.
.
mov dx,10
mul dx
.
.
.
а код:
MULTIPLY 8
ассемблируется в следующий вид:
.
.
.
sub dx,dx
shl ax,1
rcl dx,1
shl ax,1
rcl dx,1
shl ax,1
rcl dx,1
.
.
.
Нужно учитывать, что расширение макрокоманд выполняется во
время ассемблирования, а не во время выполнения. Каждый вызов
макрокоманды MULTIPLY приводит к ассемблированию нового кода: ди-
ректива IF в этой макрокоманде определяет, какие инструкции ас-
семблировать. Не путайте макрокоманды с подпрограммами, а услов-
TASM2 #3-5/Док = 42 =
ное ассемблирование с операторами if и аналогичными в языках
высокого уровня.
Завершение макрорасширения с помощью директивы EXITM
-----------------------------------------------------------------
В последнем примере содержится директива, которую мы до сих
пор не рассматривали - директива EXITM. Эта директива указывает
Турбо Ассемблеру, что нужно прекратить расширение текущей макро-
команды или блока повторения. Если же текущая макрокоманда или
блок повторения является вложенным по отношению к другой макроко-
манде или блоку повторения, то расширение внешнего блока повторе-
ния или макрокоманды продолжается.
В нашем примере директива EXITM прекращает расширение блока
повторения, содержащего EXITM, однако расширение макрокоманды
MULTIPLY, которая содержит блок повторения, продолжается. Факти-
чески в данном примере инструкции REPT и EXITM используются сов-
местно для получения аналога цикла while в языке Си. Здесь опять
нужно упомянуть о том, что цикл while в MULTIPLY - это цикл этапа
ассемблирования, позволяющий определить, какой код нужно ассемб-
лировать, а не цикл этапа выполнения.
TASM2 #3-5/Док = 43 =
Определение меток с помощью макрокоманд
-----------------------------------------------------------------
Одна из потенциальных проблем в макрокомандах возникает,
когда вы хотите определить в макрокоманде метку. Например, следу-
ющий код приведет к ошибке из-за повторного определения метки
SkipLabel, поскольку эта метка определяется в расширении макроко-
манды DO_DEC:
.
.
.
DO_DEC MACRO
jcxz SkipLabel
dec cx
SkipLabel:
ENDM
.
.
.
DO_DEC
.
.
.
DO_DEC
.
.
.
К счастью, в Турбо Ассемблере предусмотрено простое реше-
ние данной проблемы в виде директивы LOCAL. Эта директива в мак-
рокоманде приводит к тому, что область действия указанной метки
или меток ограничивается данной макрокомандой. Например, директи-
ву LOCAL можно использовать, чтобы привести предыдущий пример к
корректному виду:
.
.
.
DO_DEC MACRO
LOCAL SkipLabel
jcxz SkipLabel
dec cx
SkipLabel:
ENDM
.
TASM2 #3-5/Док = 44 =
.
.
DO_DEC
.
.
.
DO_DEC
.
.
.
Если директива LOCAL используется в макрокоманде, она должна
указываться непосредственно за директивой MACRO. С помощью одной
директивы LOCAL локальными можно объявить несколько меток. Можно
также использовать несколько директив LOCAL:
.
.
.
TEST_MACRO MACRO
LOCAL LoopTop,LoopEnd,SkipInc
LOCAL NoEvent,MacroDone
.
.
.
ENDM
.
.
.
Имена, присваиваемые в действительности локальным меткам,
имеют вид:
??XXXX
где XXXX - это шестнадцатиричное число в диапазоне от 0 до
0FFFFh. Следовательно, вы не должны присваивать вашим меткам име-
на, которые начинаются с символов ??, иначе это может привести к
конфликту с локальными метками, генерируемыми Турбо Ассемблером.
Опережающие ссылки на макрокоманды не допускаются: макроко-
манды должны определяться до того, как они вызываются. В свете
того, что ранее говорилось нами об опережающих ссылках, это имеет
определенный смысл, так как Турбо Ассемблер не знает, сколько
байт нужно резервировать для макрокоманды, на которую имеется
опережающая ссылка. Однако с другой стороны макрокоманды можно
TASM2 #3-5/Док = 45 =
определять в любом месте исходного модуля.
В макрокоманде может содержаться любая допустимая в Ассемб-
лере строка. Это включает в себя директивы определения данных, а
также код и даже директивы определения сегментов, все типы меток
и директивы управления листингом.
Имеется несколько директив, которые созданы специально для
использования в макрокомандах. Это директивы IFDIF, IFIDN,
IFDIFI, IFIDNI, IFB и IFNB. Есть также несколько директив услов-
ной обработки ошибок, которые используются в макрокомандах. Это
директивы ERRDIF, ERRIDN, ERRDIFI, ERRIDNI, ERRB и ERRNB. Об этих
директивах рассказывается в Главе 6 и в "Справочном руководстве"
(в Главе 3).
Имеется также несколько специальных операций, которые можно
использовать в макрокомандах:
& Операция подстановки
<> Строковая операция литерального текста
! Операция символа в кавычках
% Операция вычисления выражения
;; Подавление комментария
Операция подстановки & обсуждалась в предыдущем разделе,
посвященном макрокомандам. Эти и другие специальные операции бо-
лее полно определяются в "Справочном руководстве".
Развитые структуры данных
-----------------------------------------------------------------
Для облегчения функций управления сложными структурами дан-
ных в Турбо Ассемблере предусмотрены три директивы: STRUC, RECORD
и UNION. Вы возможно заметили, что названия этих директив анало-
гичны тем, которые используются в языках высокого уровня. Дейс-
твительно, есть некоторое подобие между директивами структур дан-
ных Турбо Ассемблера и теми, которые используются в языках
высокого уровня.
Однако директивы определения структур данных в языке Ассемб-
лера, будучи полезными, являются тем не менее не такими развиты-
ми, как использующиеся в языках высокого уровня. Например, в язы-
ке Ассемблера область действия имени элемента структуры не
ограничивается этой структурой, поэтому имя каждого элемента
структуры должно быть уникально в исходном модуле.
TASM2 #3-5/Док = 46 =
Кроме того, в отличие от языков Си и Паскаль, в языке Ас-
семблера директивы определения структур данных - это удобство, а
не необходимость: можно работать со структурами записей, данными
и объединениями Ассемблера не используя директивы определения
структур данных. Тем не менее эти директивы очень удобны и их
стоит изучить.
Следующее ниже обсуждение относится к Турбо Ассемблеру, ра-
ботающему в режиме MASM. В улучшенном режиме (Ideal) Турбо Ас-
семблер поддерживает значительно более мощные формы директив оп-
ределения структур данных. О расширенных средствах улучшенного
режима можно прочитать в Главе 11.
Перед тем, как мы начнем рассказывать о структурах данных
Турбо Ассемблера, стоит сделать одно замечание: если на них от-
сутствуют в инструкциях и директивах опережающие ссылки, структу-
ры, записи и объединения могут содержаться в любом месте текста.
Директива STRUC
-----------------------------------------------------------------
Директиву STRUC, которая позволяет вам определить структуру
данных, полезно определить в том случае, когда вам приходится
иметь дело с данными, разделенными на логические группы.
Для тех, кто знаком с языком Си, директива STRUC аналогичная
оператору структуры языка Си.
Например, предположим, что вы хотите определить структуру
данных, содержащую имя, возраст и доход для одного пользователя.
Вот эта структура:
CLIENT STRUC
NAME DB 'Здесь указывается имя...'
AGE DW ?
INCOME DD ?
CLIENT ENDS
Структура CLIENT (пользователь) содержит три поля: поле NAME
содержит имя (до 20 символов длиной), поле AGE содержит возраст,
который хранится, как 16-битовое значение, и поле INCOME, содер-
жащее доход (хранится, как 32-битовое значение).
Структуру CLIENT можно использовать следующим образом:
TASM2 #3-5/Док = 47 =
.
.
.
CLIENT STRUC
NAME DB 'Здесь указывается имя...'
AGE DW ?
INCOME DD ?
CLIENT ENDS
.
.
.
.DATA
MisterBark CLIENT <'John Q. Bark',32,10000>
.
.
.
.CODE
.
.
.
mov ax,[MisterBark.Age]
mov bx,OFFSET MisterBark
mov ax,WORD PTR [bx.INCOME]
mov dx,WORD PTR [bx.INCOME+2]
.
.
.
Этот пример стоит подробно рассмотреть. Отметим сначала, что
определения структуры завершаются директивой ENDS. Это та же ди-
ректива, что заканчивает определения сегментов. Например, далее
определяется структура внутри сегмента данных.
.
.
.
_Data SEGMENT WORD PUBLIC 'DATA'
.
.
.
Test STRUC
.
.
.
Test ENDS
TASM2 #3-5/Док = 48 =
.
.
.
_Data ENDS
Во-вторых, отметим, что переменная MisterBark структуры
CLIENT создана так, как если бы существовал новый тип данных с
именем CLIENT (фактически, именно такой тип данных вы и создали,
определив структуру CLIENT). Если для структуры CLIENT вы приме-
ните операцию SIZE, то получите значение 26 (размер структуры).
При создании переменной MisterBark в угловых скобках предус-
мотрены три параметра описания. Эти параметры становятся началь-
ными значениями соответствующих полей переменной MisterBark.
Строка 'John Q. Bark' - это начальное значения поля NAME, 32 -
начальное значение поля AGE, а 10000 - начальное значение поля
INCOME.
Вам не требуется определять начальные значения всех полей,
определенных в структуре. Например:
MisterBark CLIENT <>
не инициализирует никаких полей MisterBark, а
MisterBark CLIENT <,,19757>
инициализирует только поле INCOME. Однако угловые скобки требует-
ся указывать, даже если поля не инициализированы.
Если при создании переменной в памяти вы не определяете на-
чальное значение, то есть три возможных способа, с помощью кото-
рых это начальное значение может быть задано. Если вы задали на-
чальное значение данного поля при определении структурного типа,
то этому полю присваивается указанное значение (по умолчанию).
Например, в следующем фрагменте программы начальное значение
задается только для одного поля MisterBark (поле NAME) при созда-
нии переменной MisterBark. Однако, определено начальное значение,
заданное для поля AGE при определении структуры CLIENT, поэтому
это будет значением поля AGE для MisterBark. Для поля INCOME зна-
чение нигде не задано, поэтому данное поле инициализируется зна-
чением 0. Пример:
.
.
TASM2 #3-5/Док = 49 =
.
CLIENT STRUC
NAME DB 'Здесь указывается имя...'
AGE DW ?
INCOME DD ?
CLIENT ENDS
.
.
.
.DATA
MisterBark CLIENT <'John Q. Bark>
.
.
.
В результате выполнения данной программы поле NAME инициали-
зируется значением 'John Q. Bark', поле AGE - значением 21, а по-
ле INCOME - значением 0. Заметим, что начальное значение поля
NAME, задаваемое при создании переменной MisterBark, отменяет на-
чальное значение, заданное при определении структуры CLIENT.
С помощью операции DUP вы можете инициализировать массив
структур. Например:
Clients CLIENT 52 DUP (<>)
Здесь создается массив Clients, состоящий из 52 структур ти-
па CLIENT, каждая из которых инициализируется значениями по умол-
чанию.
Если вы снова взглянете на пример исходной структуры, то
увидите новую операцию - точку (.). Эта операция представляет со-
бой другую форму операции + для адресации в памяти. То есть, сле-
дующие строки выполняют одинаковые действия:
.
.
.
mov ax,[bx.AGE]
mov ax,[bx].AGE
mov ax,[bx+AGE]
mov ax,[bx]+AGE
.
.
.
TASM2 #3-5/Док = 50 =
Операция точки часто используется при структурных ссылках на
содержимое в обозначениях языка Си, где также используется точка.
Это делается также, чтобы было понятно, что используется поле
структуры. Вы можете использовать ту операцию, которая вам больше
нравится (. или +).
Поля структуры, определенные с помощью директивы STRUC, в
действительности представляют собой метки, приравненные к смеще-
нию поля в структуре. Используя данное ранее определение для
CLIENT и MisterBark, можно сказать, что следующие две строки эк-
вивалентны:
.
.
.
mov [MisterBark.AGE],ax
mov [MisterBark+20],ax
.
.
.
или
.
.
.
AGE_FIELD EQU 20
.
.
.
mov [MisterBark+AGE_FIELD],ax
.
.
.
TASM2 #3-5/Док = 51 =
Недoстатки и преимущества использования директивы STRUC
-----------------------------------------------------------------
Для чего же нужно использовать директиву STRUC? Во-первых,
поля структуры обеспечивают типизацию данных. Турбо Ассемблер
знает, что MisterBark.AGE в первом примере - это переменная раз-
мером в слово, поскольку AGE - это элемент структуры, а выражение
MisterBark+AGE во втором примере не имеет присущего ему размера.
Во-вторых, намного проще изменить определение структуры, чем
изменять смещения констант, или даже набор присваиваний. Напри-
мер, если вы решили, что поле NAME должно иметь длину 30 симво-
лов, то вам пришлось бы изменить запись для поля NAME в определе-
нии CLIENT. Если бы вы использовали присваивания, вам пришлось бы
вручную вычислять и изменять смещения полей AGE и INCOME. В боль-
ших структурах это была бы огромная работа.
Наконец, директива STRUC облегчает создание и инициализацию
структур данных.
Короче говоря, директива STRUC дает удобный способ создания
структур данных и доступа к ним. С другой стороны, структуры дан-
ных на языке Ассемблера способствуют ошибкам в большей степени,
чем структуры данных на языке Си. Например, когда вы используете
для ссылки на структуру данных регистр, в Турбо Ассемблере нет
способа сообщить, содержит ли регистр указатель на допустимую для
этого типа структуру данных. В следующем фрагменте программы ре-
гистр BX загружается значением 0, но Турбо Ассемблер не может
знать, расположена по смещению 0 допустимая структура данных
DATA, или нет:
.
.
.
mov bx,0
mov dx,[bx.AGE]
.
.
.
Это не проблема языка Ассемблера, это просто отражает его
природу. Ведь есть выбор между тем, чтобы дать вам полную свободу
в программировании и тем, чтобы защитить вас от самого себя. Язык
Ассемблера предоставляет вам свободу. Нужно иметь в виду еще один
важный момент: в ссылках на структуры данных Турбо Ассемблер мо-
жет выполнять только ограниченную проверку на ошибки. Ответствен-
TASM2 #3-5/Док = 52 =
ность за правильную загрузку указателей возлагается на вас.
Уникальные имена полей структур
-----------------------------------------------------------------
Несколько досаден тот факт, что имена полей структур, кото-
рые в действительности просто являются метками, должны быть в
данном исходном модуле уникальны. Например, если вы в данном ис-
ходном модуле определили структуру CLIENT, нигде в другом месте
модуля вы не могли бы использовать метку с именем INCOME (даже в
другой структуре). INCOME - это просто метка со значением 22 и,
конечно, в одном исходном модуле не может содержаться две метки с
одним и тем же именем. В следующем фрагменте программы из-за по-
пытки переопределения AGE будет генерироваться ошибка:
.
.
.
CLIENT STRUC
NAME DB 'Здесь указывается имя...'
AGE DW ?
INCOME DD ?
CLIENT ENDS
.
.
.
AGE EQU 21
.
.
.
TASM2 #3-5/Док = 53 =
Вложенные структуры
-----------------------------------------------------------------
Структуры могут быть вложенными, например:
.
.
.
.DATA
.
.
.
AGE_STRUC STRUC
YEARS DW ?
MONTHS DW ?
AGE_STRUC ENDS
.
.
.
CLIENT STRUC
NAME DB 'Здесь указывается имя...'
AGE AGE_STRUC <>
INCOME DW ?
CLIENT ENDS
.
.
.
MisterBark CLIENT <>
.
.
.
.CODE
.
.
.
mov dx,[MisterBark.AGE.MONTHS]
mov si,OFFSET MisterBark
mov cx,[si.AGE.YEARS]
.
.
.
Здесь структура AGE_STRUC с именем AGE в структуре CLIENT
является вложенной, а затем в структуре CLIENT MisterBark можно
ссылаться на поля MONTHS и YEARS.
TASM2 #3-5/Док = 54 =
Инициализация структур
-----------------------------------------------------------------
Относительно инициализации структур нужно сделать несколько
предупреждений. Во-первых, если вы пытаетесь инициализировать
строковое поле структуры строкой, длина которой превышает длину
поля, то будет генерироваться ошибка ассемблирования.
Во-вторых, единственным видом поля, которое можно инициали-
зировать строковым значением - это строковое поле. Следующий да-
лее код ассемблироваться не будет:
.
.
.
TEST STRING
TEXT DG 30 DUP (' ')
TEST ENDS
.
.
TStruct TEST <'Test string'>
.
.
.
даже если строка TEXT инициалирована нулями, так как Турбо Ас-
семблер рассматривает TEXT, как массив из 30 пробелов, а не стро-
ку из 30 символов. Допустим, имеется следующий код:
.
.
.
TEST STRUC
TEXT DB 'Здесь следует строка.....'
TEST ENDS
.
.
.
TStruct TEST <'Test string'>
.
.
.
Этот код будет ассемблироваться.
TASM2 #3-5/Док = 55 =
В-третьих, хотя вы можете инициализировать более одного эле-
мента данных, как принадлежащих к одному полю структуры, инициа-
лизировать вы можете, самое большее, один элемент на поле (когда
вы создаете вхождение этой структуры). Например, в следующем
фрагменте программы, когда создается структура TestStruc, первый
байт поля B инициализируется значением 2, в то время как второй
байт каждого поля остается равным значению по умолчанию 20h (про-
бел):
.
.
.
T STRUC
A DB 0ffh,0ffh
B DB 0ffh,0ffh
T ENDS
.
.
.
TestStruc T <1,2>
.
.
.
В данном разделе мы обсудили версию директивы STRUC для ре-
жима MASM. В улучшенном режиме (режим IDEAL) директива STRUC зна-
чительно более мощная и позволяет использовать больше средств,
доступных в языках высокого уровня (об улучшенном режиме Турбо
Ассемблера рассказывается в Главе 11).
TASM2 #3-5/Док = 56 =
Директива RECORD
-----------------------------------------------------------------
Директива RECORD дает вам возможность определять поля бит в
байте или слове. Определения полей бит можно использовать для ге-
нерации масок с целью выделения одного или более полей бит, а
также в качестве счетчиков сдвигов для выравнивания вправо любого
битового поля. Директива RECORD не имеет отношения к оператору
Паскаля record.
Предположим, что вы определите структуру данных, которая со-
держит 1-битовые флаги и 12-битовое значение. Это можно сделать с
помощью директивы RECORD следующим образом:
TEST_REC RECORD FLAG1:1,FLAG2:1,FLAG3:1,TVAL:12
В данном примере определяется три флага FLAG1, FLAG2 и FLAG3
и поле данных с именем TVAL. Значение, указываемое для каждого
поля после двоеточия, задает размер этого поля в битах (каждый из
флагов имеет размер 1 бит, TVAL - 12 бит).
Каким образом поля хранятся внутри записи? Это довольно
сложно. Первое поле (FLAG1) - это самый левый (наиболее значащий)
бит записи. Второе поле (FLAG2) - это следующий по значимости бит
записи и т.д., пока вы не достигните значения TVAL, которое за-
канчивается в самом младшем бите записи. Однако эта запись имеет
размер только 15 бит и один бит в слове остается неучтенным (за-
писи всегда занимают точно 8 или 16 бит). Выполняется следующее
правило: записи в целом всегда выравниваются вправо на границу
байта или слова.
Как мы уже сказали, это немного сложно. Для прояснения ситу-
ации приведем пример. Запись типа TEST_REC определена с помощью
строки вида:
TRec TEST_REC <1,0,0,52h>
Здесь мы создали переменную TRec типа записи TEST_REC. Зна-
чения в угловых скобках представляют собой начальные значения со-
ответствующих полей, поэтому поле TRec FLAG1 инициализируется
значением 0, а поле TVAL инициализируется значением 52h. На Рис.
9.1 показано размещение и начальные значения четырех полей записи
переменной TRec.
FLAG1 FLAG3
не используется | FLAG2 | TVAL
TASM2 #3-5/Док = 57 =
| | | | |
---|-----|-----|-----|-------------|----------------------
| v | v | v | v | v |
TRec | 0 | 1 | 0 | 0 | 52h |
| | | | | |
----------------------------------------------------------
бит 15 14 13 12 11 0
Рис. 9.1. Расположение и начальное значение полей в записи
TREC.
Если общий размер записи (общая сумма всех полей) составляет
8 или менее бит, то запись хранится в байте, в противном случае
она хранится в слове. За исключением тех случаев, когда разрешено
ассемблирование с использованием процессора 80386 (в этом случае
допускаются записи до 32 бит), записи, размер которых превышает
16 бит, не поддерживаются.
Инициализация переменной типа запись во многом аналогична
инициализации структурной переменной. Если вы задаете начальное
значение для данного поля записи при создании переменной типа за-
пись, то поле инициализируется этим значением (как показано в
последнем примере).
Если при создании переменной-записи вы не зададите начальное
значение для данного поля записи, то возможны два используемых по
умолчанию значения. Когда вы создаете тип записи, вы можете за-
дать для любого поля используемое по умолчанию значение. Напри-
мер:
TEST_REC RECORD FLAG:1=1,FLAG:1=0,FLAG3,TVAL:12=0fffh
задает по умолчанию значение 1 для FLAG1, значение 0 для FLAG 2 и
0FFFh - для TVAL (для FLAG3 явное значение по умолчанию не зада-
ется). По умолчанию каждое поле, значение которого явно не зада-
ется, принимает значение 0, поэтому по умолчанию FLAG3 присваива-
ется значение 0.
Отсюда следует, что c учетом определения TEST_REC и создания
TRec:
.
.
.
.DATA
.
TASM2 #3-5/Док = 58 =
.
.
TEST_REC RECORD FLAG:1=1,FLAG:1=0,FLAG3,TVAL:12=0fffh
.
.
.
TRec TEST_REC <,1,,2>
.
.
.
поля инициализируются следующим образом:
- поле FLAG1 инициализируется значением 1;
- поле FLAG2 инициализируется значением 1;
- поле FLAG3 инициализируется значением 0;
- поле TVAL инициализируется значением 2.
Общее значение переменной-записи TRec - 6002h. Заметим, что
начальные значения, заданные при создании записи, переопределяют
начальные значения, задаваемые при определении записи.
После определения тип записи аналогичен другим типам данных.
Вы, например, можете использовать операнды типа записи в операции
SIZE, или можно определить массив записей с помощью операции DUP.
Например, далее определяется массив из 90 записей типа TEST_REC:
TRecArray TEST_REC 90 DUP (<1,1,1,0>)
Как и имена полей структур, имена полей записей представляют
собой метки. Поскольку определять метки в исходном модуле допус-
кается только один раз, это означает, что имена полей записей
должны быть в данном исходном модуле уникальны.
Доступ к записям
-----------------------------------------------------------------
Теперь, когда вы знаете, каким образом можно создать запись,
и как хранятся различные поля в записи, можно приступить к изуче-
нию доступа к записям. Вы можете с полным основанием подумать,
что обращаться к записям можно точно также, как и к полям струк-
туры, например:
mov al,[TRec.FLAG2] ; это работать не будет!
TASM2 #3-5/Док = 59 =
но это не так. Процессор 8086 может работать только с 8- или
16-битовыми операндами в памяти, поэтому невозможно загрузить
1-битовое поле, например, в регистр. Однако с полями записей мож-
но делать следующее: можно определить их размер в байтах, опреде-
лить, сколько бит нужно выровнять вправо или влево и для того,
чтобы выделить их, создать маску. Другими словами, хотя процессор
8086 не позволяет вам непосредственно работать с полями записей,
Турбо Ассемблер поддерживает работу с этими полями с помощью та-
ких инструкций, как AND и SHR.
Значение данного поля записи представляет собой число бит,
на которые нужно сдвинуть запись, чтобы выровнять вправо на это
поле (то есть поместить бит 0 поля в бит 0 записи). Например:
.
.
.
mov al,FLAG1
mov ah,TVAL
.
.
.
загружают в AL 14 и в AH 0, поэтому:
.
.
.
mov ax,[TRec]
mov cl,FLAG1
shr ax,cl
.
.
.
выравнивают вправо на поле FLAG1 TRec в AX.
Значение самого типа записи - это значение байта или слова,
которое могло бы генерироваться с помощью создания записи с дан-
ными начальными значениями. Например, инструкция:
mov ax,TEXT_REC <1,1,1,0fffh>
загружает в AX значение 7FFFh - значение, которое вы могли бы по-
лучить, если бы создали тип записи TEST_REC с начальными значе-
ниями <1,1,1,0FFFh>. Нужно учитывать различие между загрузкой AX
TASM2 #3-5/Док = 60 =
типом записи TEST_REC (как в последнем примере) и загрузкой ре-
гистра AX типом записи TREC, как в примере:
.
.
.
TEST_REC RECORD FLAG1:1=1,FLAG2:1=0,FLAG3:1,TVAL:12=0fffh
.
.
.
TRec TEST_REC <,1,,,2>
.
.
.
.CODE
.
.
.
mov ax,[TRec]
.
.
.
где регистр AX загружается значением 6002h (значение переменной
TRec).
TASM2 #3-5/Док = 61 =
Операция WITH
-----------------------------------------------------------------
Операция WITH возвращает размер записи или поля записи в би-
тах. Например, на следующей строке в регистре AL сохраняется зна-
чение 15 (число бит в записи TEST_REC):
mov al,WITH TEST_REC ; размер поля записи в битах
а далее значение 1 (длина каждого из полей флагов) сохраняется в
регистрах AL, AH и BL и значение TVAL - в регистре BH:
.
.
.
mov al,WITH FLAG1
mov ah,WITH FLAG2
mov bl,WITH FLAG3
mov bh,WITH TVAL
.
.
.
TASM2 #3-5/Док = 62 =
Операция MASK
-----------------------------------------------------------------
Операция MASK возвращает маску, подходящую для выделения за-
писи или поля записи в помощью инструкции AND. Например:
mov ax,MASK TEST_REC
записывает значение 7FFFh в регистр AX, а инструкции:
.
.
.
mov ax,MASK TEST_REC
mov dx,[TRec]
and dx,ax
.
.
.
записывают значение записи TRec в регистре DX, размаскируя бит
15, который не является частью записи TEST_REC.
Операция MASK более полезна, когда требуется выделить от-
дельное поле записи. Далее определяется, установлен ли флаг FLAG3
поля TRec:
.
.
.
mov ax,[TRec]
and ax,MASK FLAG3
jz Flag3NotSet ; флаг не установлен
.
.
.
Заметим, что вместо инструкции AND можно спокойно использо-
вать инструкцию TEST. Далее выполняется та же проверка, что и в
предыдущем примере, при этом не изменяются никакие регистры или
ячейки памяти:
.
.
.
jz Flag3NotSet
TASM2 #3-5/Док = 63 =
.
.
.
Операцию MASK полезно использовать для работы с полями запи-
си в сочетании с инструкциями сдвига (как вы увидите далее).
TASM2 #3-5/Док = 64 =
Для чего используются записи?
-----------------------------------------------------------------
Теперь вы узнали, что представляют собой записи и как они
используются. Когда может действительно возникнуть необходимость
в использовании записей? Конечно, записи используются нечасто, но
они очень удобны: ведь несколько полей данных вы можете записать
в один байт или в слово. Некоторые используемые базовой системой
ввода-вывода (BIOS) переменные также структурированы в виде запи-
сей. Например, младший байт флага оборудования BIOS, в котором
хранится относящаяся к аппаратному обеспечению информация (актив-
ный видеоадаптер, число имеющихся дисководов и т.д.) - это поле
структуры:
EQ__FLAG RECORD NUMDISKS:2,VIDEO:2,RSRVD:2,MATHCHIP:1,AREDISKS:1
где:
NUMDISKS - число установленных дисководов для гибких дисков,
- 1.
VIDEO - показывает, какой тип дисплейного адаптера в данный
момент активен.
RSRVD - поле, зарезервированное для использования в различ-
ных микрокомпьютерах фирмы IBM.
MATHCHIP = 1, если установлен арифметический сопроцессора
(типа 8087).
AREDISKS = 1, если установлены дисководы для гибких дисков.
Приведем пример функции, которая использует запись EQ_FLAG
и операции над записью для установки поля дисплейного адаптера во
флаге-переменной аппаратуры BIOS:
;
; Возвращает текущее значение поля дисплейного адаптера
; флага-переменной аппаратуры BIOS
;
; Ввод: нет
;
; Вывод:
; AL = 0, если дисплейный адаптер в данный момент не выбран
; 1, если выбран цветной дисплейный адаптер 40х25
; 2, если выбран цветной дисплей 80х25
TASM2 #3-5/Док = 65 =
; 3, если выбран монохромный дисплей 80х25
;
; Используемые регистры: AX, CL, ES
;
EQ__FLAG RECORD NUMDISKS:2,VIDEO:2,RSRVD:2,MATHCHIP:1,AREDISKS:1
;
GetBIOSEquipmentFlag PROC
mov ax,40h
mov es,ax ; ES указывает на сегмент данных
; BIOS
mov al,es:[10h] ; получить младший байт флага
; аппаратуры
and al,MASK VIDEO ; выделить поле дисплейного
; адаптера
mov cl,VIDEO ; получить число байт для
; сдвига поля вправо (выравни-
; вание вправо на это поле)
shr al,cl ; выровнять вправо на поле
; дисплейного адаптера
ret
GetBIOSEquipmentFlag ENDP
Приведем пример дополнительной функции, которая устанавлива-
ет поле флага аппаратуры BIOS в заданное значение:
;
; Устанавливает значение поля флага аппаратуры BIOS
;
; Ввод:
; AL = 0, если дисплейный адаптер в данный момент не выбран
; 1, если выбран цветной дисплейный адаптер 40х25
; 2, если выбран цветной дисплей с режимом 80х25
; 3, если выбран монохромный дисплей 80х25
;
; Вывод: нет
;
; Нарушаемые регистры: AX, CL, ES
;
EQ__FLAG RECORD NUMDISKS:2,VIDEO:2,RSRVD:2,MATHCHIP:1,AREDISKS:1
;
SetBIOSEquipmentFlag PROC
mov ax,40h
mov es,cx ; ES указывает на сегмент данных
; BIOS
TASM2 #3-5/Док = 66 =
mov cl,VIDEO ; получить число байт для
; сдвига передаваемого значения
; влево для выравнивания его на
; поле дисплейного адаптера
shl al,cl ; выровнять значение
mov ah,es:[10h] ; получить младший байт флага
; аппаратуры
and ah,NOT MASK VIDEO ; очистить поле дисплейного
; адаптера
and al,MASK VIDEO ; обеспечить правильное зада-
; ние нового значение поля
; дисплейного адаптера
or al,ah ; занести новый дисплейный
; адаптер
mov es:[10h],al ; установить новый флаг
; аппаратуры
ret
SetBIOSEquipmentFlag ENDP
В данном разделе мы обсудили версию директивы RECORD режима
MASM. В улучшенном режиме (IDEAL) версия данной директивы нес-
колько отличается от режима MASM (более подробно об улучшенном
режиме рассказывается в Главе 11).
TASM2 #3-5/Док = 67 =
Директива UNION
-----------------------------------------------------------------
Директива UNION обеспечивает способ, с помощью которого мож-
но ссылаться на данную ячейку памяти, как на данные нескольких
типов. Эта директива аналогична оператору union языка Си.
Предположим, у вас имеется счетчик, который иногда использу-
ется, как 8-битовый счетчик, а иногда - как 16-битовый. Его можно
описать, как следующее объединение:
.
.
.
FLEX_COUNT UNION
COUNT8 DB ?
COUNT16 DW ?
FLEX_COUNT ENDS
.
.
.
Заметим, что как и директива STRUC, директива UNION должна
завершаться соответствующей директивой ENDS.
С учетом предыдущего определения объединения FLEX_COUNT вы
можете создать и использовать такой двойной счетчик следующим об-
разом:
.
.
.
.DATA
Counter FLEX_COUNT ,?>
.
.
.
.CODE
.
.
.
mov [Counter.COUNT16],0ffffh
LoopTop:
.
.
.
TASM2 #3-5/Док = 68 =
dec [Count.COUNT16]
jnz LoopTop
.
.
.
mov [Counter.COUNT8],255
ShortLoopTop:
.
.
.
dec [Counter.COUNT8]
jnz LoopTop
.
.
.
Как и при использовании структуры, операция точки использу-
ется для ссылки на поля объединения (можно также использовать
операцию +). Ссылка на переменную с помощью полей объединения эк-
вивалентна использованию переопределения типа. Предыдущий пример
эквивалентен следующему:
.
.
.
.DATA
Counter DW ?
.
.
.
.CODE
.
.
.
mov WORD PTR [Counter],0ffffh
LoopTop:
.
.
.
dec WORD PTR [Counter]
jnz LoopTop
.
.
.
mov BYTE PTR [Counter],255
ShortLoopTop:
TASM2 #3-5/Док = 69 =
.
.
.
dec BYTE PTR [Counter]
jnz LoopTop
.
.
.
Преимущество использования объединений по сравнению с ис-
пользованием переопределений типа состоит в том, что вы с большей
вероятностью используете корректное имя элемента объединения, чем
будете каждый раз вспоминать о переопределении типа. Кроме того,
повторяющаяся операция с переменной-объединением становится оче-
видной, если вы посмотрите на определение переменной, поэтому
программу, в которой содержатся объединения, гораздо легче пони-
мать и обслуживать.
В объединениях можно использовать вложенные объединения и
структуры. Например, следующее объединение позволяет организовать
доступ к 4-байтовой переменной памяти либо как указателю (типа
сегмент:смещение) размером в двойное слово, либо как перемен-
ной-смещению размером в слово и переменной-сегменту размером в
слово:
.
.
.
SEG_OFF STRUC
POFF DW ?
PSEG DW ?
SEG_OFF ENDS
.
.
.
PUNION UNION
DPTR DD ?
XPTR SEG_OFF <>
PUNION ENDS
.
.
.
.CODE
.
.
.
TASM2 #3-5/Док = 70 =
mov [bx.XPTR.POFF],si
mov [bx.XPTR.PSEG],ds
.
.
.
les di,[bx.DPTR]
.
.
.
Как и при использовании директив STRUC и RECORD, имена по-
лей, определяемых с помощью директивы UNION, представляют собой
обычные метки. В улучшенном режиме директива UNION значительно
более мощная и предоставляет множество средств, которые доступны
в языках высокого уровня (об улучшенном режиме рассказывается в
Главе 11).
TASM2 #3-5/Док = 71 =
Директивы определения сегментов
-----------------------------------------------------------------
В Главе 5 мы уже рассказывали о том, как использовать упро-
щенные директивы определения сегментов. Вы уже также знаете дос-
таточно о стандартных директивах определения сегментов, чтобы
создать рабочую программу. Теперь мы обсудим каждую из стандарт-
ных директив определения сегментов подробно и дадим вам более
подробную информацию о том, что представляют собой упрощенные ди-
рективы определения сегментов. Мы рассмотрим также пример прог-
раммы, в которой используются несколько директив определения кода
и данных, чтобы вы получили представление о том, как работает
программа, состоящая из нескольких сегментов.
Вспомним, что упрощенные директивы определения сегментов
легче использовать, но они менее мощные, чем стандартные (полные)
директивы определения сегментов. В следующих разделах мы рассмот-
рим такие стандартные директивы определения сегментов, как
SEGMENT, GROUP и ASSUME.
TASM2 #3-5/Док = 72 =
Директива SEGMENT
-----------------------------------------------------------------
Директива SEGMENT используется для того, чтобы начать сег-
мент. Каждой директиве SEGMENT должна соответствовать директива
ENDS, завершающая сегмент. В отличие от упрощенных директив опре-
деления сегментов, директива SEGMENT позволяет вам полностью уп-
равлять атрибутами каждого сегмента.
Полная форма директивы SEGMENT имеет следующий вид:
имя SEGMENT выравнивание комбинирование использование 'класс'
где поля выравнивание", "комбинирование", "использование" и
"класс" необязательны. Каждое из этих полей мы обсудим поочеред-
но.
Поля "имя" и "выравнивание"
-----------------------------------------------------------------
Поле "имя" задает имя сегмента. Имена сегментов являются
метками, поэтому в своих исходных модулях они должны быть уни-
кальны. При завершении сегмента то же имя должно использоваться в
директиве ENDS.
Поле "выравнивание" задает границу памяти, с которой должен
начинаться сегмент. Допустимы следующие границы выравнивания:
BYTE - используется следующий доступный адрес байта.
DWORD - используется следующий адрес, выровненный на границу
двойного слова.
PAGE - используется адрес следующей страницы (выравнивается на
границу, кратную 256 байтам).
PARA - используется следующий адрес параграфа (выравнивается
адресу, кратному 16 байтам.
WORD - используется следующий адрес, выровненный на границу
слова.
Для большинства компактных программ подходит выравнивание на
границу байта. В большинстве 16-разрядных компьютеров (таких, как
AT) более предпочтительно выравнивание на границу слова, так как
16-разрядные процессоры более эффективно работают с данными, вы-
ровненными на границу слова. По тем же причинам в 32-разрядных
компьютерах предпочтительнее выравнивание на границу двойного
слова. Выравнивание на границу параграфа необходимо для сегмен-
TASM2 #3-5/Док = 73 =
тов, которые будут полностью занимать объем 64К.
Поле "комбинирование"
-----------------------------------------------------------------
Поле "комбинирование" управляет тем способом, с помощью ко-
торого сегменты с теми же именами в других модулях будут соче-
таться с данным модулем при их компоновке. Это поле может прини-
мать следующие значения:
AT PRIVATE
COMMON PUBLIC
MEMORY STACK
VIRTUAL
Вы можете также просмотреть последний раздел ("Упрощенные
директивы определения сегментов"), где показаны типы комбинирова-
ния, используемые в языках высокого уровня.
Тип комбинирования AT приводит к тому, что начало сегмента
будет размещаться по указанному адресу в памяти. Реальный код не
генерируется, вместо этого сегменты с типом комбинирования (соче-
тания) AT используются, как трафарет для доступа к областям памя-
ти, таким, как сегмент данных ПЗУ базовой системы ввода-вывода и
дисплейная память. Например:
.
.
.
VGA_GRAPHICS_MEMORY SEGMENT AT 0A000h
BitMapStart LABEL BYTE
VGA_GRAPHICS_MEMORY ENDS
.
.
.
mov ax,VGA_GRAPHICS_MEMORY
mov es,ax
ASSUME ES:VGA_GRAPHICS_MEMORY
mov cx,08000h
sub ax,ax
cld
rep stosw
.
.
.
TASM2 #3-5/Док = 74 =
очищает графический экран адаптера VGA.
Тип комбинирования COMMON задает, что начало данного сегмен-
та и начало других сегментов с тем же именем должно выравни-
ваться таким образом, что сегменты будут перекрывать друг друга.
Общий размер сегмента представляет собой размер наибольшего сег-
мента с данным именем. Один из способов использования типа ком-
бинирования COMMON состоит во включении файла, в котором опреде-
ляется сегмент COMMON, в каждом модуле, где имеется ссылка на
данный сегмент, благодаря чему все модули могут совместно исполь-
зовать один и тот же сегмент.
Тип комбинирования PUBLIC указывает компоновщику, что нужно
выполнить конкатенацию данного сегмента с другими сегментами с
тем же именем, благодаря чему сегменты составляют один большой
сегмент. Общий размер сегмента представляет собой сумму размеров
всех сегментов с этим именем. Общий размер сегментов PUBLIC не
должен превышать 64К (как и размер всех других сегментов). Тип
PUBLIC используется, когда несколько модулей совместно используют
один и тот же сегмент, но каждый модуль определяет свои собствен-
ные переменные. Переменные в сегментах PUBLIC с помощью директивы
GLOBAL часто используются модулями совместно.
Тип комбинирования MEMORY - это тоже самое, что тип PUBLIC.
Тип комбинирования STACK указывает компоновщику, что нужно
выполнить конкатенацию всех сегментов с данным именем в один сег-
мент, и построить файл .ЕХЕ, чтобы при выполнении программы ре-
гистры SS:SP указывали на конец этого сегмента. Это специальный
тип комбинирования, который должен использоваться только для сте-
ка и ни для чего более.
Тип комбинирования VIRTUAL определяет сегмент специального
вида, который будет интерпретироваться, как общая область, и при-
соединяться во время компоновки к другому сегменту. Предполагает-
ся, что сегмент VIRTUAL присоединяется к сегменту, в который он
вложен. От этого сегмента сегмент VIRTUAL наследует атрибуты. Ди-
ректива ASSUME рассматривает сегмент VIRTUAL, как часть его роди-
тельского сегмента, другими словами, сегменте VIRTUAL интерпрети-
руется, как обычный сегмент. Компоновщик рассматривает сегменты
VIRTUAL, как общую область, которая будет использоваться разными
модулями. Это позволяет совместно использовать в разных модулях
статические данные, содержащиеся во включаемых файлах.
Наконец, тип комбинирования PRIVATE указывает компоновщику,
TASM2 #3-5/Док = 75 =
что данный сегмент не следует сочетать ни с какими другими сег-
ментами. Это позволяет вам определять сегменты, которые являются
локальными по отношению к данному модулю (при этом не нужно бес-
покоиться о возможных конфликтах с сегментами с теми же именами,
использующимися в других модулях. Если тип комбинирования не ука-
зывается, то для сегментов по умолчанию задается тип комбинирова-
ния PRIVATE.
Назначение полей "использование" и "класс"
-----------------------------------------------------------------
Поле "использование" директивы SEGMENT предназначено только
для работы с процессором 80386. Более подробно об этом поле расс-
казывается в Главе 10.
Поле "класс" используется для управления порядком, в котором
компоновщик размещает сегменты. Все сегменты данного класса раз-
мещаются в непрерывном блоке памяти, независимо от их порядка в
исходном коде. В разделе "Упрощенные директивы определения сег-
ментов показаны классы, используемые в языках высокого уровня.
Для простоты вы можете соблюдать эти соглашения. (Более подробно
об упорядочивании сегментов рассказывается в следующем разделе.)
Размер, тип, имя и уровень вложенности сегмента
-----------------------------------------------------------------
Общий размер сегментов в классе ограничен только доступной
во время выполнения программы памятью. Однако размер отдельных
сегментов не может превышать 64К.
Заметим, что тип класса (если он присутствует) должен заклю-
чаться в кавычки. Тип класса должен быть уникален в данном исход-
ном модуле, то есть никакая используемая в данном модуле метка не
может тоже имя, что и имя класса, указанное в этом модуле. Одна-
ко, вы должны убедиться, что все определения данного сегмента в
исходном модуле имеют одни и те же атрибуты, в противном случае
Турбо Ассемблер будет генерировать ошибку.
Удобный способ избежать таких ошибок заключается в том, что-
бы задать атрибуты только один раз при определении сегмента в ис-
ходном модуле. Если при повторном определении сегмента атрибуты
обнаружены не будут, то Турбо Ассемблер автоматически использует
атрибуты, заданные при первом определении сегмента.
TASM2 #3-5/Док = 76 =
Наконец, сегменты могут быть вложенными, что означает, что
вы можете определить сегмент до того, как вы закончите предыдущий
сегмент, например:
.
.
.
DataSeg SEGMENT PARA PUBLIC 'DATA'
.
.
.
DataSeg2 SEGMENT PARA PRIVATE 'FAR_DATA'
.
.
.
DataSeg2 ENDS
.
.
.
DataSeg ENDS
.
.
.
В общем случае вложенность не является полезной, однако есть
по крайней мере один случай, когда она может оказаться полезной -
это макрокоманда. Чтобы определить сегмент в макрокоманде, вам
обычно приходится завершать, а затем возобновлять текущий сег-
мент, а чтобы это сделать, вам требуется знать текущее имя сег-
мента, что в контексте макрокоманды не всегда очевидно. Вложен-
ность сегментов позволяет вам определить сегмент, даже не зная
имени текущего сегмента, например:
.
.
.
TEST MACRO
.
.
.
TestSeg SEGMENT WORD PRIVATE 'FAR_DATA'
.
.
.
TestSeg ENDS
.
TASM2 #3-5/Док = 77 =
.
.
ENDM
.
.
.
После завершения вложенного сегмента Турбо Ассемблер просто
возобновляет ассемблирование в сегменте, который был активным до
начала вложенного сегмента.
TASM2 #3-5/Док = 78 =
Порядок сегментов
-----------------------------------------------------------------
В основном вам не приходится беспокоиться о том, каков поря-
док сегментов в получаемом файле .ЕХЕ. Во-первых, порядок, в ко-
тором сегменты следуют в выполняемом файле, часто не имеет значе-
ния. Во-вторых, в большинстве случаем, когда вам нужно соблюдать
порядок сегментов, легко можно обойтись компилятором языка высо-
кого уровня или директивой DOSSEG. Если вы выполняете компоновку
с языком высокого уровня, то порядком сегментов обычно управляет
компилятор этого языка. Если вы пишете программу только на языке
Ассемблера и указываете директиву DOSSEG, то при упорядочивании
сегментов будут соблюдаться соглашения фирмы Microsoft, то есть
порядок сегментов будет следующим:
- сегменты класса CODE;
- сегменты с классом, отличным от CODE, не являющиеся частью
DGROUP;
- сегменты, являющиеся частью DGROUP:
а) сегменты с классом, отличным от STACK и BSS;
б) сегменты класса BSS;
в) сегменты класса STACK.
Если вам хочется узнать о порядке, в каком компоновщик раз-
мещает ваши сегменты, можете просто использовать параметр команд-
ной строки /s, который укажет утилите TLINK, что нужно генериро-
вать подробный файл схемы сегментов, и просмотрите этот файл.
Остается вопрос, каким образом упорядочиваются сегменты, ес-
ли вы не выполняете компоновку с языком высокого уровня и не ис-
пользуете директиву DOSSEG? Чаще всего вам не потребуется знать
ответ на этот вопрос, но для того случая, когда это имеет для вас
значение, мы сделаем некоторое пояснение (это несколько сложнее,
чем может показаться).
При отсутствии явного задания порядка сегментов (например, с
помощью директивы DOSSEG) компоновщике группирует вместе все сег-
менты данного класса (где класс сегмента определяется полем
"класс" в директиве SEGMENT). Сами группы сегментов помещаются в
файл .EXE просто в том порядке, в каком компоновщик их обнаружи-
вает. Это означает, что порядок, в котором компонуются объектные
файлы, влияет на конечный порядок сегментов в файле .EXE.
Теперь мы получили сегменты, строго упорядоченные по клас-
сам. А как упорядочиваются сегменты внутри каждого класса? Они
TASM2 #3-5/Док = 79 =
также помещаются в файл .EXE в том порядке, в каком компоновщик
их обнаруживает. Одним из факторов здесь будет порядок, в котором
компонуются файлы .OBJ. Другой фактор - это порядок, в котором
сегменты размещены в каждом файле .OBJ.
Директива .SEQ указывает Турбо Ассемблеру, что сегменты в
файле .OBJ нужно разместить в том порядке, в каком они следуют в
исходном файле. При последовательном упорядочивании сегментов по-
рядок сегментов в исходном модуле может повлиять на порядок сег-
ментов в файле .EXE. Это режим работы, принятый в Турбо Ассембле-
ре по умолчанию, поэтому сегменты будут упорядочиваться
последовательно, даже если вы опустите директиву .SEQ (но если не
используется директива .ALPHA).
Директива .ALPHA указывает Турбо Ассемблеру, что сегменты в
объектном файле нужно размещать в алфавитном порядке. При упоря-
дочивании по алфавиту порядок сегментов в данном исходном модуле
не влияет на порядок сегментов в файле .EXE. В некоторых старых
ассемблерах этот режим работы используется по умолчанию, поэтому,
чтобы чтобы программы правильно работали, вам иногда может пона-
добиться использование данной директивы.
Итак, теперь вы имеете сегменты, свободно упорядоченные по
классам, и упорядоченные внутри класса в соответствии с порядком
их следования. Вы можете управлять упорядочиванием сегментов в
классе как в соответствии с тем порядком, в котором компонуются
объектные файлы, так и с помощью директив .SEQ и .ALPHA. Если
выбрана директива .SEQ, то на порядок следования сегментов в вы-
полняемом файле .EXE порядок следования сегментов в данном исход-
ном модуле.
Как вы можете видеть, упорядочивание сегментов - дело не
простое. Однако, как ни странно, вам не придется беспокоиться о
порядке сегментов: обычно он не имеет значения, а когда он важен,
то об упорядочивании сегментов за вас, как правило, заботится
компилятор языка высокого уровня или директива DOSSEG.
TASM2 #3-5/Док = 80 =
Директива GROUP
-----------------------------------------------------------------
Директива GROUP используется для комбинирования двух или бо-
лее сегментов в одну логическую запись, после чего ко всем этим
сегментам можно адресоваться относительно одного сегментного ре-
гистра.
Предположим, у вас есть программа, которая обращается к дан-
ным в двух сегментах. Обычно вам приходится загружать сегментный
регистр и указывать директиву ASSUME каждый раз, когда вы хотите
сначала обратиться к одному сегменту, а затем к другому. Это до-
вольно обременительно и требует лишнего времени. Намного проще
скомбинировать сегменты в одну группу с именем DataGroup, загру-
зить в регистр DS начало DataGroup, использовать директиву ASSUME
DS для группы DataGroup, а затем в любой момент обращаться к тому
или иному из этих сегментов. Приведем пример программы:
.
.
.
DataGroup GROUP DataSeg1,DataSeg2
.
.
.
DataSeg1 SEGMENT PARA PUBLIC 'DATA'
MemVar1 DW 0
DataSeg1 ENDS
.
.
.
mov ax,DataGroup
mov ds,ax
ASSUME DS:DataGroup
.
.
.
mov ax,[MemVar1]
mov [MemVar2],ax
.
.
.
Для чего может потребоваться использовать группы, хотя проще
получить тот же результат с помощью одного имени сегмента и типа
комбинирования PUBLIC? Действительно, в программах, которые пи-
TASM2 #3-5/Док = 81 =
шутся только на языке Ассемблера, нет большой необходимости в
группах, хотя если вы хотите, вы конечно можете их использовать.
Группы используются в основном, когда организуется интерфейс Ас-
семблера с кодом языков высокого уровня. В частности, группа
DGROUP используется в языках высокого уровня, чтобы можно было
обращаться к стеку, неинициализированным данным ближнего типа и
константам относительно одного сегментного регистра.
При использовании групп сегментов нужно соблюдать одно клю-
чевое правило: все сегменты в группе должны лежать в одном сег-
менте размеров 64К, так как все они должны быть доступны с по-
мощью одного сегментного регистра. Нужно помнить о том, что
порядок сегментов зависит от многих факторов (об этом уже расска-
зывалось в предыдущем разделе), поэтому, если вы не будете акку-
ратны, сегменты могут отстоять друг от друга на значительное
расстояние. Самый надежный подход здесь - это объявить все сег-
менты группы, как сегменты одного класса и определить их один за
другим в начале всех модулей, в которых они определяются.
Однако, если вы выполняете компоновку с языком высокого
уровня или где-либо в программе используете директиву DOSSEG, то
нет необходимости беспокоиться о том, чтобы собрать все сегменты
DGROUP вместе. В обоих случаях компоновщик автоматически настроит
все сегменты DGROUP.
Хотя сегменты в группе не должны превышать размер в 64К, при
компоновке они не обязательно должны быть непрерывными. Между
сегментами, входящими в группу, могут лежать другие сегменты.
Примечание: Если вы используете группу сегментов, то
нужно при загрузке сегмента (для ссылки на группу) всегда
аккуратно использовать в директиве ASSUME имя группы. В
противном случае Турбо Ассемблер будет генерировать смеще-
ния относительно начала сегмента, а не начала группы, даже
если сегментный регистр указывает на начало группы. Напри-
мер, в следующем фрагменте программы с учетом предыдущего
определения DGROUP возникнет ошибка:
.
.
.
mov ax,DGROUP
mov ds,ax
ASSUME DS:Stack ; будет получено некорректное
; смещение!
.
TASM2 #3-5/Док = 82 =
.
.
Вместо этого нужно использовать следующее:
.
.
.
mov ax,DGROUP
mov ds,ax
ASSUME DS:DGROUP
.
.
.
Короче говоря, если вы загружаете сегментный регистр,
чтобы он указывал на группу DGROUP, нужно убедиться, что вы
используете в директиве ASSUME эту группу, а не какой-либо
из составляющих ее сегментов.
В макроассемблере фирмы Microsoft MASM содержится ошибка,
относящаяся к использованию с группами операции OFFSET. Эта ошиб-
ка может также возникать при инициализации данных адресом или
метками в группе. В целях совместимости Турбо Ассемблер воспроиз-
водит эту ошибку. Чтобы обойти ее, нужно в метках (когда вы их
используете в операции OFFSET или для инициализации данных) ука-
зывать префиксы переопределения сегментов. (Подробнее об этом
рассказывается в Главе 6 в разделе "Не забывайте использовать в
операндах и таблицах данных переопределения группы").
TASM2 #3-5/Док = 83 =
Директива ASSUME
-----------------------------------------------------------------
Директива ASSUME (подразумевать, предполагать) позволяет вам
сообщить Турбо Ассемблеру, на какой сегмент или группу указывает
данный сегментный регистр. Заметим, что это не то же самое, что
действительная загрузка сегментного регистра для ссылки на данный
сегмент (вы должны это делать отдельно с помощью инструкции MOV).
Назначение директивы ASSUME состоит в том, чтобы дать Турбо Ас-
семблеру возможность проверить допустимость ваших ссылок на па-
мять и при обращении к памяти автоматически включить префиксы пе-
реопределения сегментов (если это требуется).
Директива ASSUME для регистра CS должна следовать перед лю-
бым кодом в каждом исходном модуле. Благодаря этому Турбо Ассемб-
лер знает, в каком сегменте подразумевается размещение инструкций
(это нужно для выполнения переходов, вызовов и установки началь-
ного адреса программы).
По мере необходимости в любой исходный модуль могут вклю-
чаться другие директивы ASSUME (с указанием различных сегментных
регистров). Подразумеваемый сегмент для любого сегментного ре-
гистра может быть изменен, когда вы этого пожелаете. В одной ди-
рективе ASSUME можно изменить любой или все предполагаемые сег-
ментные регистры.
Вы можете задать предполагаемый сегментный регистр по имени
сегмента, имени группы, или выделить сегмент из метки с помощью
операции SEG. Кроме того, для указанию Турбо Ассемблеру, что лю-
бой или все сегментные регистры не указывают ни на какой сегмент,
можно использовать ключевое слово NOTHING.
Приведем пример директивы ASSUME:
Stack SEGMENT PARA STACK 'STACK'
DB 512 DUP (0)
Stack ENDS
TGROUP GROUP DataSeg1,DataSeg2
DataSeg1 SEGMENT PARA PUBLIC 'DATA'
.
.
.
DataSeg1 ENDS
DataSeg2 SEGMENT PARA PUBLIC 'DATA'
.
.
TASM2 #3-5/Док = 84 =
.
DataSeg2 ENDS
.
.
.
DataSeg3 SEGMENT PARA PUBLIC 'CODE'
MemVar DW 0
.
.
.
CodeSeg SEGMENT PARA PUBLIC 'CODE'
ASSUME CS:CodeSeg,DS:TGROUP,SS:Stack,ES:NOTHING
ProgramStart:
mov ax,TGROUP
mov ds,ax
ASSUME DS:TGROUP
.
.
.
mov ax,SEG MemVar ; аналогично DataSeg3
mov es,ax
ASSUME ES:SEG MemVar
.
.
.
CodeSeg ENDS
END ProgramStart
Если в директиве ASSUME указывается группа, то предполагает-
ся, что заданный сегментный регистр указывает на начало данной
группы сегментов. Однако, если в директиве ASSUME указывается
сегмент, являющийся частью группы, то подразумевается, что сег-
ментный регистр указывает на начало этого регистра, а не группы.
Это может вызвать проблемы, поскольку обычно сегментные регистры
устанавливаются таким образом, чтобы указывать на начало группы,
а не входящих в нее сегментов. Например, далее регистр AX будет
загружен из неверной ячейки памяти, так как DS указывает на нача-
ло DGROUP, а директива ASSUME некорректно показывает, что регистр
DS указывает на начало DataSeg2:
.
.
.
TGROUP GROUP DataSeg1,DataSeg2
DataSeg1 SEGMENT PARA PUBLIC 'DATA'
TASM2 #3-5/Док = 85 =
.
.
.
DataSeg1 ENDS
DataSeg2 SEGMENT PARA PUBLIC 'DATA'
MemVar DW 0
DataSeg2 ENDS
.
.
.
CodeSeg SEGMENT PARA PUBLIC 'CODE'
ASSUME CS:CodeSeg
.
.
.
mov ax,TGROUP
mov ds,ax
ASSUME DS:DataSeg2 ; неправильно!
; (нужно TGROUP)
mov ax,[MemVar] ; будет загружено из
; неправильного смещения
; (относительно DataSeg2 а не
; относительно группы TGROUP)
.
.
.
Если вы используете упрощенные директивы определения сегмен-
тов, то в общем случае нет необходимости указывать директиву
ASSUME, так как Турбо Ассемблер генерирует соответствующие пред-
положения об используемых сегментах автоматически. Однако, если
при использовании упрощенных директив определения сегментов вы
изменили один из сегментных регистров, то нужно указать соответс-
твующую директиву ASSUME. Например, далее задается, чтобы регистр
DS указывал на сегмент .DATA, сегмент .CODE, сегмент .FARDATA и,
наконец, снова на сегмент .DATA:
.
.
.
.DATA
.
.
.
.FARDATA
.
TASM2 #3-5/Док = 86 =
.
.
.CODE
mov ax,@Data
mov ds,ax
ASSUME DS:@Data
.
.
.
mov ax,@Code
mov ds,ax
ASSUME DS:@Code
.
.
.
mov ax,@FarData
mov ds,ax
ASSUME DS:@FarData
.
.
.
mov ax,@Data
mov ds,ax
ASSUME DS:@Data
.
.
.
Как мы уже отмечали, благодаря директиве ASSUME Турбо Ас-
семблер может при ссылках на память в случае необходимости вклю-
чать префиксы переопределения сегментов. Например, в следующем
фрагменте программы в инструкции, где имеется ссылка на MemVar,
будет помещен префикс ES: (поскольку директиве ASSUME некорректно
указывает, что через регистр DS нельзя достичь сегмента, где на-
ходится переменная MemVar):
.
.
.
DataSeg SEGMENT PARA PUBLIC 'DATA'
MemVar DB ?
.
.
.
DataSeg ENDS
.
TASM2 #3-5/Док = 87 =
.
.
CodeSeg SEGMENT PARA PUBLIC 'CODE'
ASSUME CS:CodeSeg,DS:NOTHING,ES:DataSeg
.
.
.
mov ax,DataSeg
mov ds,ax
mov es,ax
mov [MemVar],1
.
.
.
Отсюда можно заключить, что директивы ASSUME нужно использо-
вать аккуратно, чтобы они все время соответствовали действитель-
ным значениям сегментных регистров.
Упрощенные директивы определения сегментов
-----------------------------------------------------------------
Некоторые детали упрощенных директив определения сегментов
мы уже обсудили в Главе 5. Однако основной аспект упрощенных ди-
ректив определения сегментов, который мы еще не обсуждали, это
какие именно сегменты создаются упрощенными директивами определе-
ния сегментов. Это не та информация, которая потребуется вам в
повседневной работе, но если вы используете смесь упрощенных и
стандартных директив определения сегментов, знать это необходимо.
Сегменты и группы сегментов, создаваемые по директивам
.CODE, .DATA, .DATA?, .STACK, .CONST, .FARDATA и .FARDATA? зави-
сят от модели памяти, выбираемой по директиве .MODEL. (Если вы
помните, о моделях памяти мы рассказывали в Главе 4.) Соответс-
твие между моделями памяти и сегментами, создаваемыми по упрощен-
ным директивам определения сегментов, показаны в следующих табли-
цах:
TASM2 #3-5/Док = 88 =
Таблица 9.1. Используемые по умолчанию
сегменты и типы для сверхмалой модели памяти
-----------------------------------------------------------------
Директива Имя Выравнивание Комбинирование Класс Группа
-----------------------------------------------------------------
.CODE _TEXT WORD PUBLIC 'CODE' DGROUP
.FARDATA FAR_DATA PARA private 'FAR_DATA'
.FARDATA? FAR_BSS PARA private 'FAR_BSS'
.DATA _DATA WORD PUBLIC 'DATA' DGROUP
.CONST CONST WORD PUBLIC 'CONST' DGROUP
.DATA? _BSS WORD PUBLIC 'BSS' DGROUP
.STACK STACK PARA STACK 'STACK' DGROUP
-----------------------------------------------------------------
Таблица 9.2. Используемые по умолчанию
сегменты и типы для малой модели памяти
-----------------------------------------------------------------
Директива Имя Выравнивание Комбинирование Класс Группа
-----------------------------------------------------------------
.CODE _TEXT WORD PUBLIC 'CODE'
.FARDATA FAR_DATA PARA private 'FAR_DATA'
.FARDATA? FAR_BSS PARA private 'FAR_BSS'
.DATA _DATA WORD PUBLIC 'DATA' DGROUP
.CONST CONST WORD PUBLIC 'CONST' DGROUP
.DATA? _BSS WORD PUBLIC 'BSS' DGROUP
.STACK STACK PARA STACK 'STACK' DGROUP
-----------------------------------------------------------------
Таблица 9.3. Используемые по умолчанию
сегменты и типы для средней модели памяти
-----------------------------------------------------------------
Директива Имя Выравнивание Комбинирование Класс Группа
-----------------------------------------------------------------
.CODE имя _TEXT WORD PUBLIC 'CODE'
.FARDATA FAR_DATA PARA private 'FAR_DATA'
.FARDATA? FAR_BSS PARA private 'FAR_BSS'
.DATA _DATA WORD PUBLIC 'DATA' DGROUP
.CONST CONST WORD PUBLIC 'CONST' DGROUP
.DATA? _BSS WORD PUBLIC 'BSS' DGROUP
.STACK STACK PARA STACK 'STACK' DGROUP
-----------------------------------------------------------------
TASM2 #3-5/Док = 89 =
Таблица 9.4. Используемые по умолчанию
сегменты и типы для компактной модели памяти
-----------------------------------------------------------------
Директива Имя Выравнивание Комбинирование Класс Группа
-----------------------------------------------------------------
.CODE _TEXT WORD PUBLIC 'CODE'
.FARDATA FAR_DATA PARA private 'FAR_DATA'
.FARDATA? FAR_BSS PARA private 'FAR_BSS'
.DATA _DATA WORD PUBLIC 'DATA' DGROUP
.CONST CONST WORD PUBLIC 'CONST' DGROUP
.DATA? _BSS WORD PUBLIC 'BSS' DGROUP
.STACK STACK PARA STACK 'STACK' DGROUP
-----------------------------------------------------------------
Таблица 9.5. Используемые по умолчанию
сегменты и типы для большой или сверхбольшой модели памяти
-----------------------------------------------------------------
Директива Имя Выравнивание Комбинирование Класс Группа
-----------------------------------------------------------------
.CODE _TEXT WORD PUBLIC 'CODE'
.FARDATA FAR_DATA PARA private 'FAR_DATA'
.FARDATA? FAR_BSS PARA private 'FAR_BSS'
.DATA _DATA WORD PUBLIC 'DATA' DGROUP
.CONST CONST WORD PUBLIC 'CONST' DGROUP
.DATA? _BSS WORD PUBLIC 'BSS' DGROUP
.STACK STACK PARA STACK 'STACK' DGROUP
-----------------------------------------------------------------
Читая последние главы, вы конечно заметили, что в програм-
мах, использующих упрощенные директивы определения сегментов, не
требуется указывать директивы ASSUME, GROUP или ENDS. Директива
.MODEL автоматически выполняет соответствующие директивы ASSUME
для выбранной модели памяти (при этом используются сегменты, по-
казанные в приведенных выше таблицах). Директива .MODEL выполняет
также определение группы для DGROUP (в соответствии с таблицами).
Что касается директивы ENDS, то начало нового сегмента с по-
мощью упрощенной директивы определения сегмента автоматически за-
вершает текущий сегмент (если он имеется).
Давайте теперь рассмотрим некоторые наиболее существенные
упрощенные директивы: директивы .DATA?, .CONST, .FARDATA и
.FARDATA? На самом деле директива .FARDATA - это единственная ди-
ректива, которую нужно использовать в программе, написанной толь-
ко на языке Ассемблера. Остальные директивы служат для соответс-
TASM2 #3-5/Док = 90 =
твия используемых сегментов языкам высокого уровня.
Директива .DATA? начинает сегмент, который содержит в DGROUP
неинициализированные данные ближнего типа. Так как сегменты .DATA
и .DATA? находятся в одной и той же группе, то директиву .DATA?
по идее совсем можно опустить, и использовать только директиву
.DATA и вопросительный знак для определения неинициализированных
данных в сегменте .DATA (кроме тех случаев, когда вы соблюдаете
соглашения языков высокого уровня).
В ту же категорию, что и директива .DATA?, попадает директи-
ва .CONST, которая начинает сегмент, содержащий данные-константы
ближнего типа. Вы также можете разместить константы в сегменте
.DATA и опустить директиву .CONST, если вам не нужно соблюдать
соглашения, принятые в языках высокого уровня.
Директива .FARDATA используется для создания сегмента данных
дальнего типа, уникального для данного исходного модуля (то есть
этот сегмент не используется совместно с другими модулями).
Этот сегмент называется FAR_DATA, но имеет тип комбинирова-
ния PRIVATE, поэтому он не комбинируется с другими сегментами.
Директива .FARDATA позволяет вам определить до 64К памяти локаль-
ных данных в каждом модуле. Конечно, если вы используете директи-
ву .FARDATA, вы должны установить сегментный регистр так, чтобы
он указывал на данный сегмент, например:
.MODEL SMALL
.DATA
InitValue DW 0
.FARDATA
MemArray DW 100 DUP (?)
.CODE
.
.
.
mov ax,@Data
mov ds,ax
mov ax,[InitValue]
mov ax,@FarData
mov di,OFFSET MemArray
mov cx,100
cld
rep stosw
.
.
TASM2 #3-5/Док = 91 =
.
Заметим, что предопределенная метка @FarData содержит имя
сегмента, определенного по директиве .FARDATA.
Хотя сегмент, определенный с помощью директивы .FARDATA, не
используется совместно с другими модулями, для совместного с дру-
гими модулями использования отдельных переменных в сегменте
.FARDATA вы можете использовать директиву GLOBAL. Например, ис-
пользуемая далее переменная MemVar будет доступна в других моду-
лях.
.MODEL SMALL
.FARDATA
GLOBAL MemVar:WORD
MemVar DW 0
.
.
.
Тогда на эту переменную можно ссылаться в другом модуле,
например:
.MODEL SMALL
GLOBAL MemVar:WORD
.DATA
.
.
.
.CODE
.
.
.
mov ax,SEG MemVar
mov ds,ax
ASSUME DS:SEG MemVar
mov ax,[MemVar]
.
.
.
Отметим, что объявление MemVar, как глобальной переменной
(GLOBAL), следует до того, как описывается какой-либо сегмент.
Это необходимо, так как глобальное описание данной переменной
должно выполняться либо в сегменте этой переменной, либо все всех
сегментов.
TASM2 #3-5/Док = 92 =
Директива .FARDATA? аналогична директиве .FARDATA, но она
создает сегмент типа PRIVATE с именем FAR_BSS. Сегменты FAR_BSS
используются в языках высокого уровня для неинициализированных
данных дальнего типа. Если вы не организуете интерфейс с языком
высокого уровня, то вам следует определить неинициализированные
данные дальнего типа в сегменте .FARDATA и забыть о .FARDATA?.
Сегмент .FARDATA дает вам дополнительные 64К памяти с дальним ти-
пом обращения, но если вам требуется более 64К памяти дальнего
типа, то вероятно потребуется использовать стандартные директивы
определения сегментов.
Если вы используете директиву .FARDATA?, предопределенная
метка @FarData? содержит имя сегмента, определенного по директиве
.FARDATA, которое можно использовать в директивах ASSUME и при
загрузке сегментных регистров.
TASM2 #3-5/Док = 93 =
Пример программы, состоящей из нескольких сегментов
-----------------------------------------------------------------
Следующая программа содержит два сегмента кода и два сегмен-
та данных. Это, вероятно, нельзя считать исчерпывающим примером
программирования с использованием нескольких сегментов, однако мы
не располагаем местом для программы, содержащей сотни или тысячи
строк. Эта программа даст вам представление о переключении между
сегментами, загрузки указателей типа "сегмент:смещение" и вызове
кода из других сегментов.
;
; Программа для демонстрации использования нескольких сегментов
; кода и данных
;
; Строка считывается с консоли, сохраняется в одном сегменте
; данных, копируется в строку в другом сегменте данных (при
; этом выполняется процесс ее преобразования в нижний регистр),
; а затем строка выводится на консоль. Для чтения, вывода и
; копирования строки используются функции в другом сегменте
; кода.
;
STACK SEGMENT PARA STACK 'STACK'
DB 512 DUP (?)
Stack ENDS
MAX_STRING_LENGTH EQU 1000
SourceDataSeg SEGMENT PARA PRIVATE 'DATA'
InputBuffer DB MAX_STRING_LENGTH DUP (?)
SourceDataSeg ENDS
DestDataSeg SEGMENT PARA PRIVATE 'DATA'
OutputBuffer DB MAX_STRING_LENGTH DUP (?)
DestDataSeg ENDS
SubCode SEGMENT PARA PRIVATE 'DATA'
ASSUME CS:SubCOde
;
; Подпрограмма для считывания строки с консоли. Конец строки
; отмечается символом возврата каретки, который преобразуется
; в пару символов возврат каретки/перевод строки (благодаря
; чему при выводе происходит перевод на следующую строку).
; Для завершения строки добавляется 0.
;
; Ввод:
TASM2 #3-5/Док = 94 =
; ES:DI - адрес расположения строки
;
; Вывод: отсутствует
;
; Используемые регистры: AX, DI
;
GetString PROC FAR
GetStringLoop:
mov ah,1
int 21h ; получить следующий символ
stosb ; сохранить его
cmp al,13 ; это возврат каретки?
jnz GetStringLoop ; нет, пока не выполнено
mov BYTE PTR es:[di],10
mov BYTE PTR es:[di+1],0 ; завершить строку
; символом перевода строки и 0
ret
GetString ENDP
;
; Подпрограмма для копирования строки и преобразования ее
; в нижний регистр.
;
; Ввод:
; DS:SI - строка для копирования
; ES:DI - место, куда нужно поместить строку
;
; Вывод: отсутствует
;
; Используемые регистры: AX, DI, SI
;
CopyLowerCase PROC FAR
CopyLoop:
lodsb
cmp al,'A'
jb NotUpper
cmp al,'Z'
ja NotUpper
add al,20h ; преобразовать в нижний
; регистр (если это верхний
; регистр)
NotUpper:
stosb
and al,al ; cтрока завершается 0?
jnz CopyLoop ; нет, скопировать другой
; символ
ret
TASM2 #3-5/Док = 95 =
CopyLowerCase ENDP
;
; Подпрограмма для вывода строки на консоль.
;
; Ввод:
; ES:DI - адрес копируемой строки
;
; Вывод: отсутствует
;
; Используемые регистры: AH, DL, SI
;
DispleyString PROC FAR
DisplayStringLoop:
mov dl,[si] ; получить следующий символ
and dl,dl ; это 0, завершающий строку?
jz DisplayStringDone ; да, выполнено
inc si ; ссылка на следующий символ
mov ah,2
int 21h ; вывести символ
jmp DisplayStringLoop
DisplayStringDone:
ret
DisplayString ENDP
SubCode ENDS
Code SEGMENT PARA PRIVATE 'CODE'
ASSUME CS:Code,DS:NOTHING,ES:NOTHING,SS:Stack
ProgramStart:
cld ; увеличение в строковых
; инструкциях регистров-
; указателей
;
; Считать строку с консоли в InputBuffer.
;
mov ax,SourceDataSeg
mov es,ax
ASSUME ES:SourceDataSeg
mov di,OFFSET InputBuffer
call GetString ; считать строку с консоли
; и сохранить ее по адресу
; ES:DI
;
; Вывести символ перевода строки, чтобы продвинуться на
; следующую строку.
;
mov ah,2
TASM2 #3-5/Док = 96 =
mov dl,10
int 21h
;
; Скопировать строку из InputBuffer (входной буфер) в
; OutputBuffer (выходной буфер), преобразуя ее в нижний
; регистр.
;
push es
pop ds
ASSUME DS:SourceDataSeg
mov ax,DestDataSeg
mov es,ax
ASSUME ES:DestDataSeg
mov si,OFFSET InputBuffer ; скопировать из DS:DI
mov di,OFFSET OutputBuffer ; в ES:DI,
call CopyLowerCase ; преобразуя в низкий
; регистр
;
; Вывести строку (строчные буквы).
;
push es
pop ds
ASSUME DS:DestDataSeg
mov si,OFFSET OutputBuffer
call DisplayString ; вывести строку, распо-
; ложенную по адресу
; DS:SI, на консоль
;
; Выполнено.
;
mov ah,4ch
int 21h
Code ENDS
END ProgramStart
Заметим, что в данном примере перед основной программой сле-
дует подпрограмма. Это делается для того, чтобы избежать опережа-
ющих ссылок, так как подпрограмма и основная программа находятся
в различных сегментах данных. Если основная программа следует
первой, то при каждом вызове подпрограммы вам придется указать
переопределение FAR PTR, так как Турбо Ассемблер не может выпол-
нять автоматическое ассемблирование опережающих ссылок дальнего
типа. Однако при использованной в примере организации программы
при всех вызовах подпрограмм используются обратные ссылки, поэто-
му Турбо Ассемблер может автоматически генерировать дальние вызо-
вы для таких подпрограмм.
TASM2 #3-5/Док = 97 =
Во всех других отношениях эта программа весьма проста. При
ссылке на данные в подпрограммах используются полные указатели
(сегмент:смещение), и, если это необходимо, в основной программе
значения регистров ES и DS устанавливаются в различные сегменты
данных. Обратите внимание на использование для копирования строки
и преобразования ее в нижний регистр строковых инструкций. Пос-
кольку в инструкции LODS по умолчанию используется регистр DS, а
в STOSW - ES, то эти инструкции идеально подходят для использова-
ния из в программе, где требуется обращаться к двум сегментам
сразу.
TASM2 #3-5/Док = 98 =
Глава 10. Процессор 80386 и другие процессоры
-----------------------------------------------------------------
До сих пор мы рассматривали программирование на языке Ас-
семблера для процессора 8086. (Косвенно мы при этом рассмотрели
также процессор 8088, который используется в компьютерах IBM PC и
XT, поскольку он в основном эквивалентен процессору 8086, но ос-
нащен 8-разрядной шиной данных.) Процессор 8086 - это не единс-
твенный процессор, поддерживаемый Турбо Ассемблером. Имеется це-
лое семейство развитых процессоров 8086, известных, как серия
iAPx86, и семейство математических сопроцессоров, являющихся раз-
витием сопроцессора 8087.
Наиболее развитым процессором серии iAPx86 является, без
сомнения, процессор 80386, благодаря которому персональные компь-
ютеры стали столь же мощными, как мини-ЭВМ. Тем не менее, каждый
процессор серии iAPx86 имеет интересные улучшения по сравнению с
возможностями процессора 8086. Далее мы рассмотрим программирова-
ние для процессора 80386: увидим, как можно разрешить в Турбо Ас-
семблере использование средство процессора 80386, познакомимся с
новыми регистрами, режимами адресации и инструкциями процессора
80386. После этого мы рассмотрим мощное свойство Турбо Ассемблера
- возможность чередования 16-разрядных и 32-разрядных инструкций
и сегментов, а также некоторые примеры программ для процессора
80386. Наконец, мы кратко проанализируем, каким образом возмож-
ности процессора 80386 можно расширить за счет использования соп-
роцессором 80287 и 80387.
Выбор в программе на Ассемблере типа процессора
-----------------------------------------------------------------
По умолчанию Турбо Ассемблер ассемблирует код только для
процессора 8086. Чтобы Турбо Ассемблер мог поддерживать другие
процессоры серии iAPx86, или сопроцессоры, вы должны указывать
соответствующие директивы. Следующие директивы сообщают Турбо Ас-
семблеру, какой тип процессора нужно поддерживать при ассемблиро-
вании кода:
.186 .286C .287 .386C .387 .8087
.286 .286P .386 .386P .8086
Эти директивы можно указывать в любом месте исходного фай-
ла, после чего они сразу вступают в действие. В одном исходном
файле можно разместить несколько директив выбора типа процессора,
при этом текущим процессором считается процессор, выбранный по
TASM2 #3-5/Док = 99 =
последней указанной директиве.
В любой момент можно указать директиву .8086, по которой
Турбо Ассемблер будет снова поддерживать только процессор 8086.
(В остальной части данной главы все ссылки на процессор 8086 от-
носятся в равной степени и к процессору 8088.)
В качестве примера приведем следующую функцию, которая
складывает два 32-разрядных значения, используя процессор 8086,
затем 80386, и наконец снова 8086:
DOSSEG
.MODEL SMALL
.CODE
Add32 PROC
mov ax,[bp+4] ; получить младшую половину
; источника 1
mov dx,[bp+6] ; получить старшую половину
; источника 1
mov bx,[bp+8] ; получить младшую половину
; источника 2
mov cx,[bp+10] ; получить старшую половину
; источника 2
.386 ; использовать для сложения
; регистры процессора 80386
shl eax,16
mov ax,dx
rol eax,16 ; занести 32 бита источника 1
; в регистр EAX
mov dx,cx
mov edx,16 ; поместить 32 бита источника 2
; в EDX
mov dx,bx
add eax,adx ; сложить источник 1 и источник 2
rol eax,16
mov dx,ax ; поместить старшую половину
; результата в регистр AX
.8086
ret
Add32 ENDP
END
Процессоры 80186 и 80188
-----------------------------------------------------------------
TASM2 #3-5/Док = 100 =
Процессор 80186 (который является процессором серии iAPx86)
в основном аналогичен процессору 8086. Процессор 80186 поддержи-
вает все инструкции процессора 8086, а также несколько новых инс-
трукций и расширенные формы некоторых инструкций процессора 8086.
Кроме того, при выполнении многих операций (особенно при вычисле-
нии адресов памяти) процессор 80186 работает существенно быстрее
процессора 8086, поэтому программы, написанные для работы на про-
цессоре 8086, на процессоре 80186 выполняются значительно быст-
рее.
Процессор 80188 обладает программной совместимостью с про-
цессором 80186. Единственное отличие между ними заключается в
том, что процессор 80186 имеет 16-разрядную шину данных, а про-
цессор 80188 - 8-разрядную.
TASM2 #3-5/Док = 101 =
Разрешение ассемблирования для процессора 80186
-----------------------------------------------------------------
Поддержка Турбо Ассемблера для ассемблирования кода процес-
сора 80186 разрешается по директиве .186.
Далее мы рассмотрим новые и расширенные инструкции процес-
сора 80186. Подробная информация по инструкциям процессора 80186
содержится в Главе 3 "Справочного руководства".
Перед началом рассмотрения отметим, что процессор 8086 не
распознает ни одну из тех инструкций, которые мы будем обсуждать.
В итоге все программы, которые содержат хотя бы одну инструкцию
(новую или расширенную) процессора 80186, на процессоре 8086 ра-
ботать не будут.
Новые инструкции
-----------------------------------------------------------------
Набор инструкций процессора 80186 содержит следующие новые
инструкции:
BOUND INS OUTS PUSHA
ENTER LEAVE POPA
Инструкции PUSHA и POPA
-----------------------------------------------------------------
Инструкции PUSHA и POPA предоставляют эффективное средство,
с помощью которого можно заносить и извлекать из стека все восемь
общих регистров. Инструкций PUSHA заносит в стек восемь общих ре-
гистров в следующем порядке: AX, CX, DX, BX, SP, BP, SI, DI. Инс-
трукция POPA извлекает регистры DI, SI, BP, BS, DX, CX и AX (то
есть выполняет действие, обратное действию инструкции PUSHA). Ре-
гистр SP инструкцией POPA не извлекается, вместо этого значение
SP увеличивается на 16 - длину блока регистров, занесенных в стек
по инструкции PUSHA, а значение SP, занесенное в стек по инструк-
ции PUSHA, очищается инструкцией POPA и отбрасывается. На сег-
ментные регистры, флаги и указатель инструкций PUSHA и POPA не
влияют.
Например, код:
.186
TASM2 #3-5/Док = 102 =
.
.
.
SampleFunction PROC
pusha
.
.
.
popa
ret
SampleFunction ENDP
.
.
.
сохраняет все 8 общих регистров с помощью двух инструкций, вместо
16 инструкций, которые потребовались бы для раздельного сохране-
ния и извлечения регистров. (Не забывайте для разрешения исполь-
зования специальных инструкций процессора 80186 указывать дирек-
тиву .186.)
Учтите, что инструкция PUSHA выполняется быстрее, чем во-
семь отдельных инструкций PUSH, но медленнее, чем три или четыре
инструкции PUSH. Если вы хотите сохранить только несколько ре-
гистров, то лучше сделать это с помощью инструкции PUSH. Такое же
замечание можно сделать относительно инструкций POPA и POP.
Перед использованием специфических для процессора 80186 инс-
трукций (таких, как PUSHA и POPA) не забудьте разрешить их ас-
семблирование с помощью директивы .186.
TASM2 #3-5/Док = 103 =
Инструкции ENTER и LEAVE
-----------------------------------------------------------------
Инструкции ENTER и LEAVE используются для того, чтобы уста-
новить и отменить границы стека, в которых передаваемые параметры
и локальные динамические переменные доступны относительно регист-
ра BP. Эти инструкции особенно полезны при организации интерфейса
Ассемблера с языками, ориентированными на работу со стеком (нап-
ример, с языком Си). (См. главу, где содержится информация об ор-
ганизации интерфейса функций Ассемблера с Турбо Си, приложение, в
котором говорится об интерфейсе с Турбо Прологом, и главу, где
описывается интерфейс с Турбо Паскалем, а также приложение, пос-
вященную интерфейсу с Турбо Бейсиком.)
Инструкция ENTER сохраняет регистр BP вызывающей программы,
устанавливает его таким образом, чтобы он указывал на начало пе-
редаваемых параметров (если они имеются) в новых границах стека,
устанавливает, как это необходимо, SP для выделения пространства
для локальных переменных, и даже копирует блок указателей на гра-
ницы стека языка высокого уровня в новые границы стека (если это
необходимо).
Инструкция LEAVE имеет действие, обратное инструкции ENTER,
и восстанавливает BP и SP в то состояние, которое они имели перед
выполнением соответствующей инструкции ENTER.
Например, следующая инструкция использует ENTER для уста-
новки совместимой с Си границы стека (при этом 20 байтов резерви-
руется для локальных переменных), а инструкция LEAVE использует-
ся, чтобы отменить границу стека и восстановить границу стека
вызывающего кода:
.
.
SampleFunction PROC
enter 10,1
.
.
.
leave
ret
SampleFunction ENDP
.
.
.
TASM2 #3-5/Док = 104 =
Первый операнд инструкции ENTER представляет собой 16-бито-
вое промежуточное значение, задающее число байтов, зарезервиро-
ванных для локальных переменных в новой границе стека. Второй
операнд инструкции ENTER - это 8-битовое промежуточное значение,
задающее уровень вложенности функции, для которой должна созда-
ваться новая граница стека. Этот операнд задает, сколько раз нуж-
но скопировать указатели границы стека из границ стека вызывающе-
го кода в новые границы стека.
Отметим, что чтобы возвратить управление в вызывающий код,
после инструкции LEAVE необходима инструкция RET. Инструкция
LEAVE отменяет текущую границу стека, но не выполняет возврата
управления.
Инструкции ENTER и LEAVE не сохраняют никаких регистров вы-
зывающего кода. Для этой цели следует использовать инструкции
PUSHA и POPA (или PUSH и POP).
TASM2 #3-5/Док = 105 =
Инструкция BOUND
-----------------------------------------------------------------
Инструкция BOUND проверяет, что 16-битовое значение находит-
ся в диапазоне со знаком, заданном двумя смежными словами памяти,
при этом верхняя граница записана по адресу, расположенному не-
посредственно над нижней границей. Обе границы интерпретируются,
как значения со знаком, поэтому можно задать максимальный диапа-
зон от -32768 до +32767 включительно. Значения, совпадающие с
нижней или верхней границей, рассматриваются, как принадлежащие
заданному диапазону.
Инструкция BOUND используется обычно для того, чтобы пре-
дотвратить выход за границы массива. Например, в следующем фраг-
менте программы проверяется, находится ли значение регистра BX в
диапазоне от 0 до 99 включительно перед использованием его в ка-
честве индекса в 100-байтовом массиве TestArray.
.
.
.
.DATA
TestArrayBounds LABEL DWORD
DW 0 ; нижняя граница массива
DW 99 ; верхняя граница массива
TestArray DB 100 DUP (?)
.
.
.
.CODE
.
.
.
mov ax,@Data
mov ds,ax
.
.
.
bound bx,[TestArrayBounds]
mov al,[TestArray+bx]
.
.
.
Если значение регистра BX не находится в заданном диапазоне,
то генерируется прерывание INT 5. Перед использованием инструкции
TASM2 #3-5/Док = 106 =
BOUND должен быть, конечно, установлен драйвер прерываний.
Первый операнд инструкции BOUND представляет собой 16-раз-
рядный регистр общего назначения, содержащий проверяемое значе-
ние. Второй операнд инструкции BOUND - это двойное слово, содер-
жащее диапазон. Это двойное слово содержит 16-битовую нижнюю
границу в младшем слове и 16-битовую верхнюю границу со знаком в
качестве старшего слова.
Относительно инструкции BOUND следует сделать одно замеча-
ние: указатель инструкций, заносимый в стек при генерации преры-
вания INT 5 при выходе проверяемого значения за границы диапазо-
на, указывает на саму инструкцию BOUND, а не на следующую
инструкцию. Если это не будет скорректировано обработчиком преры-
вания INT 5 перед выполнением инструкции IRET, то та же инструк-
ция BOUND снова сгенерирует прерывание INT 5, и так до бесконеч-
ности. Поэтому обработчик прерывания INT 5 для инструкций BOUND
должен вывести сообщение и завершить программу без выполнения ин-
струкции IRET, или скорректировать условие выхода за границы диа-
пазона перед выполнением инструкции IRET и продолжением работы.
TASM2 #3-5/Док = 107 =
Инструкции INS и OUTS
-----------------------------------------------------------------
Инструкции INS и OUTS обеспечивают эффективную передачу
данных между портами ввода-вывода и памятью.
Инструкция MOVS перемещает один или более байт (или слов) из
порта ввода-вывода, на который указывает регистр DX, в массив в
памяти, на который указывают ES:DI, увеличивая DI на 1 (или на 2)
после того, как каждый байт (или слово) будет передан (или умень-
шая SI, если установлен флаг направления). На регистр DX инструк-
ция INS не влияет. Как и в случае всех строковых инструкций, ко-
торые выполняют запись в память, использование ES в качестве
сегментного регистра целевого сегмента переопределить нельзя.
Инструкция OUTS перемещает один или более байт (или слов) из
массива в памяти, на который указывают DS:SI, в порт ввода-выво-
да, на который указывает регистр DX, увеличивая значение SI на 1
(или 2) после пересылки каждого байта (или слова), либо уменьшая
регистр SI, если установлен флаг направления. На регистр DX инс-
трукция OUTS не влияет. С помощью префикса переопределения сег-
мента можно выбрать сегментный регистр, отличный от регистра DS.
В следующем фрагменте программы инструкция INSB (байтовая форма)
используется для копирования 300h байт в память из порта вво-
да-вывода 3000h, а затем используется инструкция OUTSB для копи-
рования копирования этого байтового блока в порт ввода-вывода
3001h:
.
.
.
cld
mov ax,@Data
mov ds,ax
mov es,ax
mov dx,3000h
mov di,OFFSET Buffer
mov cx,300h
rep insb ; скопировать 300 байт из
; порта в буфер
mov dx,3001h
mov si,OFFSET Buffer
mov cx,300h
rep outsb ; скопировать 300h байт из
; буфера в порт
.
TASM2 #3-5/Док = 108 =
.
.
TASM2 #3-5/Док = 109 =
Расширенные версии инструкций процессора 8086
-----------------------------------------------------------------
В наборе инструкций процессора 80186 имеются следующие рас-
ширенные версии инструкций процессора 8086:
IMUL ROL SAR
PUSH ROR SHL
RCL SAL SHR
RCR
Занесение в стек промежуточных значений
-----------------------------------------------------------------
В то время как процессор 8086 может заносить в стек только
регистровые операнды или операнды в памяти, процессор 80186 может
заносить в него также и промежуточные значения:
push 19
Занесение в стек промежуточных значений полезно использовать
при передачи в стеке функциям параметров-констант. Например, код
процессора 8086 для функционального вызова Си:
Average(5, 2);
имеет следующий вид:
mov ax,2
push ax
mov ax,5
push ax
call _Average
add sp,4
а при наличии процессора 80186 его можно преобразовать в следую-
щий вид:
push 2
push 5
call _Average
add sp,4
Заметим, что хотя процессор 8086 не имеет инструкции PUSH с
непосредственным значением, синтаксис Турбо Ассемблера версии 2.0
TASM2 #3-5/Док = 110 =
позволяет вам задать в исходном файле такую инструкцию. Когда эта
инструкция будет обнаружена, она заменяется в объектном коде
10-байтовой последовательностью, которая моделирует данную опера-
цию, сохраняя все регистры и флаги.
Сдвиги и циклические сдвиги c непосредственными значениями
-----------------------------------------------------------------
В то время как процессор 8086 может может только выполнять
сдвиг или циклический сдвиг на 1 бит или на число бит, заданное в
регистре CL, процессор 80186 может выполнять сдвиг или цикличес-
кий сдвиг на значение-константу:
.
.
.
ror ax,3
shl dl,7
.
.
.
Это удобно использовать для выполнения сдвигов на несколько
битов без необходимости загрузки в регистр CL счетчика бит. Нап-
ример, следующий код процессора 8086 выполняет умножение содержи-
мого регистр AX на 256:
.
.
.
mov cl,8
shl ax,cl
.
.
.
С использованием инструкций процессора 80186 это принимает
вид:
shl ax,8
Умножение на непосредственное значение
-----------------------------------------------------------------
TASM2 #3-5/Док = 111 =
Процессор 8086 может умножать только 8- или 16-разрядный
регистр или операнд в памяти на AL или AX, размещая результат в
регистре AX или в паре регистров DX:AX. В процессоре 80186 пре-
дусмотрены две новые формы умножения, которые используются, когда
16-разрядное умножение будет размещаться в 16 битах.
Одна из новых форм умножения перемножает 16-разрядный ре-
гистр и 16-разрядное непосредственное значение, а результат сох-
раняет обратно в 16-разрядном регистре. Например, следующая инс-
трукция умножает содержимое DX на 4, а произведение записывает в
обратно в DX:
imul dx,4
Первый операнд, который может представлять собой любой
16-разрядный регистр общего назначения, является одновременно ис-
точником для одного из сомножителей и приемником для произведе-
ния. Второй операнд, который должен представлять собой непосредс-
твенное 16-битовое значение, - это другой сомножитель.
Еще одна новая форма умножения перемножает 16-разрядный ре-
гистр или операнд в памяти на 16-битовое непосредственное значе-
ние, и сохраняет результат в заданном 16-битовом регистре. Нап-
ример, следующая инструкция умножает содержимое регистра DX на
600h и помещает произведение в регистр CX:
imul cx,dx,600h
Аналогично, следующая инструкция умножает 16-разрядное зна-
чение в [BX+SI+1] на 3 и помещает произведение в регистр AX.
imul ax,[bx+si+1],3
В данной форме инструкции IMUL первый операнд представляет
собой приемник. Этот операнд может быть любым 16-разрядным общим
регистром. Второй операнд, который может задаваться любым 16-раз-
рядным общим регистром или ячейкой памяти, является источником
одного из сомножителей. Третий операнд, который должен задаваться
16-битовым непосредственным значением, - это другой сомножитель.
Если немного подумать, то станет ясно, что первая из новых
форм умножения - это в действительности подмножество второй новой
формы. Например, следующая инструкция:
imul si,10
TASM2 #3-5/Док = 112 =
это просто сокращенная форма инструкции:
imul si,si,10
Для обеих новых форм инструкции IMUL соответствующий шест-
надцатиричный код будет одинаковым. Тем не менее, удобно иметь
возможность использовать более простую форму инструкции IMUL с
двумя операндами, когда один и тот же регистр используется и как
источник, и как приемник.
При любой из новых форм умножения любая часть результата,
которая не помещается в 16 битах, теряется. Если теряются знача-
щие биты (предполагая, что результат должен представлять собой
значение со знаком), то устанавливаются флаги переноса и перепол-
нения. В новых формах операции умножения умножение значений со
знаком и беззнаковых значений не различаются, поскольку результат
имеет длину только 16 битов, и младшие 16 битов произведения (по-
лученного в результате перемножения как значений со знаком, так и
беззнаковых значений) всегда совпадают. Следовательно, для обоз-
начения новых форм умножения можно использовать только инструкцию
IMUL.
TASM2 #3-5/Док = 113 =
Процессор 80286
-----------------------------------------------------------------
Процессор 80286 был первым процессором серии iAPx86, который
позволил устранить ограничение по памяти в 1 мегабайт, и который
поддерживал также защиту памяти и виртуальную память. Процессор
80286 поддерживает все инструкции процессоров 8089 и 80186 и кро-
ме того позволяет использовать дополнительные инструкции, обеспе-
чивающие управление памятью с развитой архитектурой.
Процессор 80286 имеет два режима операций: реальный режим и
защищенный режим. Работа процессора 80286 в реальном режиме прак-
тически аналогична работе процессора 80286 (он обеспечивает тот
же набор инструкций, что и процессор 80186). Это тот режим, в ко-
тором в компьютере, использующем процессор 80286 (таком, как PC
AT фирмы IBM) работают большинство прикладных продуктов (напри-
мер, Турбо Паскаль или Quattro) и операционная система PC-DOS.
Средства управления памятью в процессоре 80286 доступны
только в защищенном режиме. И только в этом режиме можно одновре-
менно запустить на выполнение несколько не оказывающих влияние
друг на друга задач. При этом можно адресоваться к памяти, объем
которой превышает 1 мегабайт. В этом режиме на компьютерах с про-
цессором 80286 работает операционная система OS/2.
Для работы в защищенном режиме в процессоре 80286 использу-
ются следующие инструкции:
CLTS LIDT LMSW
LGDT LLDT LTR
Эти инструкции процессора 80286 предназначены только для ис-
пользования операционной системой. В прикладных программах нет
необходимости (и возможности) использовать инструкции защищенного
режима. Использование этих инструкций и защищенного режима про-
цессора 80286 в общем случае довольно сложно. Эту тему мы в дан-
ном руководстве освещать не будем.
В процессоре 80286 введены введены два дополнительных состо-
яния в регистре флагов: бит вложенной задачи и поле ввода-вывода
привилегированного уровня. Как и инструкции защищенного режима,
оба бита предназначены только для использования в системном прог-
раммном обеспечении, поэтому прикладные программисты не должны с
ними работать. Процессор 80286 имеет также несколько новых ре-
гистров, с которыми можно работать только с помощью инструкций
защищенного режима. Это регистр задачи, регистр слова состояния
TASM2 #3-5/Док = 114 =
машины и регистр таблицы глобальных дескрипторов. Прикладными за-
дачами эти регистры не используются, поэтому в данном руководстве
мы не будем их описывать.
TASM2 #3-5/Док = 115 =
Ассемблирования с использованием инструкций процессора 80286
-----------------------------------------------------------------
В Турбо Ассемблере разрешить ассемблирование с использовани-
ем инструкций процессора 80286 (незащищенный режим) можно с по-
мощью директивы .286. (Для совместимости с более ранними версиями
ассемблеров в Турбо Ассемблере поддерживается также директива
.286С.)
Заметим, что директива .286 неявно разрешает поддержку всех
инструкций процессора 8086 и 80186, так как процессор 80286 под-
держивает полный набор инструкций для более ранних процессоров
серии iAPx86.
Поддержка инструкций защищенного режима процессора 80286
разрешается по директиве .286Р. Инструкции незащищенного режима
процессора 80286 также разрешаются по директиве .286Р (как и при
выполнении директивы .286).
Более подробная информация об инструкциях процессора 80286
содержится в Главе 3 "Справочного руководства".
Важно заметить, что процессоры 8086 и 80186 не распознают
инструкции защищенного режима процессора 80286. В итоге все прог-
раммы, которые инструкции защищенного режима, на процессорах 8086
и 80186 работать не будут. Однако процессор 80386 поддерживает
как инструкции защищенного режима процессора 80286, так и инс-
трукции незащищенного режима.
TASM2 #3-5/Док = 116 =
Процессор 80386
-----------------------------------------------------------------
Процессор 80386 представляет собой значительную веху в эво-
люции микрокомпьютеров, обеспечивая новые и расширенные инструк-
ции, расширенный набор 32-разрядных регистров, линейные сегменты
размером до 4 гигабайт и возможность эмулировать одновременную
работу нескольких процессоров 8086, быстрые сдвиги и циклические
сдвиги, страничную память, большую скорость таймера, чем на пре-
дыдущих процессорах семейства iAPx86 (что приводит к большей ско-
рости работы) и т.д. Как вы можете заметить, для поддержки всех
возможностей процессора 80386 необходимо использовать все расши-
рения языка Ассемблера для процессоров 8086/80186/80286. Турбо
Ассемблер обеспечивает полный набор расширений процессора 80386,
поддерживая все его режимы и средства.
Процессор 80386 - это развитый процессор, на несколько по-
рядков более сложный, чем процессор 8086, поэтому мы не сможем
здесь охватить все аспекты программирования для процессора 80386.
Однако мы рассмотрим, какую поддержку процессора 80386 обеспечи-
вает Турбо Ассемблер.
Выбор режима ассемблирования для процессора 80386
-----------------------------------------------------------------
Как и процессор 80286, процессор 80386 имеет два типа инс-
трукций - привилегированные и непривилегированные. Непривилегиро-
ванные инструкции может выполнять любая программа. Однако приви-
легированные инструкции может выполнять только программа,
выполняющаяся на текущем уровне приоритета 0 (наиболее привилеги-
рованный уровень). Привилегированные инструкции процессора 80386
представляют собой расширение множества привилегированных инс-
трукций процессора 80286 (инструкции защищенного режима) и также
предназначены только для использования операционной системой.
Поддержка непривилегированных инструкций процессора 80386
разрешается по директиве .386. (Для совместимости с более ранними
ассемблерами Турбо Ассемблер поддерживает также директиву .386С,
которая также разрешает ассемблирование инструкций процессора
80386.)
Заметим, что директива .386 неявно разрешает также использо-
вание всех инструкций процессора 8086 и 80186, и, кроме того,
всех непривилегированных инструкций процессора 80286, поскольку
процессор 80386 поддерживает весь набор инструкций более ранних
TASM2 #3-5/Док = 117 =
процессором серии iAPx86. Использование непривилегированных инс-
трукций процессора 80386 разрешается также и по директиве .386Р
(как и при выполнении директивы .386). Так как процессор 80386
поддерживает все привилегированные инструкции процессора 80286,
директива .386Р неявно разрешает поддержку всех привилегированных
инструкций процессора 80286.
TASM2 #3-5/Док = 118 =
Новые типы сегментов
-----------------------------------------------------------------
Возможность процессора 80386 поддерживать сегменты размером
64К (как в процессоре 80286) или линейные сегменты до 4 гигабайт
потребовала двух новых типов сегментов - USE16 и USE32.
При ссылке на сегмент размером 64К 16-разрядное смещение мо-
жет храниться либо в базовом или индексном регистре (BX, SI, DI
или BP), либо использоваться в качестве непосредственного смеще-
ния. В этом режиме работает процессор 80286 (и 8086). В процессо-
ре 80386 сегментам, имеющим максимальный размер 64К, дается тип
использования USE16, например:
.386
.
.
.
DataSeg Segment USE16
Var1 DW ?
Ptr1 DW Var1
DataSeg ENDS
.
.
.
CodeSeg SEGMENT USE16
ASSUME CS:CodeSeg
mov ax,DataSeg
mov fs,ax
ASSUME FS:DataSeg
mov [Var1],0 ; установить Var1 в значение 0
mov bx,[Ptr1] ; загрузить 16-разрядный
; указатель на Var1
inc WORD PTR fs:[bx] ; увеличить значение Var1
.
.
.
CodeSeg ENDS
.
.
.
Отметим использование FS - одного из двух дополнительных
сегментов (наряду с GS) процессора 80386.
Отметим также, что смещение хранится в любом из общих
TASM2 #3-5/Док = 119 =
32-разрядных регистров процессора 80386 и может использоваться
для адресации к сегменту USE16, если если величина смещения не
превышает 0FFFFh (65535).
Для ссылки на любую ячейку в 4-гигабайтовом сегменте нужно
использовать 32-разрядное смещение записанное в любом из 32-раз-
рядных регистров или указываемое непосредственно. Сегментам про-
цессора 80386, которые имеют максимальную длину 4 гигабайта, да-
ется тип USE32, например:
.386
.
.
.
BigDataSeg SEGMENT USE32
Var1 DW ?
Ptr1 DD Var1
BigDataSeg ENDS
.
.
.
CodeSeg SEGMENT USE16
ASSUME CS:CodeSeg
mov ax,BigDataSeg
mov fs,ax
ASSUME FS:BigDataSeg
mov [Var1],0 ; установить Var1 в значение 0
mov eax,[Ptr1] ; загрузить 32-разрядный
; указатель на Var1
inc WORD PTR fs:[eax] ; увеличить значение Var1
.
.
.
CodeSeg ENDS
.
.
.
Отметим использование в качестве указателя регистра EAX.
Процессор 80386 позволяет использовать в качестве базового ре-
гистра или в индексного регистра все восемь 32-разрядных общих
регистров (EAX, EBX, ECX, EDX, ESI, EDI, EBP и ESP). (Более под-
робно см. далее в разделе "Новые режимы адресации").
Для переопределения используемого по умолчанию размера сме-
щения данного операнда можно использовать операции SMALL и LARGE.
TASM2 #3-5/Док = 120 =
Операция SMALL приводит к тому, что будет использоваться 16-раз-
рядное смещение, а LARGE вынуждает использовать 32-разрядное сме-
щение. Например:
.386
.
.
.
CodeSeg SEGMENT USE16
ASSUME CS:CodeSeg
mov ax,DateSeg
mov ds,ax
ASSUME DS:DataSeg
mov ax,[LARGE TestLoc]
.
.
.
CodeSeg ENDS
.
.
.
DataSeg SEGMENT USE32
TestLoc DW 0
DataSeg ENDS
.
.
.
Здесь успешно используется опережающая ссылка на TestLoc
(хотя TestLoc находится в сегменте USE32). При этом операция
LARGE применяется для того, чтобы ссылка на TestLoc осуществля-
лась с помощью 32-битового смещения. Без переопределения LARGE
здесь генерировалась бы ошибка, так как Ассемблер предполагает,
что для опережающей ссылки в сегменте CodeSeg типа USE16 исполь-
зуется 16-разрядное смещение.
На самом деле действие операций SMALL и LARGE более тонкое,
чем простой выбор между 16 и 32-разрядным размером смещения. Опе-
рация SMALL указывает Турбо Ассемблеру, что данную инструкцию
нужно ассемблировать с использованием 16-разрядных режимов адре-
сации процессора 8086, с помощью которых можно адресоваться толь-
ко к 64К памяти. С другой стороны, операция LARGE указывает Турбо
Ассемблеру, что данную инструкцию нужно ассемблировать с исполь-
зованием новых 32-разрядных режимов адресации (см. далее раздел
"Новые режимы адресации"), с помощью которых можно адресоваться к
TASM2 #3-5/Док = 121 =
4 гигабайтам памяти.
Например, код:
.
.
.
.386
CodeSeg SEGMENT USE16
.
.
.
mov ax,[SMALL ebx+esi+1]
.
.
.
CodeSeg ENDS
.
.
.
ассемблируется к виду:
mov ax,[bx+si+1]
Здесь операция SMALL указывает Турбо Ассемблеру, что нужно
использовать 16-разрядные режимы адресации процессора 8086, поэ-
тому вместо EBX и ESI в ассемблируемом коде используются регистры
BX и SI. Однако код:
.
.
.
.386
CodeSeg SEGMENT USE16
.
.
.
mov ax,[SMALL eax+ecx+1]
.
.
.
CodeSeg ENDS
.
.
.
TASM2 #3-5/Док = 122 =
ассемблироваться не будет, так как выражение EAX+ECX+1 не являет-
ся допустимым 16-разрядным режимом адресации. (С другой стороны,
как вы увидите в разделе "Новые режимы адресации", EAX+ECX+1
представляет собой допустимый 32-разрядный режим адресации.)
Более подробно об операциях SMALL и LARGE и об их взаимо-
действии с сегментами USE16 и USE32 рассказывается далее в разде-
ле "Смешанное использование 16- и 32-разрядных инструкций и сег-
ментов". В этом разделе также поясняется выбор сегментов USE16 и
USE32.
В связи с использованием сегментов USE16 и USE32 возникает
очень важный вопрос, касающийся размера косвенных переходов. Мы
коснемся этого вопроса далее в разделе "32-разрядный указатель
инструкций".
Если при определении сегмента не задано ни USE32, ни USE16,
то при ассемблировании для 80386 всегда подразумевается USE32.
TASM2 #3-5/Док = 123 =
Упрощенные сегментные директивы и типы сегментов для 80386
-----------------------------------------------------------------
Если вы используете и директиву .386, и упрощенные директивы
определения сегментов, то по умолчанию сегменты выравниваются на
границу двойного слова (DWORD). Это имеет смысл, если учесть, что
компьютеры, в которых используется процессор 80386 быстрее рабо-
тают с данными, выровненными на границу двойного слова.
Когда вы используете упрощенные директивы определения сег-
ментов, Турбо Ассемблер генерирует сегменты USE32, если директива
.386 указана перед директивой .MODEL, и сегменты USE16, если ди-
ректива .386 указана после директивы .MODEL. Например, в следую-
щем фрагменте программы создается 32-битовый код и сегменты дан-
ных:
.386
DOSSEG
.MODEL LARGE
.DATA
.
.
.
.CODE
.
.
.
а в следующем фрагменте создаются 16-битовый код и сегменты:
DOSSEG
.MODEL LARGE
.386
.DATA
.
.
.CODE
.
.
48-битовый тип данных FWORD
-----------------------------------------------------------------
Интересный момент в использовании сегментов USE32 состоит в
TASM2 #3-5/Док = 124 =
том, что размер указателя дальнего типа (то есть полного указате-
ля в форме "сегмент:смещение") на ячейку в сегменте USE32 занима-
ет 6 байт, а не обычные 4 байта, так как смещения в сегменте
USE32 имеют размер 32 бита. Например, при использовании в сегмен-
те USE16 дальний указатель на буфер размером 8000h байтов записы-
вается в 4 байта и загружается следующим образом:
.386
.
.
.
DataSeg SEGMENT USE16
Buffer DB 8000h DUP (?)
BufferPtr LABEL DWORD
DW OFFSET Buffer
DW SEG Buffer
DataSeg ENDS
.
.
.
CodeSeg SEGMENT USE16
ASSUME CS:CodeSeg
mov ax,DataSeg
mov ds,ax
ASSUME DS:DataSeg
les bx[BufferPtr] ; загружает в ES:BX 16-
; битовый сегмент и 16-
; битовое смещение Buffer
.
.
.
CodeSeg ENDS
.
.
.
При использовании же сегмента USE32 указатель дальнего типа
на Buffer записывается в 6 байт и загружается следующим образом:
.386
.
.
.
DataSeg SEGMENT USE32
Buffer DB 8000h DUP (?)
TASM2 #3-5/Док = 125 =
BufferPtr LABEL FWORD
DD OFFSET Buffer
DW SEG Buffer
DataSeg ENDS
.
.
.
CodeSeg SEGMENT USE32
ASSUME CS:CodeSeg
mov ax,DataSeg
mov ds,ax
ASSUME DS:DataSeg
les ebx[BufferPtr] ; загружает в ES:EBX 16-
; битовый сегмент и 32-
; битовое смещение Buffer
.
.
.
CodeSeg ENDS
.
.
.
Отметим использование нового типа данных FWORD. Значение ти-
па FWORD имеют длину 6 байт. Аналогично операциям BYTE PTR, WORD
PTR и DWORD PTR можно использовать операцию FWORD PTR.
lqs esi,FWORD PTR [BufferPtr]
Для определения 6-байтовых переменных имеется также новая
директива DF:
.386
.
.
.
DataSeg SEGMENT USE32
FPtr ?
DataSeg ENDS
.
.
.
CodeSeg SEGMENT USE32
ASSUME CS:CodeSeg
mov ax,DataSeg
mov ds,ax
TASM2 #3-5/Док = 126 =
ASSUME DS:DataSeg
mov eax,OFFSET DestinationFunction
mov DWORD PTR [FPtr+4],ax
jmp [FPtr]
.
.
.
CodeSeg ENDS
.
.
.
TASM2 #3-5/Док = 127 =
Новые регистры
-----------------------------------------------------------------
В процессоре 80386 общие регистры, регистр флагов и указа-
тель инструкций процессора 8086 увеличены по размеру до 32 бит,
кроме того добавлены два новых сегментных регистра. На Рис. 10.1
показан набор регистров процессора 80386, при этом расширения для
процессора 80386 по сравнению с процессором 8086 заштрихованы.
31 16 15 0
----------------------------------------- \
|###################| AH | AL | |
EAX |###################|-------------------| |
|###################| AX | |
----------------------------------------- |
----------------------------------------- |
|###################| BH | BL | |
EBX |###################|-------------------| |
|###################| BX | |
----------------------------------------- |
----------------------------------------- |
|###################| CH | CL | |
ECX |###################|-------------------| |
|###################| CX | |
----------------------------------------- |
----------------------------------------- |
|###################| DH | DL | |
EDX |###################|-------------------| | Общие
|###################| DX | | регистры
----------------------------------------- |
----------------------------------------- |
|###################| | |
ESI |###################| SI | |
|###################| | |
----------------------------------------- |
----------------------------------------- |
|###################| | |
EDI |###################| DI | |
|###################| | |
----------------------------------------- |
----------------------------------------- |
|###################| | |
EBP |###################| BP | |
|###################| | |
----------------------------------------- |
TASM2 #3-5/Док = 128 =
----------------------------------------- |
|###################| | |
ESP |###################| SP | |
|###################| | /
-----------------------------------------
-----------------------------------------
|###################| | Указатель
EIP |###################| IP | инструкций
|###################| |
-----------------------------------------
-----------------------------------------
|###################| | Регистр
EFLAGS |###################| FLAGS | флагов
|###################| |
-----------------------------------------
31 16 15 0
15 0
---------------------
| | \
CS | | |
| | |
--------------------- |
--------------------- |
| | |
DS | | |
| | |
--------------------- |
--------------------- |
| | |
DS | | |
| | |
--------------------- |
--------------------- | Cегментные
| | | регистры
ES | | |
| | |
--------------------- | --------------------- |
--------------------- |
|###################| |
FS |###################| |
|###################| |
--------------------- |
--------------------- |
|###################| |
GS |###################| |
TASM2 #3-5/Док = 129 =
|###################| |
--------------------- |
--------------------- |
| | |
SS | | |
| | |
--------------------- /
15 0
Рис. 10.1 Регистры процессора 80386.
Кроме того в процессоре 80386 имеется несколько специальных
регистров, несколько новых и несколько совместимых с процессором
80286, работать с которыми можно только с помощью привилегирован-
ных инструкций. Как и в процессоре 80286, эти регистры использу-
ются только системным программным обеспечением, поэтому в данном
руководстве мы о них не рассказываем.
TASM2 #3-5/Док = 130 =
32-разрядные общие регистры
-----------------------------------------------------------------
32-разрядные регистры общего назначения называются EAX, EBX,
ECX, EDX, ESI, EDI, EBP и ESP. Младшие 16 бит этих регистров об-
разуют множество 16-разрядных регистров процессора 8086, которые
нам уже хорошо знакомы. Например, младшие 16 бит регистра EAX
представляют собой регистр AX. Аналогично, младшие 8 бит регистра
EAX представляют собой регистр AL. В результате к различным час-
тям регистра EAX можно теперь обращаться с помощью четырех раз-
личных имен: 32-битового регистра EAX, 16-битового регистра AX и
8-битовых регистров AH и AL. Тоже самое относится к регистрам
EBX, ECX и EDX.
32-разрядные общие регистры процессора 80386 используются
точно также, как и 16- или 8-разрядные регистры. Например, в дан-
ном фрагменте программы в EAX записывается 1, регистр EBX уста-
навливается в значение 0, и значение регистра EAX cкладывается с
EBX:
.
.
.
mov eax,1
sub ebx,ebx
add ebx,eax
.
.
.
32-разрядные общие регистры можно использовать таким же об-
разом, как и обычные 16-разрядные регистры.
При обращении к 32-разрядным регистрам есть имеется только
один небольшой недостаток: невозможно непосредственно использо-
вать старшие 16 битов 32-разрядного регистра, как 16-разрядный
регистр. Если вы хотите использовать старшие 8 бит регистра AX,
то можно просто сослаться на регистр AH, а если вы хотите исполь-
зовать младшие 16 битов регистра ESI, как регистр, то можно прос-
то ссылаться на SI. Однако нет эквивалентного способа ссылаться,
скажем, на старшие 16 бит, например, регистра EAX. Это может ока-
заться неприятным при смешанной работе со значениями размером в
слово и двойное слово, однако такую неприятность можно обойти.
Чтобы получить доступ к старшим 16 битам 32-разрядного ре-
гистра, можно просто выполнить циклический сдвиг 16 битов в любом
TASM2 #3-5/Док = 131 =
направлении, обратиться с младшим 16 битам регистра и снова вы-
полнить для регистра циклический сдвиг на 16. Например, в следую-
щем фрагменте программы в регистр AX загружается 16-битовое зна-
чение, выполняется циклический сдвиг регистра EDX на 16 бит, что-
бы переставить (поменять местами) старшее и младшее слова EDX, AX
помещается в регистр DX, и снова выполняется перестановка старше-
го и младшего слова в EDX:
.
.
.
mov ax,[Sample16BitValue]
ror edx,16
mov dx,ax
ror edx,16
.
.
.
В результате мы получим следующий эффект: значение, первона-
чально загруженное в регистр AX, в результате помещается в стар-
шее слово EDX. Хотя это и затруднительная процедура, выполняется
она не так медленно, как может показаться. Благодаря групповому
сдвигу процессора 80386 для выполнения каждой инструкции ROR тре-
буется только 3 цикла.
TASM2 #3-5/Док = 132 =
32-разрядный регистр флагов
-----------------------------------------------------------------
Младшее слово регистра флагов процессора 80386 идентично ре-
гистру флагов процессора 8086. Старшие 16 бит регистра флагов
процессора 8086 содержит два новых флага. Один из этих новых фла-
гов показывает, работает ли в данный момент процессор 80386 как
виртуальный процессор 8086, а другой флаг предназначен для ис-
пользования при разработке средств отладки. Прикладным программ-
ным обеспечением эти флаги обычно не используются.
32-разрядный указатель инструкций
-----------------------------------------------------------------
Указатель инструкций процессора 80386 имеет размер 32 бита,
что отличается от 16-битового указателя инструкций процессора
8086. Этот расширенный указатель инструкций поддерживает сегменты
кода размером до 4 гигабайт.
Расширенный указатель инструкций процессора 80386 создает
некоторые сложности при задании косвенных переходов в памяти.
Например, следующий код ясно определяет косвенный переход дальне-
го типа с помощью 16-битового сегмента и 32-битового смещения:
jmp [FWORD PTR JumpVector]
Рассмотрим, однако, следующее:
jmp [DWORD PTR JumpVector]
Что это, 32-битовый косвенный переход ближнего типа, или
косвенный переход дальнего типа с 16-битовым сегментом и 16-бито-
вым смещением? С помощью операнда DWORD может быть задан любой из
этих двух типов переходов.
Здесь на помощь могут прийти операции SMALL и LARGE. Конс-
трукция:
jmp SMALL [DWORD PTR JumpVector]
ассемблируется, как косвенный переход дальнего типа по адресу,
заданному 16-битовым сегментом и 16-битовым смещением, записанном
в JumpVector, а
jmp LARGE [DWORD PTR JumpVector]
TASM2 #3-5/Док = 133 =
ассемблируется, как косвенный переход ближнего типа по адресу,
заданному текущим значением регистра CS и 32-битовым смещением,
записанным в JumpVector. В первом случае операция SMALL указывает
Турбо Ассемблеру, что переход нужно интерпретировать, как переход
из сегмента USE16. В сегментах USE16 32-битовые косвенные операн-
ды инструкции перехода состоят из 16-битового сегмента и 16-бито-
вого смещения. Во втором случае операция LARGE указывает Турбо
Ассемблеру, что переход нужно интерпретировать, как выполняющийся
в сегменте USE32. В сегментах типа USE32 32-битовый косвенный пе-
реход состоит только из 16-битовых смещений.
Отметим, что операции SMALL и LARGE указываются в данных
примерах вне квадратных скобок: позиция этих операций имеет зна-
чение. Когда операции SMALL и LARGE указываются вне квадратных
скобок, они влияют на размер операнда, в данном случае - на раз-
мер перехода. Когда эти операции указываются внутри скобок, они
влияют на размер адреса. Например, данный код указывает Турбо Ас-
семблеру, что нужно использовать для ссылки на JumpVector 32-би-
товое смещение, но не сообщает ему, нужно ли интерпретировать
значение, записанное в JumpVector, как 32-битовое смещение ближ-
него типа, или как комбинацию 16-битового сегмента и 16-битового
смещения ближнего типа:
jmp [LARGE DWORD PTR JumpVector]
Поэтому это не решает исходной проблемы определения типа пе-
рехода.
В отдельных выражениях операции LARGE и SMALL могут исполь-
зоваться как внутри, так и вне квадратных скобок. Например, сле-
дующий код задает косвенный переход дальнего типа по 16-битовому
сегменту и 16-битовому смещению, записанным в переменной
JumpVector размером в двойное слово, которая сама адресуется с
помощью ближнего 32-битового смещения:
jmp SMALL [LARGE DWORD PTR JumpVector]
TASM2 #3-5/Док = 134 =
Новые сегментные регистры
-----------------------------------------------------------------
В процессоре 8086 к четырем сегментным регистрам, поддержи-
ваемым в процессоре 8086 добавлены два новых сегментных регистра
- FS и GS. Эти два новых регистра не предназначены для какой-то
конкретной функции, и никакая инструкция или режим адресации по
умолчанию их не использует. В итоге использование регистров FS и
GS не является обязательным, но может оказаться удобным при обра-
щении к данным в нескольких сегментах сразу.
Регистры FS и GS используются также, как в некоторых инс-
трукциях используется регистр ES: с помощью префикса переопреде-
ления сегмента. Префикс переопределения может указываться явно:
.386
.
.
.
TestSeg SEGMENT USE16
SCRATCH_LEN EQU 1000h
Scratch DB SCRATCH_LEN DUP (?)
TestSeg ENDS
.
.
.
CodeSeg SEGMENT USE16
ASSUME CS:CodeSeg
mov ax,TestSeg
mov fs,ax
mov bx,OFFSET Scratcn
mov cx,Scratch_LEN
mov al,0
ClearScratch:
mov fs:[bx],al
inc bx
loop ClearScratch
.
.
.
CodeSeg ENDS
.
.
или неявно с помощью директивы ASSUME:
TASM2 #3-5/Док = 135 =
.386
.
.
.
TestSeg SEGMENT USE16
SCRATCH_LEN EQU 1000h
Scratch DB SCRATCH_LEN DUP (?)
TestSeg ENDS
.
.
.
CodeSeg SEGMENT USE16
ASSUME CS:CodeSeg
mov ax,TestSeg
mov gs,ax
ASSUME GS:TestSeg
sub bx,bx
mov cx,SCRATH_LEN
mov al,0
ClearScratch:
mov [Scratch+bx],al
inc bx
loop ClearScratch
.
.
.
CodeSeg ENDS
.
.
.
В последнем примере директива ASSUME GS:TestSeg указывает
Турбо Ассемблеру, что при каждом обращении по имени к переменным
в TestSeg нужно автоматически включать префикс переопределения
(в отличие от доступа с помощью регистра-указателя), поэтому явно
префикс переопределения вам указывать не нужно. Однако префикс
переопределения содержится в выполняемом коде, добавляя байт к
каждой инструкции, которая обращается к памяти с помощью регистра
FS или GS. Следовательно, там где это возможно, вместо сегмента
FS или GS предпочтительнее использовать сегментный регистр DS
(или ES в качестве приемника в строковой инструкции).
Новые режимы адресации
-----------------------------------------------------------------
TASM2 #3-5/Док = 136 =
Процессор 80386 поддерживает все режимы адресации процессо-
ров 8086, 80186 и 80286 и позволяет также использовать новые ре-
жимы адресации. В качестве базового регистра можно использовать
любой из восьми 32-разрядных общих регистров, а в качестве ин-
дексного регистра - любой из 8 32-разрядных общих регистров, кро-
ме регистра SP. (Вспомним, что процессор 8086 позволяет использо-
вать в качестве базовых только регистры BX и BP, а в качестве
индексных - только SI и DI.)
Предположим, например, что регистр EDI содержит значение
10000h, а EAX содержит 4. Тогда следующий код будет представлять
собой допустимую инструкцию для процессора 80386, увеличивающую
байт по смещению 10006h (10000h + 4 + 2) в сегменте, на который
указывает DS:
inc BYTE PTR [edi+eax+2]
Приведем еще один пример новых возможностей адресации в про-
цессоре 80386:
.
.
.
mov ecx,[esp+4]
mov ebx,[esp+8]
mov WORD PTR [exc+ebx],0
.
.
.
Однако с помощью новых режимов адресации процессор 80386
способен на большее. При вычислении адреса памяти индексный ре-
гистр можно умножать на 2, 4 или 8. Для этого после индексного
регистра просто помещается *2, *4 или *8 (это средство называется
индексным масштабированием). Например, девятую запись размером в
двойное слово в таблице DwordTable можно загрузить в регистр EAX
следующим образом:
.
.
.
mov ebx,8
mov eax,[DwordTable+ebx*4]
.
.
.
TASM2 #3-5/Док = 137 =
что эквивалентно следующему:
.
.
.
mov ebx,8
shl ebx,2
mov eax,[DwordTable+ebx]
shr ebx,2
.
.
.
Индексное масштабирование может быть чрезвычайно полезно при
доступе к элементам, как к массивам слов, двойных слов или чет-
верных слов. Например, рассмотрим следующий фрагмент программы,
которая сортирует элементы в массиве слов в возрастающем порядке:
.386
.
.
.
CodeSeg SEGMENT USE32
ASSUME CS:CodeSeg
.
.
.
;
; Сортировка массива слов в возрастающем порядке.
;
; Ввод:
; DS:EBX - указатель на начало сортируемого массива слов.
; EDX - длина массива (в словах).
;
; Используемые регистры:
; AX, ECX, EDX, ESI, EDI.
;
SortArray PROC
and edx,edx
jz EndSortWordArray
mov esi,0 ; сравнить элемент 0
; со всеми другими
SortOnNextWord:
dec edx ; уменьшить счетчик
; сравниваемых слов
TASM2 #3-5/Док = 138 =
jz EndSortWordArray
mov ecx,edx ; число элементов,
; с которыми нужно
; сравнить данный
; элемент
mov edi,esi ; сравнить данный
; элемент со всеми
; оставшимися элемен-
; тами
CompareToAllRemainingWords:
inc edi ; индекс следующего
; сравниваемого
; элемента
mov ax,[ebx+esi*2]
cmp ax,[ebx+edi*2] ; текущий элемент
; меньше элемента,
; с которым он
; сравнивается?
jbe NoSwap ; да, менять их
; местами не нужно
xchg ax,[ebx+edi*2] ; поменять местами
; текущий и сравниваемый
; элемент
NoSwap:
loop CompareToAllRemainingWords
inc esi ; ссылка на следующий
; элемент, который
; нужно сравнить со
; всеми остальными
; элементами
imp SortOnNextWord
EndSortWordArray:
ret
SortWordArray ENDP
.
.
.
CodeSeg ENDS
.
.
.
Процедура SortWordArray сохраняет номера элементов или ин-
дексы текущего и сравниваемого элементов в регистрах ESI и EDI.
Эти значения не являются указателями (степенями числа 2), хотя
массив и является массивом слов. Они представляют собой простые
TASM2 #3-5/Док = 139 =
скалярные индексы массива, аналогично тому, как n представляет
собой индекс массива в операторе языка Си:
i = Array[n];
Важный момент в процедуре SortWordArray состоит в том, что
средство индексного масштабирования процессора 80386 позволяет
вам умножать индексы на 2 в поле адресации к памяти, преобразуя,
таким образом, индексы в смещения в массиве слов.
Если для адресации к памяти используется единственный ре-
гистр, то этот регистр всегда считается базовым. Если для адреса-
ции к памяти используются два регистра, то самый левый регистр в
квадратных скобках считается базовым регистром, а самый правый
регистр - индексным. Если же, однако, с одним или двумя регистра-
ми в квадратных скобках указывается масштабирование, то масштаби-
руемый регистр всегда рассматривается, как индексный регистр.
Вопрос о том, какой из регистров является базовым, важен,
так как по умолчанию базовый регистр управляет сегментом, на ко-
торый делается ссылка при данном обращении к памяти. Доступ к па-
мяти, осуществляемый с помощью использования регистров EBP и ESP
в качестве базовых, приводит к ссылке на сегмент, на который ука-
зывает регистр SS, а доступ к памяти, при котором в качестве ба-
зовых используются регистры EAX, EBX, ECX, EDX, ESI или EDI при-
водит к ссылке на сегмент, на который указывает регистр DS.
Например, следующие инструкции приводят к ссылке на сегмент, оп-
ределяемый регистром DS:
mov al,[eax]
xchg edx,[ebx+ebp]
shr BYTE PTR [esi+esp+2],1
mov [ebp*2+edx],ah
sub cx,[esi+esi*2]
а следующие инструкции ссылаются на регистр, определяемый регист-
ром SS:
rol WORD PTR [ebp],1
dec DWORD PTR [esp+4]
add ax,[eax*2+esp]
mov [ebp*2],edi
Выбираемый по умолчанию по базовому регистру сегмент можно
переопределить с помощью явного префикса переопределения сегмента
или с помощью директивы ASSUME. Например:
TASM2 #3-5/Док = 140 =
.386
.
.
.
TestSeg SEGMENT USE32
Array1 DW 100h DUP (0)
TestSeg ENDS
.
.
.
CodeSeg SEGMENT USE16
ASSUME CS:CodeSeg
mov ax,TestSeg
mov fs,ax
ASSUME FS:TestSeg
mov dx,[ebx+Array1] ; неявное переопределение
; в результате ASSUME
mov esi,OFFSET Array1
mov cx,100h
IncLoop:
inc WORD PTR fs:[esi] ; явное переопределение
inc esi
inc esi
loop IncLoop
.
.
.
CodeSeg ENDS
.
.
.
Новые режимы адресации процессора 80386 работают только с 32
-разрядными регистрами адресации к памяти. 16-разрядные регистры
можно использовать только для ограниченного доступа к памяти,
также, как в процессоре 8086. Например, следующая инструкция MOV
будет ошибочной даже при наличии процессора 80386:
mov ax,[cx+dx+10h]
Индексное масштабирование для 16-разрядных регистров также
не допускается. Для адресации к памяти 16- и 32-разрядные регист-
ры не могут сочетаться. Поэтому, например, следующее выражение
использоваться не может:
TASM2 #3-5/Док = 141 =
add dx,[bx+eax]
TASM2 #3-5/Док = 142 =
Новые инструкции
-----------------------------------------------------------------
Давайте рассмотрим новые и расширенные инструкции процессора
80386 (подробнее инструкции процессора 80386 описываются в Главе
3 "Справочного руководства").
Примечание: Нужно иметь в виду, что процессоры 8086,
80186 и 80286 не распознают ни одну из тех новых и расши-
ренных инструкций, которые мы будет описывать. В результате
любая программа, в которой используются новые или расширен-
ные инструкции процессора 80386 на более ранних процессорах
работать не будет.
В процессоре 80386 введены следующие новые инструкции:
BSF BTR LFS MOVZX
BSR BTS LGS SETxx
BT CDQ LSS SHLD
BTC CWDE MOVSX SHRD
Проверка бит
-----------------------------------------------------------------
Инструкциями проверки бит процессора 80386 являются инструк-
ции BT, BTC, BTR и BTS. Инструкция BT - это основная инструкция
проверки бит, копирующая значение заданного типа во флаг перено-
са. Например, в следующем фрагменте программы переход на Bit3Is1
происходит только в том случае, если бит 3 регистра EAX отличен
от 0:
.
.
.
bt eax,3
jc Bit3Is1
.
.
.
Bit3Is1:
.
.
.
Если регистр EAX содержит значение 00000008h, то в этой
TASM2 #3-5/Док = 143 =
программе произойдет переход на метку Bit3Is1. Если же регистр
EAX содержит значение 0FFFFFF7h, то переход выполнен не будет.
Первый операнд инструкции BT представляет собой 16- или 32-раз-
рядный общий регистр или ячейку памяти, содержащую проверяемый
бит. Второй операнд - это номер проверяемого бита, заданный
8-битовым непосредственным значением или содержимым 16- или
32-разрядного общего регистра. Если в качестве второго операнда
используется регистр, то его размер должен совпадать с размером
первого операнда.
Заметим, что номер проверяемого бита может задаваться как
регистром, так и непосредственным значением, а поле, в котором
проверяется бит, может представлять собой как ячейку памяти, так
и регистр. Например, установить флаг переноса в состояние бита 5
слова по адресу Table+ebx+esi*2 можно следующим образом:
.
.
.
mov ax,5
bt WORD PTR [Table+ebx+esi*2],ax
.
.
.
Нужно помнить о том, что номера битов отсчитываются с 0
(младший бит) до старшего (наиболее значащего) бита. Если регистр
AL содержит значение 80h, то бит 7 в регистре AL установлен.
Инструкция BTC аналогична инструкции BT, только копируемое
во флаг переноса значение представляет собой дополнение заданного
бита. То есть флаг переноса устанавливается в значение 1, если
заданный бит равен 0, и в значение 0, если заданный бит равен 1.
Инструкция BTC устраняет необходимость использования инструкции
CMC, когда требуется задать состояние флага переноса, обратное
значению проверяемого бита.
Инструкция BTR также аналогична инструкции BT, но после ко-
пирования проверяемого бита во флаг переноса его значение стано-
вится равным 0. Аналогично, инструкция BTS устанавливает проверя-
емый бит, копируемый во флаг переноса, в значение 1. Эти
инструкции проверки полезно использовать для анализа и установки
состояния флага в одной операции (при этом подразумевается, что
не может произойти прерывание между проверкой флага и установкой
его в новое значение).
TASM2 #3-5/Док = 144 =
Просмотр битов
-----------------------------------------------------------------
Для нахождения первого или последнего ненулевого бита опе-
ранда размером в слово или двойное слово полезно использовать ин-
струкции BSF и BSR. Инструкция BSF просматривает исходный опе-
ранд, начиная с бита 0 (младший бит), определяя первый ненулевой
бит. Если все биты операнда-источника являются нулевыми, то флаг
нуля очищается. В противном случае флаг нуля устанавливается, а в
целевой регистр (приемник) копируется номер первого найденного
ненулевого бита.
В качестве примера приведем следующий фрагмент программы,
где инструкция BSF используется, чтобы определить расположение
первого (младшего) ненулевого бита в DX. Когда первый ненулевой
бит DX будет обнаружен в бите 2, значение 2 загружается в регистр
CX.
.
.
.
mov dx,0001101010101100b
bsf cx,dx
jnz AllBitsAreZero
shr dx,cl
.
.
.
AllBitsAreZero: ; все биты равны нулю
.
.
.
CL используется затем, как значение, на которое нужно сдви-
нуть DX, в результате чего значение CX сдвигается вправо ровно
настолько, сколько необходимо, чтобы переместить младший бит в
бит 0.
Второй операнд инструкции BFS - это 16- или 32-разрядный об-
щий регистр или просматриваемая ячейка памяти, а первый операнд -
это 16- или 32-разрядный общий регистр, в который будет записы-
ваться номер первого ненулевого бита в просматриваемых данных.
Оба операнда должны иметь одинаковый размер.
Инструкция BSR аналогична инструкции BFS, но просмотр она
начинает со старшего (наиболее значащего) бита операнда-источника
TASM2 #3-5/Док = 145 =
к младшему биту. В следующем примере индекс старшего ненулевого
бита в TestVar (значение 27) помещается в регистр EAX:
.
.
.
TestVar DD 0FFFFF00h
.
.
.
bsr eax,[TestVar]
.
.
.
TASM2 #3-5/Док = 146 =
Перемещение данных с расширением по знаку или нулю
-----------------------------------------------------------------
Инструкции MOVZX и MOVSX позволяют вам копировать 8- или 16-
разрядное значение в 16- или 32-разрядный общий регистр без необ-
ходимости использования лишних инструкций для расширения значения
до заданного размера. Инструкция MOVZX заполняет старшие биты
приемника нулями, а инструкция MOVSX распространяет знак значения
в соответствии с размером приемника. Обе инструкции аналогичны
стандартной инструкции MOV.
Например, располагая набором инструкций процессора 8086, для
копирования беззнакового значения в регистре DL в регистр BX нуж-
но было бы сделать следующее:
.
.
.
mov bl,dl
sub bh,bh
.
.
.
В процессоре 80386 для этого достаточно одной инструкции:
movzx bx,dl
Расширение по знаку с помощью инструкций процессора 8086 вы-
полнить еще сложнее. Для копирования переменной в памяти (со зна-
ком) размером в байт TestByte в DX без использования инструкции
MOVSX нужно было бы выполнить следующие инструкции:
.
.
.
mov al,[TestByte]
cwb
mov dx,ax
.
.
.
Это же можно сделать с помощью одной инструкции:
movsx dx,[TestByte]
TASM2 #3-5/Док = 147 =
Инструкции MOVZX и MOVSX могут перемещать 8-битовые значения
в 32-битовые регистры:
mov eax,al
TASM2 #3-5/Док = 148 =
Преобразование данных типа DWORD или QWORD
-----------------------------------------------------------------
Для преобразования значений со знаком размером в байт в ре-
гистре AL в значения со знаком размером в слово и значений со
знаком в регистре AL размером в слово в значения со знаком разме-
ром в двойное слово в процессоре 80386 предусмотрены соответс-
твенно инструкции CBW и CWD. В процессор 80386 добавлены еще две
инструкции преобразования, CWDE и CDQ, которые облегчают работу с
32-разрядными регистрами процессора 80386.
Инструкция CWDE преобразует значение со знаком размером в
слово, записанное в регистре AX, в значение со знаком размером в
двойное слово, так же как инструкция CWD. Различие между этими
двумя инструкциями состоит в том, что в то время как инструкция
CWD помещает 32-разрядный результат в DX:AX, инструкция CWDE по-
мещает помещает 32-разрядный результат в регистр EAX, который
можно затем использовать в 32-разрядных инструкциях процессора
80386.
Например, в результате выполнения инструкций:
.
.
.
mov ax,-1
cwde
.
.
.
в регистр EAX будет записано 32-битовое значение -1.
Инструкция CWD преобразует значение со знаком размером в
двойное слово в регистре EAX в значение со знаком (8-байтовое) в
EDX:EAX. Инструкции:
.
.
.
mov eax,-7
cdq
.
.
.
TASM2 #3-5/Док = 149 =
сохраняют значение -7 в 64-битовой паре регистров EDX:EAX. При
этом старшее слово результата (0FFFFFFFh) записывается в EDX, а
младшее слово результата, значение 0FFFFFFF9h (-7) - в регистре
EAX.
Сдвиг нескольких слов
-----------------------------------------------------------------
Сдвиг нескольких слов, например сдвиг 32-битового значения
на 4 бита влево, доставляет в процессоре 8086 много хлопот, пос-
кольку каждое слово должно сдвигаться по одному биту, при этом
биты перемещаются поочередно через флаг переноса из одного ре-
гистра в другой. Инструкции процессора 80386 SHRD и SHLD исправ-
ляют эту ситуацию, обеспечивая сдвиг на несколько битов двух ре-
гистров или регистра и ячейки памяти.
Предположим, например, что 32-битовое значение записано на
процессоре 8086 в регистрах DX:AX. Тогда для сдвига 32-битового
значения влево (по направлению к старшему биту) на 4 бита потре-
буется следующее:
.
.
.
shl ax,1
rcl dx,1
shl ax,1
rcl dx,1
shl ax,1
rcl dx,1
shl ax,1
rcl dx,1
.
.
.
В процессоре 80386 тот же результат можно получить, исполь-
зуя всего две инструкции:
.
.
.
shld dx,ax,4
shl ax,4
.
TASM2 #3-5/Док = 150 =
.
.
(Конечно, все 32-битовое значение можно было бы просто запи-
сать в регистр EAX и выполнить сдвиг с помощью инструкции:
shl eax,4
но данный пример предназначен для того, чтобы проиллюстрировать
преимущества использования инструкции SHLD по сравнению с инс-
трукциями процессора 8086.)
Первый операнд инструкции SHLD - это 16- или 32-разрядный
общий регистр или ячейка памяти, для которых нужно выполнить
сдвиг. Второй операнд представляет собой 16- или 32-разрядный
регистр, в который нужно выполнить сдвиг, а третий операнд - это
число бит, на которые нужно осуществить сдвиг. Размеры первого и
второго операнда должны совпадать. Третий операнд должен
представлять собой непосредственное значение или регистр CL. В
последнем случае целевой операнд сдвигается на число бит,
определяемый регистром CL.
Инструкция SHRD аналогична инструкции SHLD, однако она вы-
полняет сдвиг из наиболее значащего (старшего) бита в направлении
младших битов. В следующем примере 64-битовое значение, записан-
ное в TestQWord сдвигается вправо на 7 бит:
.
.
.
mov cl,7
mov eax,DWORD PTR [TestQWord+4]
shrd DWORD PTR [TestQWord],eax,cl
shr eax,cl
mov DWORD PTR [TestQWord+4],eax
.
.
.
TASM2 #3-5/Док = 151 =
Условная установка бит
-----------------------------------------------------------------
Общее применение для условных проверок и переходов состоит в
установке значения ячейки памяти, чтобы отразить определенное
состояние. Например, может оказаться желательным установить зна-
чение двух переменных, что две переменные равны, указатель равен
нулю, или в предыдущей операции был установлен флаг переноса.
Процессор 8086 далеко не идеален для выполнения таких операций,
так как для для установки флагов, чтобы отразить результаты про-
верки условия, требуется несколько инструкций. В процессоре 80386
для ускорения таких операций проверки и установки предусмотрена
мощная группа инструкций SET.
Предположим, например, что вы хотите установить переменную в
памяти TestFlag только в том случае, если установлен старший бит
регистра AX. На процессоре 8086 вам пришлось бы сделать сле-
дующее:
.
.
.
mov [TestFlag],0 ; предположим, что
; старший бит не
; установлен
test ax,80h
jz MSBNotSet
mov [TestFlag],1
MSBNotSet:
.
.
.
В процессоре 80386 можно использовать следующие инструкции:
.
.
.
test ah,80h
setnz [TestFlag]
.
.
.
При этом TestFlag будет установлена в значение 1, если бит 7
регистра AH равен 1, и в значение 0, если бит 7 регистра AH равен
TASM2 #3-5/Док = 152 =
0.
Проверку в любом из знакомых вам условных переходах можно
выполнить с помощью инструкции SET. Инструкция SETNC устанавлива-
ет целевой операнд в значение 1, если флаг переноса равен 0, и
сбрасывает значение целевого операнда в 0, если флаг переноса ра-
вен 1. Инструкция SETS устанавливает приемник, если флаг знака
равен 1, и сбрасывает его, если флаг знака равен 0 и т. д. Опе-
ранд инструкции SET может быть 8-битовым общим регистром или
8-битовой переменной в памяти. 16- и 32-разрядные операнды не до-
пускаются.
TASM2 #3-5/Док = 153 =
Загрузка регистров SS, FS и GS
-----------------------------------------------------------------
Инструкция процессора 8086 LDS позволяет вам загружать как
регистр DS, так и один из общих регистров из памяти в одной инс-
трукции, позволяя, таким образом, очень эффективно устанавливать
дальние указатели. Инструкция LES обеспечивает аналогичную воз-
можность, но вместо DS загружает регистр ES. В процессоре 80386
для загрузки дальних указателей добавлены инструкции LSS, LFS и
LGS, которые загружают дальние указатели на основе сегментных ре-
гистров SS, FS и GS соответственно.
Например, в следующей программе в GS:BX загружается указа-
тель дальнего типа на битовый массив видеопамяти по адресу
A000:0000:
.
.
.
DataSeg SEGMENT USE16
ScreenPointer LABEL DWORD
dw 0
dw 0A000h
DataSeg ENDS
.
.
.
CodeSeg ENDS
.
.
.
CodeSeg SEGMENT USE16
ASSUME CS:CodeSeg,DS:DataSeg
mov ax,DataSeg
mov ds,ax
.
.
.
lqs bx,[ScreenPointer]
.
.
.
CodeSeg ENDS
.
.
.
TASM2 #3-5/Док = 154 =
Как и с помощью инструкций LDS и LES с помощью инструкций
LSS, LFS и LGS можно загружать указатели типа SMALL или LARGE
(см. раздел "48-битовый тип данных FWORD").
TASM2 #3-5/Док = 155 =
Расширенные инструкции
-----------------------------------------------------------------
В процессор 80386 не только добавлены новые мощные инструк-
ции по сравнению с набором инструкций процессором
8086/80186/80286, но также расширены имеющиеся инструкции. Это
следующие инструкции:
CMPS JC JNAE JNLE JPO OUTS
IMUL JCXZ JNB JNO JS POPA
INS JE JNBE JNP JZ POPF
IRET JG JNC JNS LODS PUSHA
JA JGE JNE JNZ LOOP PUSHF
JAE JL JNG JO MOV SCAS
JB JLE JNGE JP MOVS STOS
JBE JNA JNL JPE
Кроме того, в процессоре 80386 многие инструкции могут рабо-
тать с 32-разрядными операндами, хотя их мнемоника явно не изме-
нилась.
Специальные версии инструкции MOV
-----------------------------------------------------------------
Процессор 80386 поддерживает специальные формы инструкции
MOV, которые позволяют программе, работающей на уровне приоритета
0 (уровень с максимальными полномочиями) перемещать данные между
32-разрядными общими регистрами и специальными регистрами процес-
сора 80386. Таким способом можно обращаться к следующим регистрам
процессора 80386:
CR0 DR0 DR3 TR6
CR2 DR1 DR6 TR7
CR3 DR2 DR7
Например, отладочный регистр DR0 можно загрузить адресом,
который будет перехватываться, с помощью инструкции:
.
.
.
.386P
.
.
.
TASM2 #3-5/Док = 156 =
mov eax,OFFSET FunctionEntry
mov dr0,eax
.
.
.
а системные управляющие флаги можно загрузить из управляющего ре-
гистра CR0 в регистр EDX с помощью инструкции:
.
.
.
.386P
.
.
.
mov edx,cr0
.
.
.
Заметим, что чтобы Турбо Ассемблер мог ассемблировать специ-
альные формы инструкции MOV, должна действовать директива .386Р
(так как они являются привилегированными инструкциями).
В общем случае специальные регистры, к которым можно полу-
чить доступ с помощью новых форм инструкции MOV, используются
только системным программным обеспечением и не используются прик-
ладными программами.
TASM2 #3-5/Док = 157 =
32-разрядные версии инструкций процессора 8086
-----------------------------------------------------------------
Многие инструкции процессора 8086 расширены таким образом,
чтобы можно было использовать новые возможности адресации и новые
операнды процессора 80386. Следующая инструкция выполняет 32-раз-
рядное вычитание 32-битового регистра EBX из 32-битовой перемен-
ной по адресу EBP+EAX*8+10h. При этом для ссылки на целевую ячей-
ку памяти используются 32-разрядные регистры:
sub DWORD PTR [ebp+eax*8+10h],ebx
Для 32-разрядных возможностей, добавленных в большинство ин-
струкций процессора 8086, не требуется новая мнемоника инструк-
ций. На 32-разрядный характер операции указывают обычно операнды
или тип сегмента, в котором выполняется операция. Однако для не-
которых инструкций процессора 8086 потребовалась новая мнемоника,
указывающая, что они расширены до 32-разрядных возможностей про-
цессора 80386. Далее мы рассмотрим эти инструкции.
Новые версии инструкций LOOP и JCXZ
-----------------------------------------------------------------
Инструкции LOOP, LOOPE, LOOPNE и JCXZ работают обычно с
16-разрядным регистром CX. Процессор 8086 предусматривает как 16-
разрядную, так и 32-разрядную версию этих инструкций. 32-разряд-
ные версии вместо регистра CX могут работать с регистром ECX.
Инструкции LOOP, LOOPE и LOOPNE используют в качестве счет-
чика цикла регистр CX или ECX, в зависимости от типа сегмента (16
-битового или 32-битового). Если вы хотите обеспечить, чтобы в
качестве регистра управления циклом всегда использовался регистр
CX (даже в 32-битовом сегменте), то используйте словную форму
данных инструкций (LOOPW, LOOPWE или LOOPWNE). Аналогично, если
нужно обеспечить использование в качестве управляющего циклом ре-
гистра регистр ECX, используйте следующие формы инструкций:
LOOPD, LOOPDE и LOOPDNE.
Инструкция LOOPD уменьшает содержимое ECX и выполняет пере-
ход по указанному смещению, если возвращаемое значение отлично от
нуля. Например, следующий цикл выполняется 8000000000h раз:
.
.
.
TASM2 #3-5/Док = 158 =
mov ecx,8000000000h
LoopTop:
loopd LoopTop
.
.
.
Инструкция LOOPDE уменьшает содержимое ECX регистра и выпол-
няет переход на целевое смещение, пока флаг нуля равен 1, а ECX
не равен 0. (LOOPDZ - это еще одна форма той же инструкции.) Ана-
логично, инструкция LOOPDNE уменьшает значение регистра ECX и пе-
реходит по целевому смещению, пока флаг нуля равен 0, а ECX не
равен 0. (LOOPDNZ - это эквивалентная инструкция.) Например, сле-
дующий цикл повторяется, пока значение, считываемое из порта вво-
да-вывода в регистр DX не станет равным 09h или проверка порта не
будет выполнена 1000000000h раз:
.
.
.
mov ecs,1000000000h
LoopTop:
in al,dx
cmp al,09h
loopdne LoopTop
jnz TimeOut
.
.
.
TimeOut:
.
.
.
Отметим, что действие инструкции JNZ в данном примере отра-
жает результат сравнения, а не инструкции LOOPDNE, так как инс-
трукции цикла не влияют на состояние флагов. Инструкция JCXZ вы-
полняет переход, если содержимое CX равно 0, а JECXZ выполняет
переход, если равно нулю содержимое ECX. Например, следующий цикл
может работать с 32-битовыми счетчиками.
.
.
.
LoopTop:
jecxz LoopEnd
TASM2 #3-5/Док = 159 =
.
.
.
jmp LoopTop
LoopEnd:
.
.
.
TASM2 #3-5/Док = 160 =
Новые версии строковых инструкций
-----------------------------------------------------------------
В процессоре 80386 все строковые инструкции могут работать с
байтами, словами или двойными словами. Версии этих инструкций,
работающие с двойными словами, просто заканчиваются буквой D, а
не буквами W или B. Это следующие инструкции:
CMPSD MOVSD SCASD
INSD OUTSD STOSD
LODSD
Каждая из этих инструкций работает сразу с 32 битами данных
и увеличивает или уменьшает при каждом повторении соответствующий
регистр-указатель на 4. Например, в следующем фрагменте программы
инструкция MOVSD используется для копирования двух двойных слов,
начиная со смещения DwordTable в два двойных слова, начинающихся
со смещения Buffer:
.
.
.
cld
mov si,OFFSET DwordTable
mov di,OFFSET Buffer
mov cx,2
rep movsd
.
.
.
При этом результат соответствует следующему коду, в котором
используется инструкция MOVSB.
.
.
.
cld
mov si,OFFSET DwordTable
mov di,OFFSET Buffer
mov cx,8
rep movsb
.
.
.
TASM2 #3-5/Док = 161 =
Один из способов рассмотрения строковых инструкций состоит в
том, что строковые инструкции размером в двойное слово аналогичны
строковым инструкциям размером в слово, подобно тому, как строко-
вые инструкции размером в слово соответствуют байтовым строковым
инструкциям.
TASM2 #3-5/Док = 162 =
Инструкция IRETD
-----------------------------------------------------------------
Инструкция IRETD аналогична инструкции IRET. Она извлекает
из стека EIP, а затем CS, как двойное слово (отбрасывая старшее
слово), после чего извлекает EFLAGS, как двойное слово.
Инструкции PUSHFD и POPFD
-----------------------------------------------------------------
Инструкция PUSHFD заносит в стек полный 32-разрядный регистр
флагов процессора 80386. Инструкция POPFD извлекает из стека пол-
ный 32-разрядный регистр флагов.
Инструкции же PUSHF и POPF заносят в стек и извлекают из не-
го только младшие 16 битов регистра флагов.
Инструкции PUSHAD и POPAD
-----------------------------------------------------------------
Инструкция PUSHAD заносит в стек восемь 32-разрядных общих
регистров в следующем порядке: EAX, ECX, EDX, EBX, ESP, EBP, ESI,
EDI. Значение, заносимое для регистра ESP, соответствует значению
регистра ESP в начале инструкции PUSHAD. Инструкция POPAD извле-
кает из стека семь 32-разрядных общих регистров в следующем по-
рядке: EDI, ESI, EBP, EBX, EDX, ECX и EAX. Все эти регистры
можно сохранить в стеке с помощью инструкции PUSHAD, а затем
восстановить с помощью инструкции POPAD. Регистр ESP инструкцией
POPAD не восстанавливается. Вместо этого выполняется выполняется
увеличение на 32, чтобы отбросить блок из восьми 32-разрядных об-
щих регистров, ранее сохраненный в стеке. Сохраненное ранее зна-
чение ESP игнорируется.
Сравните с этими инструкциями инструкции PUSHA и POPA, кото-
рые сохраняют в стеке и извлекают из него только младшие 16 битов
общих регистров.
TASM2 #3-5/Док = 163 =
Новые версии инструкции IMUL
-----------------------------------------------------------------
Дополнительно к формам инструкции IMUL, предусмотренным для
процессоров 8086/80186/80286 в процессоре 80386 предусмотрена
возможно самая удобная форма инструкции IMUL: любой общий регистр
или ячейку памяти можно умножать на любой общий регистр, при этом
результат снова сохраняется в одном из исходных регистров. Напри-
мер, инструкция:
imul ebx,[edi*4+4]
умножает содержимое регистра EBX на значение размером в двойное
слово, хранящееся по адресу памяти edi*4+4, а результат сохраняет
снова в регистре EBX.
Как можно заметить, первый операнд в этой форме инструкции
IMUL представляет собой целевой регистр. Этот операнд может быть
любым 16- или 32-разрядным общим регистром. Второй операнд может
задаваться любым 16- или 32-разрядным общим регистром или ячейкой
памяти. Размеры двух операндов должны совпадать. Если результат,
рассматриваемый, как значение со знаком, слишком велик, чтобы его
можно было разместить в приемнике, то устанавливаются флаги пере-
носа и переполнения.
Как можно ожидать, в процессоре 80386 имеются также расши-
ренные формы инструкции IMUL для процессоров 8086/80186/80286,
поддерживающие 32-разрядные операнды. Например, в следующем коде
ECX умножается на 1000000000h, а результат сохраняется в регистре
EBP:
imul ebp,ecx,100000000h
а следующая инструкция умножает ECX на EBX, записывая результат в
EDX:EAX:
imul ebx
TASM2 #3-5/Док = 164 =
Чередование 16-разрядных и 32-разрядных инструкций и сегментов
-----------------------------------------------------------------
Обычно вам требуется использовать только 16-разрядные сег-
менты (USE16). Но даже в этом случае вы можете пользоваться в
арифметических и логических операциях 32-битовыми регистрами.
Вы можете также использовать любое сочетание 16-битовых и 32
-битовых сегментов кода и данных. Если вы не пишете программное
обеспечение для операционной системы и не знаете в точности, что
вы делаете, то нет абсолютно никаких причин использовать 32-бито-
вые сегменты кода. Если вы не примете специальных мер для перек-
лючения процессора в режим, необходимый для выполнения 32-битовых
сегментов кода, то под управлением DOS они работать не смогут.
Последующие операционные системы возможно предоставят вам спосо-
бы, с помощью которых можно с пользой работать с 32-разрядными
сегментами, но пока применять их не следует.
Однако вы можете использовать 32-разрядные сегменты данных и
использовать в своей программе преимущества "свободной" адреса-
ции, обеспечиваемой 32-разрядными регистрами процессора 80386.
Давайте рассмотрим основные аспекты использования сегментов
USE16 и USE32. Максимальный размер регистров USE16 не может пре-
вышать 64К, поэтому на любую ячейку сегмента USE16 можно ссылать-
ся с помощью 16-разрядного адреса. С другой стороны, сегменты
типа USE32 могут иметь размер до 4 гигабайт, поэтому для ссылки
на произвольную ячейку такого сегмента требуется использовать 32-
разрядный адрес. (Примечание: С сегментами кода типа USE32 можно
работать только в защищенном режиме.)
Ясно, что если вам нужен сегмент, размер которого превышает
64К, то вы должны использовать сегмент типа USE32. Однако, нет
такой ситуации, при которой вы обязательно должны использовать
сегмент USE16. Здесь вы можете удивиться и подумать, почему бы не
упростить задачу и не использовать сегменты USE32 все время.
Ответ заключается в том способе, с помощью которого процессор
80386 поддерживает операнды размером в слово и двойное слово, а
также 16- и 32-разрядные смещения.
Процессор 80386 является развитием процессора 8086, в кото-
ром для различия между двумя единственно возможными размерами
операндов (8 и 16 бит) используется один бит. В процессоре 8086
имеется одно множество режимов адресации (это уже знакомые вам
режимы адресации, при которых используются регистры BX, SI, DI и
BP), при которых поддерживаются только 16-битовые смещения. В
TASM2 #3-5/Док = 165 =
следующем фрагменте программы используется операнд размером в 8
бит, а для адресации к памяти используется 16-битовый режим адре-
сации (принятый в процессоре 8086):
mov al,[bx+1000h]
В сегментах кода USE16 процессор 80386 для выбора между 8- и
16-разрядными операндами используется обычно тот же бит, что и в
процессоре 8086, и 16-битовое смещение. Однако с помощью префикса
размера операнда (066h) любую инструкцию сегмента USE16 можно
преобразовать таким образом, что она будет поддерживать 32-раз-
рядные операнды. В этом случае бит размера такой инструкции поз-
воляет выбрать между 8- и 32-битовыми операндами, а не между 8- и
16-битовыми.
Аналогично, любую инструкцию в сегменте USE16 можно преобра-
зовать таким образом, что будут использоваться 32-битовые режимы
адресации процессора 80386 (большой адрес, как описывалось в раз-
деле "Новые режимы адресации"). Для этого перед инструкцией поме-
щается префикс размера адреса (067h).
Например, в коде, ассемблируемом из следующего исходного
текста:
.
.
.
.386
.
.
.
DataSeg SEGMENT USE16
TestLoc DD ?
DataSeg ENDS
.
.
.
DataSeg SEGMENT USE16
mov ax,DataSeg
mov ds,ax
ASSUME DS:DataSeg
db 66h
mov ax,WORD PTR [TestLoc]
.
.
.
TASM2 #3-5/Док = 166 =
CodeSeg ENDS
.
.
.
так как префикс размера операнда преобразует размер операндов ин-
струкции в 32 бита, 4 байта TestLoc загружаются в EAX, а не 2
байта TestLoc в AX.
Кроме того, инструкции сегментов кода USE32 обычно обращают-
ся к 8- или 32-разрядным операндам и 32-разрядные режимы адреса-
ции процессора 80386. Однако, чтобы отдельные инструкции работали
в 16-разрядном режиме (то есть в режиме процессора 8086 с операн-
дами размером в слово и малым адресом), как в сегментах USE16,
можно использовать префиксы задания размера операнда и размера
адреса.
Не стоит беспокоиться о том, чтобы изучать префиксы задания
размеров операндом и адресов в программах для процессора 80386.
Турбо Ассемблер выполняет генерацию префиксов, необходимых для
использования 16-разрядных средств в сегментах USE32 и 32-разряд-
ных средств в сегментах USE16 прозрачным для программиста обра-
зом. Например, если вы в сегменте кода USE32 используете следую-
щую инструкцию:
mov [bx],ax
то Турбо Ассемблер автоматически включит перед инструкцией пре-
фикс задания размера операнда и размера адреса. Мы поясняем здесь
префиксы указания размеров только для того, чтобы вы поняли ос-
новной момент в выборе между 16- и 32-битовыми размерами сегмен-
тов: нужно минимизировать число генерируемых префиксов задания
размера.
Предположим, например, что вы выбрали сегмент USE16, а затем
ссылаетесь на операнды размером в двойное слово, адресуемые с по-
мощью 32-битовых режимов адресации, например:
mov eax,[edx+ecx*2+1]
Турбо Ассемблеру пришлось бы генерировать префиксы задания
размеров операндов и размера адреса практически для каждой инс-
трукции вашей программы, что привело бы к значительному увеличе-
нию ее размера и потерям в производительности. При использовании
же сегмента USE32 в той же программе префиксы использовать прак-
тически не потребовалось бы.
TASM2 #3-5/Док = 167 =
Теперь вы можете видеть, что процесс выбора сегмента нес-
колько более сложен, чем это может показаться. Если вам нужен
сегмент, превышающий 64К, вы должны выбрать сегмент типа USE32.
Если вам требуется сегмент, не превышающий по размеру 64К, вам
следует выбрать сегмент USE32, если вы чаще используете 32-бито-
вые, а не 16-битовые операнды и режимы адресации. Если же верно
обратное, то следует использовать сегменты типа USE16. Не всегда
легко сказать, какой тип сегмента будет более эффективным, но вы
всегда можете ассемблировать свою программу тем или другим спосо-
бом и посмотреть, в каком случае полученный код будет более ком-
пактным.
Теперь вы можете также увидеть, для чего иногда необходимы
операции LARGE и SMALL, чтобы можно было ассемблировать опережаю-
щие ссылки. Поскольку тип USE сегменте кода определяет используе-
мые по умолчанию размеры адресных ссылок, то предполагается, что
опережающие ссылки имеют тот же размер, что и сегменты кода. Опе-
рацию LARGE нужно использовать для опережающих ссылок из сегмен-
тов кода USE16 на сегменты данных USE32, а операцию SMALL вы мо-
жете использовать для указания необходимости использования для
опережающих ссылок из сегмента кода USE16 16-битовой адресации.
Пример функции для процессора 80386
-----------------------------------------------------------------
Давайте рассмотрим теперь пример программы для процессора
80386. Неплохо было бы рассмотреть полную программу для процессо-
ра 80386, однако поскольку операционные системы, использующие
процессор 80386, не получили еще достаточно широкого распростра-
нения (поэтому мы не можем использовать стандартные запросы к па-
мяти, запросы для получения кода клавиши, вывода на экран дисплея
и завершения программы). Вместо этого давайте рассмотрим полную
функцию, написанную на Ассемблере для процессора 80386.
В нашем примере функции с именем CalcPrimes используются
преимущества больших размером сегментов USE32 для чрезвычайно
легкого вычисления всех простых чисел в заданном диапазоне. Функ-
ция просто вычисляет все произведения в диапазоне от 2 до макси-
мального простого числа, помечая каждое произведение в одной
большой таблице, как не простое. При использовании процессора
8086 данный метод мог бы хорошо работать только для массивов, не
превышающих 64К (максимальный размер сегментов), и оказался бы
совершенно неработоспособным при достижении 1 мегабайта (макси-
мальный объем памяти, к которой может адресоваться процессор
TASM2 #3-5/Док = 168 =
8086).
В отличие от этого сегменты USE32 и 32-разрядные регистры
позволяют в процессоре 80386 легко работать с таблицей, размер
которой достигает 4 гигабайт. Фактически, процессор 80386 может
даже с помощью страничной организации памяти работать с памятью
объемом в терабайт (1000 гигабайт)! Конечно, время вычисления
простых чисел в этом случае оказалось бы при этом неприемлемо ве-
лико, но мы хотим обратить внимание на то, что в отличие от про-
цессоров 8086 и 80286 архитектура адресации к памяти процессора
80386 не является ограничивающим фактором в программах, где тре-
буется использовать значительные объемы памяти.
Исходный текст функции CalcPrimes выглядит следующим обра-
зом:
;
; Пример кода для процессора 80386 для вычисления всех
; простых чисел в диапазоне от 0 до MAX_PRIME включительно.
;
; Ввод: Отсутствует.
;
; Вывод:
; ES:EAX - указатель на PrimeFlags, который содержит 1 для
; смещения каждого числа, которое является простым, и 0 для
; смещения каждого числа, не являющегося простым.
;
; Нарушаемые регистры:
; EAX, EBX
;
; Используется алгоритм, приведенный Чарльзом Пецольдом в
; журнале PC Magazine (том 7, номер 2).
;
.386
MAX_PRIMES EQU 1000000 ; верхняя граница
; проверки простого
; числа
DataSeg SEGMENT USE32
PrimeFlags DB (MAX_PRIME + 1) DUP (?)
DataSeg ENDS
CodeSeg SEGMENT USE32
ASSUME CS:CodeSeg
CalcPrimes PROC
push ds ; сохранить регистр DS вызывающей
; программы
TASM2 #3-5/Док = 169 =
mov ax,DataSeg
mov ds,ax
ASSUME DS:DataSeg
mov es,ax
ASSUME ES:DataSeg
;
; Предположим, что все числа в заданном диапазоне являются
; простыми.
;
mov al,1
mov adi,OFFSET PrimeFlags
mov ecx,MAX_PRIME+1
cld
rep stosb
;
; Теперь исключим все числа, не являющиеся простыми, вычисляя все
; произведения (кроме произведений на 1), меньшие или равные
; MAX_PRIMES, всех чисел до MAX_PRIMES.
;
mov eax,2 ; начнем с 2, так как 0 и 1
; заведомо просты и не могут
; использоваться для исключения
; произведений
PrimeLoop:
mov ebx,eax ; начальное значение для
; вычисления всех произведений
MultipleLoop:
add ebx,eax ; вычислить следующее произве-
; дение
cmp ebx,MAX_PRIME ; мы проверили все произведения
; данного числа?
ja CheckNextBaseValue ; да, перейдем к следующему
; числу
mov [PrimeFlags+ebx],0 ; это число не является
; простым, так как его можно
; получить в результате произ-
; ведения
jmp MultipleLoop
CheckNextBaseValue:
inc eax ; ссылка на следующее начальное
; значение (для вычисления
; всех произведений)
cmp eax,MAX_PRIME ; мы исключили все произведе-
; ния?
jb PrimeLoop ; нет, проверить следующее
; множество произведений
TASM2 #3-5/Док = 170 =
;
; Возвратить указатель на таблицу статусов простых и не простых
; чисел в ES:EAX
;
mov eax,OFFSET PrimeFlags
pop ds ; восстановить DS вызывающей
; программы
ret
CalcPrimes ENDP
CodeSeg ENDS
END
Обратите внимание на то, как легко процессор 80386 позволя-
ет вам обрабатывать 32-битовые целые числа и массив размером
1000000 байтов. Фактически вся функция занимает только 20 байт. В
результате выполнения функция CalcPrimes возвращает большой ука-
затель дальнего типа на таблицу PrimeFlags, в которой смещение,
соответствующее каждому числу, содержит 1, если это число прос-
тое, и 0, если число не является простым. Например, PrimeFlag +3
было бы равно 1, так как 3 - простое число, а PrimeFlag+4 - 0,
так как 4 не является простым.
Размер PrimeFlags и максимальное проверяемое число определя-
ются с помощью приравненного идентификатора MAX_PRIME. На практи-
ке было бы более удобно, если бы вызывающая программа передавала
бы функции CalcPrimes адрес таблицы произвольного размера, а так-
же максимальное проверяемое число (которое представляет собой
длину таблицы - 1). Тогда эта функция удовлетворяла бы любым пот-
ребностям вызывающей программы по вычислению простых чисел и ее
не нужно было бы переассемблировать. В данном примере локальная
метка PrimeFlags используется главным образом для того, чтобы
проиллюстрировать использование USE32.
Приведем пример версии CalcPrimes, которая работает с пере-
даваемыми параметрами (таблицей и длиной таблицы):
;
; Пример кода для процессора 80386 для вычисления всех
; простых чисел в диапазоне от 0 до MAX_PRIME включительно.
;
; Ввод: (предполагается дальний вызов типа LARGE, при котором в
; стеке передается 6 байтов):
;
; ESP+06h на входе (последний занесенный в стек параметр)
; представляет собой значение размеров в двойное слово -
; максимальное число, проверяемое на то, является ли оно
TASM2 #3-5/Док = 171 =
; простым.
;
; ESP+0Ah на входе (первый занесенный в стек параметр)
; представляет собой указатель LARGE дальнего типа
; (смещение размером 6 байтов) на таблицу, которая содержит 1
; для смещения каждого числа, которое является простым, и 0
; для смещения каждого числа, не являющегося простым.
;
; Вывод: Отсутствует.
;
; Нарушаемые регистры:
; EAX, EBX, EDX, EDI
;
; Используется алгоритм, приведенный Чарльзом Пецольдом в
; журнале PC Magazine (том 7, номер 2).
;
.386
CodeSeg SEGMENT USE32
ASSUME CS:CodeSeg
CalcPrimes PROC FAR
push es ; сохранить ES
; вызывающей программы
push fs ; сохранить FS
; вызывающей программы
;
; Получить параметры.
mov ecx,[esp+4+06h]
lfs edx,[esp+4+0ah]
;
; Предположим, что все числа в заданном диапазоне являются
; простыми.
;
push fs
pop es
mov al,1 ; ES указывает на
; сегмент таблицы
mov adi,edx
cld
push ecx ; сохранить максимальное
; проверяемое число
inc ecx ; задать максимальное
; число (включительно)
rep stosb
pop ecx ; получить снова макси-
; мальное проверяемое
; число
TASM2 #3-5/Док = 172 =
;
; Теперь исключим все числа, не являющиеся простыми, вычисляя все
; произведения (кроме произведений на 1), меньшие или равные
; MAX_PRIMES, всех чисел до MAX_PRIMES.
;
mov eax,2 ; начнем с 2, так как 0 и 1
; заведомо просты и не могут
; использоваться для исключения
; произведений
PrimeLoop:
mov ebx,eax ; начальное значение для
; вычисления всех произведений
MultipleLoop:
add ebx,eax ; вычислить следующее произве-
; дение
cmp ebx,ecx ; мы проверили все произведения
; данного числа?
ja CheckNextBaseValue ; да, перейдем к следующему
; числу
mov BYTE PTR fs:[edx+ebx],0 ; это число не является
; простым, так как его можно
; получить в результате произ-
; ведения
jmp MultipleLoop
CheckNextBaseValue:
inc eax ; ссылка на следующее начальное
; значение (для вычисления
; всех произведений)
cmp eax,ecx ; мы исключили все произведе-
; ния?
jb PrimeLoop ; нет, проверить следующее
; множество произведений
pop fs ; восстановить FS вызывающей
; программы
pop es ; восстановить ES вызывающей
; программы
ret
CalcPrimes ENDP
CodeSeg ENDS
END
TASM2 #3-5/Док = 173 =
Сопроцессор 80287
-----------------------------------------------------------------
Набор инструкций арифметического сопроцессора 80287 за од-
ним исключением полностью совпадает с набором инструкций сопро-
цессора 8087. Этим исключением является инструкция сопроцессора
80287 FSETMP, которая переводит сопроцессор 80287 в защищенный
режим. Защищенный режим сопроцессора 80287 соответствует защищен-
ному режиму процессора 80286, с которым обычно спарен данный соп-
роцессор (хотя сопроцессор 80287 можно также использовать и с
процессором 80386). Конечно, каждая программа, которая использует
инструкцию FSETMP не будет работать на сопроцессоре 8087, так как
сопроцессор 8087 не поддерживает данную инструкцию.
В Турбо Ассемблере поддержка сопроцессора 80287 разрешается
по директиве .287. Подробная информация об инструкциях сопроцес-
сора 80287 содержится в Главе 3 "Справочного руководства".
Сопроцессор 80387
-----------------------------------------------------------------
Набор инструкций арифметического сопроцессора 80387 предс-
тавляет собой надмножество набора инструкций сопроцессоров
8087/80287. В сопроцессоре 80387 имеются следующие новые инструк-
ции:
FCOS FSINCOS FUCOMP
FPREM1 FUCOM FUCOMPP
FSIN
Инструкция FUCOM выполняет неупорядоченное сравнение между
регистра ST(0) и других регистров сопроцессора 80387. Эта инс-
трукция аналогична инструкции FCOM, только состояние результата
устанавливается неупорядоченным, если один из результатов предс-
тавляет собой не число, а не генерируется исключительная ситуа-
ция по недопустимой операции, как в случае инструкции FCOM. Инс-
трукция FUCOM выполняет неупорядоченное сравнение и дважды
выполняет извлечение из стека.
Инструкция FCOS вычисляет косинус содержимого регистра
ST(0), инструкция FSIN вычисляет синус содержимого регистра
ST(0), а инструкция FSINCOS вычисляет синус и косинус содержимого
этого регистра.
Инструкция FPREM1 вычисляет остаток от деления ST(0) на
TASM2 #3-5/Док = 174 =
ST(1) в формате, совместимом с форматом IEEE.
Не забывайте о том, что любая программа, использующая одну
из этих инструкций, не будет работать на сопроцессоре 8087 или
80287. Кроме того из-за идентичного выполнения операций в реаль-
ном и защищенном режиме инструкция FSETMP в сопроцессоре 80287
игнорируется.
В Турбо Ассемблере поддержка сопроцессора 80387 разрешается
по директиве .387. Подробная информация об инструкциях сопроцес-
сора 80287 содержится в Главе 3 "Справочного руководства".
TASM2 #3-5/Док = 175 =
Глава 11. Улучшенный режим Турбо Ассемблера
-----------------------------------------------------------------
Для тех, кто пытается приспособить для своих целей макроас-
семблер MASM, эта глава является, вероятно, наиболее важной в
данном руководстве. Кроме очень хорошей совместимости с синтак-
сисом MASM, Турбо Ассемблер позволяет несколько сгладить неудоб-
ства программирования с использованием MASM с помощью улучшенного
(или оптимального) режима.
Кроме всего прочего улучшенный режим (Ideal mode) позволяет
вам, глядя только на исходный текст, определить, как будет вести
себя выражение или операнд инструкции. При этом нет необходимости
держать в памяти все тонкости MASM. С помощью улучшенного режима
вы просто сможете писать ясные и четкие выражения, которые будут
выполняться так, как это было задумано.
Улучшенный режим использует почти все ключевые слова, опера-
ции и конструкции языка MASM. Это означает, что вы можете иссле-
довать и использовать средства улучшенного режима поочередно, не
прибегая к изучению большого числа новых правил или ключевых
слов. Все средства улучшенного режима представляют собой расшире-
ния или модификацию возможностей, имеющихся в MASM.
В данной главе описываются средства улучшенного режима и по-
ясняется, как новые синтаксические правила улучшенного режима мо-
гут помочь вам сэкономить время и усилия. Мы также обсудим под-
робно новые возможности улучшенного режима и поясним различия
между синтаксисом MASM и синтаксисом улучшенного режима.
Что такое улучшенный режим?
-----------------------------------------------------------------
В улучшенном режиме Турбо Ассемблера введен новый синтаксис
выражений и операндов инструкций. Этот новый синтаксис не отлича-
ется существенно от синтаксиса MASM, но является более простой и
понятной реализацией операций и ключевых слов MASM. Кроме того в
нем используются более осмысленные (для вас и Турбо Ассемблера)
формы.
В улучшенный режим добавлена строгая проверка типов в выра-
жениях. Это помогает свести к минимуму ошибки, возникающие при
присваивании регистрам и переменным значений неверных типов и при
использовании конструкций, которые выглядят корректными в исход-
ном тексте, но ассемблируются совсем не в тот вид, который вы
TASM2 #3-5/Док = 176 =
ожидаете. Вместо того, чтобы делать различные предположения отно-
сительно значений и выражений, улучшенный режим позволяет вам пи-
сать текст, имеющий логический и эстетический смысл.
Из-за строгой проверки типа выражения улучшенного режима
Турбо Ассемблера гораздо более понятны и менее способствуют полу-
чению непредвиденных результатов. В результате многие из проблем
MASM, о которых мы предупреждали вас в предыдущих главах, исчеза-
ют под зорким оком улучшенного режима.
Улучшенный режим содержит также ряд средств, облегчающих
процесс программирования как для новичков, так и для опытных
пользователей. Вот некоторые из таких средств:
- дублирование имен элементов во множественных структурах;
- сложные выражения HIGH и LOW;
- предсказуемая обработка директив EQU;
- корректная обработка сгруппированных сегментов данных;
- улучшенная содержательность директив;
- хорошо воспринимаемые выражения, заключенные в квадратные скобки.
Для чего используется улучшенный режим?
-----------------------------------------------------------------
Есть множество причин, по которым вам следует использовать
улучшенный режим Турбо Ассемблера. Если вы изучаете язык Ассемб-
лера, вы сможете легко строить для получения желаемого эффекта
выражения и операторы улучшенного режима. Вам не придется ходить
вокруг да около и пробовать различные варианты, пока вы не ис-
пользуете инструкцию, которая делает именно то, что вы хотите.
Если у вас имеется опыт программирования на Ассемблере, то вы мо-
жете использовать средства улучшенного режима Турбо Ассемблера
для разработки сложных программ, использующих расширения языка,
такие, например, как вложенные структуры и объединения.
В результате более четкого синтаксиса улучшенный режим Турбо
Ассемблера позволяет ассемблировать файлы на 30% быстрее, чем в
режиме MASM. Чем больше объем ваших программ и программных комп-
лексов, тем больше времени вы сэкономите при ассемблировании, пе-
рейдя в улучшенный режим.
Строгие правила проверки типов, налагаемые улучшенным режи-
мом, позволяют Турбо Ассемблеру выявлять ошибки, которые в проти-
вном случае вы обнаружили бы только при отладке вашей программы
или ее выполнении. Это аналогично тому, каким образом компиляторы
TASM2 #3-5/Док = 177 =
языков высокого уровня оказывают вам помощь, отмечая сомнительные
конструкции и несоответствие размеров данных.
Хотя в улучшенном режиме Турбо Ассемблера в некоторых выра-
жениях используется другой синтаксис, вы, тем не менее, можете
писать программы, которые будут одинаково хорошо ассемблироваться
как в режиме MASM, так и в улучшенном режиме. Вы можете также в
одном и том же исходном файле переключаться из режима MASM в
улучшенный режим и обратно так часто, насколько это необходимо.
Это особенно полезно при экспериментировании со средствами улуч-
шенного режима, или при преобразовании имеющихся программ, напи-
санных в синтаксисе MASM. Вы можете переключиться в улучшенный
режим в новых частях программы, добавляемых к имеющемуся исходно-
му коду. При этом в остальных частях программы сохранится полная
совместимость с MASM.
Переключение в улучшенный режим и выход из него
-----------------------------------------------------------------
Для переключения между режимом MASM и улучшенным режимом
можно использовать директивы IDEAL и MASM. Турбо Ассемблер всегда
начинает ассемблирование исходного файла в режиме MASM. Для пе-
реключения в улучшенный режим перед использованием любых средств
этого режима в исходный файл нужно включить директиву IDEAL. С
этого момента и до следующей директивы MASM все операторы будут
вести себя так, как описывается в данной главе. Вы можете перек-
лючаться из режима MASM в режим IDEAL в исходном файле столько
раз, сколько потребуется в в любом месте исходного файла. Приве-
дем пример:
DATA SEGMENT ; начало в режиме MASM
abc LABEL BYTE ; abc адресуется к xyz,
; как к байту
xyz DW 0 ; определить слово по
; метке xyz
DATA ENDS ; завершить сегмент
; данных
IDEAL ; перейти в улучшенный
; режим
SEGMENT CODE ; ключевое слово SEGMENT
; теперь следует первым
PROC MyProc ; ключевое слово PROC
; тоже следует первым
.
. ; здесь можно программировать
TASM2 #3-5/Док = 178 =
. ; в улучшенном режиме
END MyProc ; повторение метки MyProc
; необязательно
ENDS ; повторение имени сегмента
; не требуется
MASM ; переключение обратно в
; режим MASM
CODE SEGMENT ; перед ключевым словом SEGMENT
; теперь требуется имя
Func2 PROC ; имя теперь также следует перед
; ключевым словом PROC
.
. ; программирование в режиме
. ; MASM
IDEAL ; переключение обратно в
. ; улучшенный режим
. ; программирование в
. ; улучшенном режиме
MASM ; возвращение в режим MASM
Func2 ENDP ; имя опять требуется указывать
; перед ключевым словом
CODE ENDS ; здесь также требуется имя
Как вы можете заметить, в улучшенном режиме ключевые слова
директив (например PROC или SEGMENT) указываются перед соответс-
твующими именами идентификаторов, то есть в порядке, обратном то-
му, который принят в MASM. Кроме того имеется возможность повто-
рить имя процедуры или сегмента после директив ENDP или ENDS.
Добавление этого имени идентифицирует сегмент или процедуру, ко-
торая заканчивается, и делает программу более понятной. Это хоро-
ший практический прием, особенно в программах, содержащих нес-
колько вложенных процедур и сегментов. Однако указывать имя иден-
тификатора после ENDP или ENDS необязательно.
TASM2 #3-5/Док = 179 =
Отличия улучшенного режима и режима MASM
-----------------------------------------------------------------
В данном разделе мы опишем основные различия между
улучшенным режимом и режимом MASM. Если вы знакомы с MASM, вы
можете пожелать поэкспериментировать с отдельными средствами,
преобразуя небольшие части имеющихся программ в улучшенный режим.
Не забывайте только при этом заключать новый исходный код в
ключевые слова IDEAL и MASM. Следуя такой схеме и осваивая
улучшенный режим шаг за шагом, вы можете ассемблировать текущую
программу без необходимости пересматривать каждую инструкцию и
использования специальных средств улучшенного режима. В
конце-концов вы, конечно, можете прийти к решению программировать
целиком в улучшенном режиме. Либо вы можете чередовать модули в
режиме MASM и в улучшенном режиме. Выбор остается за вами.
Лексемы улучшенного режима
-----------------------------------------------------------------
Турбо Ассемблер считывает и воспринимает вашу программу,
разбивая текст на отдельные слова или символы, которые называются
лексемами. Примеры лексем включают в себя метки, такие как VALUE,
NAME или AGE, плюс другие идентификаторы числа, части выражений и
арифметические операции (такие как +, -, * и /).
Два типа лексем - идентификаторы и числа с плавающей точкой
- имеют в улучшенном режиме слегка различные формы. Как описыва-
ется далее, эти изменения поясняют некоторые двусмысленности и
неоднозначность в синтаксисе MASM.
Лексемы-идентификаторы
-----------------------------------------------------------------
В случае идентификаторов улучшенного режима точку в качестве
элемента имени идентификатора использовать не допускается. Точку
можно использовать только как операцию элемента структуры или в
числе с плавающей точкой.
Элементы структур и объединений (некоторые называют их поля-
ми) не определяются, как глобальные идентификаторы (то есть как
идентификаторы, доступные из любой части вашей программы). Эле-
менты структур и объединений существуют только внутри той струк-
туры, к которой они принадлежат. Это позволяет вам иметь несколь-
ко структур, содержащих элементы с одинаковыми именами. Вне
TASM2 #3-5/Док = 180 =
структуры вы также можете дублировать эти имена для других целей,
например:
Pennies DW 0
STRUC Heaven
Dimes DW ?
Niсkels DW ?
Pennies DW ? ; конфликта здесь нет
ENDS
Take Heaven <>
В этом примере показано, как создаются переменная с тремя
полями (структура с именем Heaven). Поля Dimes и Nickels уникаль-
ны для структуры. Однако Pennies встречается дважды - вне струк-
туры и внутри ее.
Этот пример показывает, что одно и тоже имя (Pennies) может
встречаться как внутри структуры, так и вне структуры, и это не
приводит к конфликту, что в MASM сделать было нельзя.
Переменная Pennies вне структуры Heaven отличается от эле-
мента Pennies, который используется внутри структуры. Ссылка на
дубликат имени внутри структуры требует трех элементов: имени
структуры, точки, и имени элемента. В данном примере Take.Pennies
равно смещению поля Pennies внутри Heaven. Однако Pennies равно
смещению переменной вне структуры.
TASM2 #3-5/Док = 181 =
Дублирование имен элементов
-----------------------------------------------------------------
Улучшенный режим позволяет вам также дублировать имена эле-
ментов в различных структурах. Элементы могут быть одного типа
или разных типов, как в следующих двух структурах, где обе струк-
туры содержат поля Size одного типа и в одной и той же позиции,
плюс поля Amount разных типов и в различных позициях:
STRUC SomeStuff
Size DW ?
Flag DB ?
Amount DW ?
ENDS
STRUC OtherStuff
Size DW ? ; конфликта здесь нет
Amount DB ? ; здесь также
ENDS
TASM2 #3-5/Док = 182 =
Лексемы, представляющие собой числа с плавающей точкой
-----------------------------------------------------------------
В улучшенном режиме Турбо Ассемблера десятичные числа с пла-
вающей точкой должны всегда включать в себя символ точки (.):
FP DT 1.0e7 ; значение с плавающей точкой
; в улучшенном режиме
Здесь определяется 10-байтовое значение с плавающей точкой с
именем FP, равное 1.0e7. В режиме MASM вы можете использовать
также следующую форму (которая также воспринимается, но является
менее понятной):
FP DT 1E7 ; значение с плавающей точкой
; в режиме MASM
Может показаться, что здесь нет ничего плохого, однако если
вы в предыдущей части программы включите директиву .RADIX 16, ко-
торая изменяет используемое по умолчанию основание с десятичного
на шестнадцатиричное, то MASM ассемблирует ваше значение с плава-
ющей точкой, как шестнадцатиричное число 01. Требуя от вас указа-
ния десятичной точки, улучшенный режим улучшенный режим никогда
не привет к такой путанице.
Текстовые и числовые присваивания (директивы EQU и =)
-----------------------------------------------------------------
Определения EQU, которые называются также приравниваниями
или присваиваниями, в улучшенном режиме всегда обрабатываются как
текст. В режиме MASM приравнивания иногда обрабатываются, как
текст, а в других случаях - как числа. Рассмотрим следующие при-
меры:
; Объявить несколько приравниваний
A = 4
B = 5
C EQU B + A
B = 6
; Описать переменную
V DW C ; 9 в режиме MASM, 10 в
; улучшенном режиме
MASM вычисляет выражение B + A при обработке выражения EQU.
TASM2 #3-5/Док = 183 =
В этот момент A равно 4, а B равно 5, таким образом, C равно 9. В
улучшенном режиме это же выражение обрабатывается по-другому, за-
писывая в текстовом виде все, что следует за директивой EQU (в
данном случае B + A). Впоследствии в улучшенном режиме выполняет-
ся подстановка этой строки (когда встречается C). В данном приме-
ре, поскольку вычисление выражение откладывается до описания пе-
ременной V, и так как B ранее было переопределено, как 6, то в
улучшенном режиме V будет равно 10 (6+4).
В улучшенном режиме директива EQU всегда определяет строку.
Знак равенства (=) всегда определяет вычисляемое выражение. Это
правило легко усвоить, если вы запомните, что символ равенства
(=) приводит к немедленному вычислению выражений, а директива EQU
откладывает вычисление выражения до того момента, когда встретит-
ся имя константы. Иногда это называют "ранним" и "поздним" прис-
ваиванием.
Выражения и операнды
-----------------------------------------------------------------
Основное различие между выражениями улучшенного режима и ре-
жима MASM состоит в функции, которую выполняют квадратные скобки.
В улучшенном режиме квадратные скобки всегда относятся к содержи-
мому заключаемой в них величины. Квадратные скобки никогда не
приводят в подразумеваемому сложению. Однако многие стандартные
конструкции MASM в улучшенном режиме не допускаются.
Операция квадратных скобок []
-----------------------------------------------------------------
В улучшенном режиме квадратные скобки должны использоваться
для получения содержимого элемента. Например:
mov ax,wordptr
приводит к выводу предупреждающего сообщения, если вы пытаетесь
загрузить указатель (wordptr) в регистр (AX). Корректной будет
следующая форма:
mov ax,[wordptr]
Здесь ясно, что вы загружаете содержимое ячейки, адресуемой
с помощью wordptr (в текущем сегменте данных DS) в регистр AX.
TASM2 #3-5/Док = 184 =
Если вы хотите сослаться на смещение идентификатора в сег-
менте, то вы должны явно использовать операцию OFFSET, например:
mov ax,OFFSET wordptr
Примеры операндов
-----------------------------------------------------------------
Давайте рассмотрим несколько часто приводящих к путанице, но
являющихся типичными, заключенных в квадратные скобки операндов,
которые воспринимает режим MASM, а затем сравним эти примеры с
корректными и более легкими для восприятия формами, которые ис-
пользуются в улучшенном режиме Турбо Ассемблера. Как вы увидите,
четкое использование квадратных скобок в улучшенном режиме, иск-
лючающее всякую двусмысленность, приводит к полной определенности
того, что вы намеревались сделать.
mov ax,[bx][si] ; режим MASM
В улучшенном режиме это приводит к синтаксической ошибке.
Если квадратные скобки определяют содержимое памяти, то эта инс-
трукция, очевидно, загружает значение, адресуемое с помощью ре-
гистра BX, плюс значение, адресуемое с помощью регистра SI. Ко-
нечно, вы можете предполагать сделать не это. Возможно вы предпо-
лагаете сделать следующее (и этого требует улучшенный режим):
mov ax,[bx+si] ; улучшенный режим
Теперь данная инструкция понятна. Содержимое ячейки памяти
по адресу OFFSET BX+SI, относительно текущего сегмента данных,
адресуемого с помощью регистра DS, загружается в регистр AX.
(Размер ячейки памяти представляет собой 16-битовое слово, так
как AX - это 16-битовый регистр. Если вы замените AX AL, или дру-
гим 8-битовым регистром, то ячейка памяти будет иметь размер в
байт.) Вот аналогичный пример:
mov ax,es:[bx][si] ; режим MASM
В улучшенном режиме это также приводит к ошибке. Эта инс-
трукция выглядит так, как будто в ней говорится следующее: "при-
менить префикс переопределения сегмента ES: к значению, адресуе-
мому с помощью регистра BX, и сложить все это с содержимым ячейки
памяти, адресуемой с помощью SI, загружая результат в регистр
AX". Это, конечно, бессмысленно, и вы, вероятно, подразумевали
следующее:
TASM2 #3-5/Док = 185 =
mov ax,[es:bx+si] ; улучшенный режим
Прекрасно! При этом складывается содержимое регистров AX и
SI, что дает значение смещения относительно регистра ES, переоп-
ределенного из используемого по умолчанию сегмента данных DS. 16-
битовое содержимое этой ячейки памяти загружается в регистр AX.
Вот еще один часто встречающийся в Ассемблере пример:
mov ax,6[bx] ; режим MASM
Математики могут подумать, что здесь значение ячейки памяти,
адресуемой с помощью регистра BX, умножается на 6. Или это это
какой-то неописанный метод индексации массива? А может быть прос-
то опечатка? В действительности, как показывает форма улучшенного
режима, это означает следующее:
mov ax,[bx+6] ; улучшенный режим
Конечно! Вы хотите загрузить в регистр AX содержимое ячейки
в текущем сегменте данных, отстоящей на 6 байтов от смещения, за-
данного регистром BX. Яснее не скажешь. Однако в режиме MASM вы-
ражения не всегда имеют такой понятный вид:
mov ax,es:[bp+8][si+6] ; режим MASM
Предположим, вы берете значение, отстоящее на 8 байтов от
BP, применяете префикс переопределения сегмента ES:, и... Нет,
переопределение должно применяться к значению, отстоящему на 6
байтов от SI. Нет, это не так, вероятно нужно взять значение по
адресу BP+8, сложить с содержимым [SI+6], применить префикс
переопределения... Однако оставим это. Улучшенный режим позволяет
проще записывать и читать такие сложные операнды:
mov ax,[es:bp+si+14]
Очевидно, значение, расположенное по смещению BP+SI+14 в
сегменте ES загружается в регистр AX. Это просто и понятно. Пой-
дем дальше:
mov al,byte ptr [bx] ; режим MASM
MASM очевидно позволяет вам задать содержимое ячеек памяти с
помощью байтовых указателей, по крайней мере в этой инструкции
вероятно делается именно это. Вы можете, конечно, ссылаться на
байты или слова только с помощью указателей (регистров и меток),
TASM2 #3-5/Док = 186 =
что в улучшенном режиме становится достаточно очевидным:
mov al,[byte ptr bx] ; улучшенный режим
Очевидно, вы указывате Турбо Ассемблеру, что BX - это байто-
вый указатель, загружая в регистр AL байт, отстоящий на BX байтов
от начала текущего сегмента данных. Приведем для полноты картины
еще один пример:
rep movs byte ptr [di],[si] ; режим MASM
MASM очевидно позволяет вам преобразовывать символы, адресу-
емые с помощью регистра DI (и возможно SI?) в байтовые указатели?
Конечно, вы этого сделать не можете. Как это можно видеть в улуч-
шенном режиме, это должно означать следующее:
rep mov [byte ptr di],[byte ptr si] ; улуч-
; шенный режим
Хотя такая запись и длиннее, ясно, что регистры DI и SI -
это байтовые указатели для инструкции MOVS.
Конечно, эти примеры не полные, и вы, вероятно, найдете в
MASM много других приводящих к путанице операндов с квадратными
скобками. Когда это происходит, попытайтесь перейти в улучшенный
режим, хотя бы для одной инструкции. Затем используйте примеры и
перепишите инструкцию в таком виде, который вы можете понять. Вы-
полнив это, вы можете использовать улучшенный режим не только для
того, чтобы писать более понятные и читаемые программы, но и для
того, чтобы лучше понять конструкции, в которых используются
квадратные скобки, которые в MASM выглядят весьма туманно.
Операции
-----------------------------------------------------------------
Изменения, внесенные в операции выражений в улучшенном режи-
ме, позволяют повысить мощность и гибкость некоторых операций,
оставив без изменения общее поведение выражений. Для того, чтобы
содействовать некоторым комбинациям операций, изменен порядок
старшинства некоторых операций. (См. Главу 2 в "Справочном руко-
водстве", где приводится таблица старшинства операций и дается
полное описание всех операций режима MASM и улучшенного режима.)
Точки в элементах структуры
TASM2 #3-5/Док = 187 =
-----------------------------------------------------------------
Аккуратно задавайте точку (.) в элементах структуры, на ко-
торые вы ссылаетесь. Операция точки для элементов структур в
улучшенном режиме является более строгой. Выражение слева от точ-
ки должно представлять собой имя элемента структуры. Используя
уже приводимые ранее примеры структур SomeStuff и OtherStuff,
загрузим теперь в регистры значения конкретных элементов струк-
тур:
; Опишем переменные с помощью структурных типов
S_Stuff SomeStuff <>
O_Stuff OtherStuff <>
mov ax,[S_Stuff.Amount] ; загрузить значение размером в
; слово
mov bl,[O_Stuff.Amount] ; загрузить значение размером в
; байт
TASM2 #3-5/Док = 188 =
Указатели на структуры
-----------------------------------------------------------------
Часто вам может потребоваться использовать регистр, содержа-
щий адрес структуры, другими словами, смещение первого байта
структуры в памяти. Либо у вам может быть переменная в памяти, с
помощью которой можно адресоваться к структуре. В этих случаях
для ссылки на конкретный элемент структуры по имени вы должны со-
общить Турбо Ассемблеру, на какую структуру вы ссылаетесь:
mov cx,[(SomeStuff ptr bx).Amount)
Это позволяет Турбо Ассемблеру определить, что регистр BX яв-
ляется указателем на SomeStaff, и что вы хотите загрузить содер-
жимое поля Amount из структуры в регистр CX. Здесь требуется ука-
зывать скобки, так как операция точки имеет более высокий приори-
тет, чем операция PTR. Без скобок в улучшенном режиме делается
попытка связать с BX Amount, что, конечно, невозможно, так как
регистры не имеют имен полей. Имена полей имеют только структуры,
таким образом, вы должны перед ссылкой на поля структуры, которые
адресуются с помощью регистров, вы должны преобразовать указатели
на структуры.
TASM2 #3-5/Док = 189 =
Операция SYMTYPE
-----------------------------------------------------------------
Так как в улучшенном режиме Турбо Ассемблера идентификатор
не может начинаться с точки, операция .TYPE MASM переименована в
улучшенном режиме в SYMTYPE (см. Главу 1 "Справочного руководс-
тва"). Несмотря на это изменение в имени, директива в обоих режи-
мах работает одинаково, возвращая значение, показывающее типы
различных идентификаторов:
Abyte DB 0
Aword DW 0
Array DD 10 DUP (8)
Btype = SYMTYPE Abyte ; 1
Wtype = SYMTYPE Aword ; 2
Atype = SYMTYPE Array ; 4
Операции HIGH и LOW
-----------------------------------------------------------------
В улучшенном режиме операции HIGH и LOW имеют два значения.
Обычно операция HIGH определяет старший (более значащий) байт
константы, а LOW определяет младший (менее значащий) байт, напри-
мер:
MaxVal = 1234h
mov ah, HIGH MaxVal ; загружает в AH 12h
mov ah, LOW MaxVal ; загружает в AH 34h
В улучшенном режиме операции HIGH и LOW можно использовать
для выбора старшей или младшей части выражения, где имеется ссыл-
ка на память:
WordVal DW 0
DbVal DD 0
QVal DQ 0
mov bl, [BYTE LOW WordVal]
mov ax, [WORD HIGH DbVal]
mov ax, [WORD LOW QVal]
Первая инструкция MOV загружает в регистр BL младший байт
двухбайтового слова с меткой WordVal. Вторая инструкция MOV заг-
ружает в AX старшее слово 4-байтового значения, записанного в
DbVal. Третья инструкция MOV загружает в регистр AX младшее слово
8-байтового (четверное слово) значения в QVal. Заметим, что син-
TASM2 #3-5/Док = 190 =
таксис здесь тот же, что и для операции PTR: ключевые слова BYTE
или WORD указываются перед операциями LOW или HIGH, плюс выраже-
ние со ссылкой на память.
Вы можете также использовать операции LOW и HIGH совместно
для выделения нужной вам информации из значения, состоящего из
нескольких байтов:
DbVal DD 12345678h
mov al,[BYTE LOW WORD HIGH DVal] ; загружает
; 34h в AL
В сочетании с BYTE и WORD ключевые слова LOW и HIGH выделяют
байты и слова из любой позиции в переменной. Здесь DVal - это
двойное слово 4-байтовой величины. Чтобы лучше понять сложные
комбинации, аналогичные приведенной, читайте выражение слева-нап-
раво. В этом случае инструкция MOV загружает в AL "младший байт
(LOW BYTE) старшего слова (WORD HIGH) значения DVal".
Необязательная операция PTR
-----------------------------------------------------------------
В выражениях можно использовать сокращенные переопределения
указателей. Чтобы сделать это, можно опустить операцию PTR. Нап-
ример:
[BYTE PTR OverTheRainbow]
В улучшенном режиме сокращение будет выглядеть так:
[BYTE OverTheRainBow]
Операция SIZE
-----------------------------------------------------------------
Операция SIZE в улучшенном режиме сообщает о действительном
числе байт, занимаемых элементом данных. Это позволяет легко оп-
ределять длину строк:
theTitle DB "The Sun Also RIses"
TheAuthor DB "Ernest Heminhway", 0
titleSize = Size theTitle ; IDEAL--18, MASM--1
authorSize = Size theAutor ; IDEAL--16, MASM--1
TASM2 #3-5/Док = 191 =
В данном примере theTitle и theAutor - это строки. В режиме
MASM операция SIZE присваивает длину (LENGTH) имени, умноженную
на ее тип (TYPE). Длина равна числу выделенных элементов данных,
в данном случае 1 (хотя строка и состоит из множества символов,
LENGTH на основании директивы DB рассматривает строки, как одно-
байтовые элементы). Значение TYPE для DB также равно 1. В итоге в
режиме MASM и titleSize, и authorSize, равны 1, что вряд ли может
помочь нам вычислить длину строки.
В улучшенном режиме операция SIZE возвращает число байт, за-
нимаемых первым элементом после директив выделения памяти (таких
как DB или DW). Поэтому titleSize равно числу символов в
theTitle. Аналогично, значение autorSize равно числу символов в
строке theAuthor. Заметим, однако, что theAuthor завершается ну-
левым байтом, отмечающим конец строки. Операция SIZE этот байт не
учитывает, возвращая только число символов в предшествующей стро-
ке. Фактически, операция SIZE возвращает длину только первого
элемента в любом списке из множества значений. Например:
CountDown DB 9,8,7,6,5,4,3,2,1,"Blast off"
TwoLines DB "First line",13,10,"Second line"
CDsize SIZE CountDown ; 1
TLsize SIZE TwoLines ; 10
Здесь CountDown адресуется к 9-байтовым значениям, следующим
за строкой "Blast off". Несмотря на это, размер CountDown
(CDsize) как в режиме MASM, так и в улучшенном режиме, будет ра-
вен 1, то есть размеру первого элемента в списке. Для второго
примера, TwoLines (который представляет собой типичный способ
хранения двух строк, которые разделены символами возврата каретки
(13) и перевода строки (10)) это не так. Две строки помечены в
программе одним именем TwoLines. Операция SIZE будет снова возв-
ращать размер первого элемента последовательности, в данном слу-
чае строки "First line". В улучшенном режиме TLSize равно 10,
числу символов в строке. В режиме MASM значение TLSIZE равно 1,
размеру первого элемента DB, то есть одному байту (символу).
TASM2 #3-5/Док = 192 =
Директивы
-----------------------------------------------------------------
Директивы в улучшенном режиме работают аналогично режиму MASM
и в большинстве случаев имеют те же имена. Однако между аналогич-
ными директивами в обоих режимах имеются важные отличия, что и
поясняется в данном разделе.
Управление листингом
-----------------------------------------------------------------
Поскольку в улучшенном режиме идентификатор не может начи-
наться с точки, все директивы управления листингом MASM начинают-
ся со знака %. Кроме того, чтобы более точно описывать управляе-
мые данными директивами операции, изменено несколько имен.
Директивы управления листингом в обоих режимах приведены в следу-
ющих таблицах.
------------------------------------------
Режим MASM Улучшенный режим
------------------------------------------
.CREF %CREF
.LALL %MACS
.LFCOND %CONDS
.LIST %LIST
.SFCOND %NOCONDS
.XALL %NOMACS
.XCREF %NOCREF
.XLIST %NOLIST
------------------------------------------
Так как в улучшенном режиме все директивы управления листин-
гом начинаются с символа %, директива %OUTS режима MASM преобра-
зована в директиву DISPLAY.
DISPLAY "Запуск драйвера ввода-вывода Ассемблера"
TASM2 #3-5/Док = 193 =
Директивы, начинающиеся с точки
-----------------------------------------------------------------
Другие директивы MASM, которые начинаются с символа точки
(.), для ясности переименованы. Например, все директивы управле-
ния процессором, например .286, которые выглядят скорее как чис-
ла, а не как директивы, начинаются теперь с символа P, например
P286N. Все директивы вывода сообщения об ошибке вида .ERRxxx пе-
реименованы в ERRIFxxx. Некоторые другие директивы называются по
прежнему, но в них отсутствует первая точка.
В следующей таблице приведен список всех директив, начинаю-
щихся с точки в режиме MASM, и их эквиваленты в улучшенном режи-
ме.
---------------------------------------------
Режим MASM Улучшенный режим
---------------------------------------------
.186 P186
.286 P286N
.286C P286N
.286P P286
.287 P287
.386 P386N
.386C P386N
.386P P386
.387 P387
.8086 P8086
.8087 P8087
.CODE CODESEG
.CONST CONST
.DATA DATASEG
.DATA? UDATASEG
.ERR ERR
.ERR1 ERRIF1
.ERR2 ERRIF2
.ERRB ERRIFB
.ERRDEF ERRIFDEF
.ERRDIF ERRIFDIF
.ERRDIFI ERRIFDIFI
.ERRE ERRIFE
.ERRIDN ERRIFIDN
.ERRIDNI ERRIFIDNI
.ERRNB ERRIFNB
.ERRNDEF ERRIFNDEF
.ERRNZ ERRIF
TASM2 #3-5/Док = 194 =
.FARDATA FARDATA
.FARDATA? UFARDATA
.MODEL MODEL
.RADIX RADIX
.STACK STACK
---------------------------------------------
Обратный порядок имени директивы и идентификатора
-----------------------------------------------------------------
В улучшенном режиме порядок синтаксического анализа проще,
чем в MASM. Если первая лексема представляет собой ключевое сло-
во, то она определяет операцию, которая должна выполняться дирек-
тивой. Если первая лексема - это не ключевое слово, то операцию
определяет вторая лексема.
В соответствии с этим изменением имеют обратных порядок име-
ни идентификатора и директивы. Подробнее это можно увидеть в сле-
дующей таблице:
------------------------------------------------------
Режим MASM Улучшенный режим
------------------------------------------------------
имя ENDP ENDP[имя]
имя ENDS ENDS[имя]
имя GROUP сегменты GROUP имя сегменты
имя LABEL тип LABEL имя тип
имя MACRO аргументы MACRO имя аргументы
имя PROC тип PROC имя тип
имя RECORD аргументы RECORD имя аргументы
имя SEGMENT аргументы SEGMENT имя аргументы
имя STRUC STRUC имя
имя UNION UNION имя
------------------------------------------------------
Заметим, что ENDS и ENDP не требуют указания соответствующих
имен при закрытии определения. Если вы указываете имя, задавайте
его таким же, как в предшествующей директиве SEGMENT или PROC.
Некоторые программисты всегда включают имена, чтобы улучшить чи-
таемость программы. Это особенно полезно при использовании вло-
женных процедур или сегментов. Это особенно полезно делать при
использовании вложенных сегментов или процедур, но не является
обязательным.
Некоторые директивы в режиме MASM и в улучшенном режиме
TASM2 #3-5/Док = 195 =
идентичны. Например, следующие директивы определяют идентификато-
ры, как часть синтаксиса языка, и таким образом в обоих режимах
совпадают.
= DD DQ
: DF DT
DB DP DW
EQU
TASM2 #3-5/Док = 196 =
Заключенные в кавычки строки, являющиеся аргументами директив
-----------------------------------------------------------------
Директива INCLUDE в улучшенном режиме воспринимает в качест-
ве аргумента заключенное в кавычки имя файла:
INCLUDE "MYDEFS.INC"
В режиме MASM кавычки не требовались:
INCLUDE MYDEFS.INC
Директивы %TITLE и %SUBTTL также требуют, чтобы указываемые
в них строки заключались в кавычки:
%TITLE "Макроопределения" ; комментарий игнорируется
%SUBTTL "Блок структурированных макрокоманд" ; коммента-
; рий игнорируется
Как показывают эти примеры, требования указывать кавычки, в
которые заключаются заголовки и подзаголовки, позволяют вам до-
бавлять в конце этих строк комментарии. В файл листинга эти ком-
ментарии не включаются. В режиме MASM все что следует за директи-
вами .TITLE и .SUBTTL становится частью строки заголовка (включая
любые комментарии).
Сегменты и группы
-----------------------------------------------------------------
Способ, с помощью которого Турбо Ассемблер обрабатывает в
улучшенном режиме сегменты и группы, может внести большое разли-
чие в формирование и выполнение программ. Как и большинству лю-
дей, вам, вероятно, не захочется ломать голову над ошибками, воз-
никающими из-за взаимодействия сегментов и групп.
Большинство трудностей в этом процессе возникает из-за про-
извольного характера предположений в MASM (и следовательно Турбо
Ассемблером в режиме MASM) о ссылках на данные или код в группах.
К счастью, улучшенный режим сглаживает некоторые наиболее явные
проблемы, которые могут вызвать директивы определения сегментов и
групп в MASM. Об этом мы и расскажем далее.
Доступ к данным в сегменте, принадлежащем группе
-----------------------------------------------------------------
TASM2 #3-5/Док = 197 =
В улучшенном режиме любой элемент данных в сегменте, являю-
щемся частью группы, рассматривается строго как элемент группы,
а не сегмента. В Турбо Ассемблере для распознавания элемента дан-
ных, как элемента сегмента, нужно использовать явное переопреде-
ление сегмента.
В режиме MASM это интерпретируется по-другому. Иногда иден-
тификаторы считается частью сегмента, а не частью группы. В част-
ности, в режиме MASM идентификатор интерпретируется, как часть
сегмента, когда он используется, как указатель на выделенные дан-
ные. Это может вызвать путаницу, так как когда вы непосредственно
обращаетесь к данным без операции OFFSET, MASM некорректно гене-
рирует ссылку на сегмент вместо ссылки на группу.
Пример поможет нам пояснить, как легко можно нажить неприят-
ности из-за специфики адресации в MASM. Рассмотрим следующую не-
полную программу MASM, в которой описываются три сегмента данных:
dseg1 SEGMENT para public 'data'
v1 db 0
dseg1 ENDS
dseg2 SEGMENT para public 'data'
v2 db 0
dseg2 ENDS
dseg3 SEGMENT para public 'data'
v3 db 0
dseg3 ENDS
DGROUP GROUP dseg1,dseg2,dseg3
cseg SEGMENT para public 'code'
ASSUME cs:cseg,ds:DGROUP
start:
mov ax,OFFSET v1
mov bx,OFFSET v2
mov cx,OFFSET v3
cseg ENDS
END start
Три сегмента dseg1, dseg2 и dseg3 группируются под одним
именем DGROUP. В результате все переменные отдельных сегментов
хранятся в памяти вместе. В исходном тексте программы в каждом из
TASM2 #3-5/Док = 198 =
отдельных сегментов описывается байтовая переменная (метки v1, v2
и v3).
В коде данной программы MASM в регистры AX, BX и CX загружа-
ются смещения адресов этих переменных. В соответствии с предшест-
вующей директивы ASSUME и из-за того, что сегменты данных сгруп-
пированы вместе, вы можете подумать, что MASM будет вычислять
смещения переменных относительно всей группы, в которой перемен-
ные очевидно хранятся в памяти.
Но произойдет совсем не это! Вопреки вашим намерениям MASM
вычисляет смещения переменных относительно отдельных сегментов
dseg1, dseg2 и dseg3. Он делает это несмотря на то, что все три
сегмента данных сгруппированы в памяти в один сегмент данных, ад-
ресуемый через регистр DS. Бессмысленно определять смещения пере-
менных относительно отдельных сегментов в тексте программы, ког-
да эти сегменты скомбинированы в памяти в один сегмент.
Единственный способ ссылки на такие переменные состоит в ссылке
на их смещения относительно всей группы.
Чтобы избавиться в MASM от этой проблемы, вам потребуется
наряду с ключевым словом OFFSET задавать имя группы:
mov ax,OFFSET DGROUP:v1
mov bx,OFFSET DGROUP:v2
mov cx,OFFSET DGROUP:v3
Хотя теперь это ассемблируется корректно и загружаются сме-
щения переменных v1, v2 и v3 относительно DGROUP (где собраны от-
дельные сегменты), вы можете легко забыть задать квалификатор
DGROUP. Если вы сделаете эту ошибку, значения смещений не позво-
лят корректно определить переменные в памяти, и вы не получите в
MASM никакого указания, что что-то произошло не так. Улучшенный
режим позволяет избежать таких неприятностей:
IDEAL
SEGMENT dseg1 para public 'data'
v1 db 0
ENDS
SEGMENT dseg2 para public 'data'
v2 db 0
ENDS
SEGMENT dseg3 para public 'data'
v3 db 0
TASM2 #3-5/Док = 199 =
ENDS
GROUP DGROUP dseg1,dseg2,dseg3
SEGMENT cseg para public 'code'
ASSUME cs:cseg,ds:DGROUP
start:
mov ax,OFFSET v1
mov bx,OFFSET v2
mov cx,OFFSET v3
ENDS
END start
Смещения переменных v1, v2 и v3 корректно вычисляются отно-
сительно группы, в которой собраны отдельные сегменты, которым
принадлежат переменные. В улучшенном режиме квалификатор DGROUP
для ссылки на переменные в сегментах группы не требуется. В режи-
ме MASM этот квалификатор также не является необходимым, но, что
хуже всего, не выдается никаких предупреждений, если вы забыли в
конкретной ссылке определить имя группы.
Определение в коде ближних и дальних меток
-----------------------------------------------------------------
При определении ближних или дальних идентификаторов в LABEL
или PROC ссылки на идентификатор делаются относительно группы, в
которой содержится сегмент. Если сегмент, где содержится иденти-
фикатор, не является частью группы, то идентификатор рассматрива-
ется относительно сегмента. Это означает, что вам не нужно ис-
пользовать директиву ASSUME CS для сегмента, чтобы определить
идентификаторы ближнего или дальнего типа. Если записать фрагмент
программы в режиме MASM:
CODE SEGMENT
ASSUME CS:CODE
XYZ PROC FAR
.
. ; код процедуры MASM
.
XYZ ENDP
CODE ENDS
то в улучшенном режиме он примет следующий вид:
TASM2 #3-5/Док = 200 =
CODE SEGMENT
PROC XYZ FAR
.
. ; код процедуры в улучшенном режиме
.
ENDP
ENDS
Эти изменения не добавляют новых возможностей по сравнению с
режимом MASM. Однако это позволяет освободить вас от необходимос-
ти указывать Ассемблеру то, что улучшенный режим может определить
сам.
Внешние, общедоступные и глобальные идентификаторы
-----------------------------------------------------------------
Всякий раз, когда вы должны задавать тип (BYTE, WORD и
т.д.), например в директивах EXTRN или GLOBAL, можно использовать
имя структуры:
STRUC MoreStuff
HisStuff DB 0
HerStuff DW 0
ItsStuff DB 0
ENDS
EXTRN SNAME:MoreStuff
Это свойство в сочетании с описанными ранее улучшениями в
операции точки позволяет вам ссылаться на элементы структур, ко-
торые являются внешними по отношению к вашему исходному модулю.
Это аналогично тому, как если бы вы определили элементы структур
в обоих модулях. Операция SIZE также корректно сообщает о размере
внешних структур данных. Каждый общедоступный символ в улучшенном
режиме определяется при задании директивы PUBLIC. Это полезно
также для переопределения переменных. В MASM все общедоступные
символы перечисляются в конце программы, что ограничивает спосо-
бы, с помощью которых вы можете переопределить символы. Например:
Perfect = 0
Public Perfect ; объявить Perfect общедоступным
Perfect = 10 ; переопределить значение Perfect
В режиме MASM PUBLIC Perfect равно 8, хотя в модуле Perfect
переопределяется после описания PUBLIC. В улучшенном режиме, пос-
TASM2 #3-5/Док = 201 =
кольку общедоступными идентификаторы становятся в конце модуля,
другой модуль, который импортирует этот идентификатор с помощью
описания EXTRN, получает значение Perfect 10.
TASM2 #3-5/Док = 202 =
Другие отличия
-----------------------------------------------------------------
В данном разделе описывается несколько дополнительных отли-
чий режима MASM от улучшенного режима.
Подавление корректировок
-----------------------------------------------------------------
В улучшенном режиме Турбо Ассемблер не генерирует сегментных
корректировок для частных сегментов (private), которые выравнива-
ются на границу страницы или параграфа. Так как компоновщик не
требует подобных корректировок, при ассемблировании программ в
улучшенном режиме может быть получен объектный код меньшего объ-
ема, компоновку которых компоновщик выполняет более быстро, чем
компоновку объектных файлов, сгенерированных в режиме MASM. Далее
показывается, каким образом такие корректировки производятся в
режиме MASM (но не в режиме IDEAL):
SEGMENT DATA PRIVATE PARA
VAR1 DB 0
VAR2 DW 0
ENDS
SEGMENT CODE
ASSUME ds:DATA
mov ax,VAR2 ; корректировок не требуется
ENDS
Это различие не влияет на код, который вы пишете. Здесь об
этом упоминается только для вашего сведения.
Операнд инструкции BOUND
-----------------------------------------------------------------
В инструкции BOUND требуется указывать операнд типа WORD
(слово), а не DWORD (двойное слово). Это позволяет вам определить
нижнюю и верхнюю границу в виде двух констант размером в слово,
что устраняет необходимость преобразования операнда в DWORD явным
образом (с помощью DWORD PTR). В режиме MASM вы должны записы-
вать:
BOUNDS DW 1,4 ; нижняя и верхняя границы
BOUND DWORD PTR BOUNDS ; требуется в режиме MASM
TASM2 #3-5/Док = 203 =
Однако в улучшенном режиме требуется только записать:
BOUNDS DW 1,4 ; нижняя и верхняя границы
BOUND [BOUNDS] ; допускается в улучшенном
; режиме
Комментарии в макрокомандах
-----------------------------------------------------------------
В улучшенном режиме комментарии в макрокомандах интерпрети-
руются, как строки. Чтобы подставить в макрокомментарий пустой
параметр, вы должны указать перед параметром амперсанд (&):
MACRO DOUBLE ARC
SHL arg,1 ; умножить &ARG на 2
ENDM
Когда эта макрокоманда вызывается в улучшенном режиме с по-
мощью вызова DOUBLE BX, то в файле листинга выведется:
SHL bx,1 ; умножить BX на 2
С другой стороны, если макрокоманда определена следующим об-
разом:
MACRO DOUBLE ARC
SHL arg,1 ; умножить ARG на 2
ENDM
то в файле листинга ARG заменено не будет:
SHL bx,1 ; умножить ARG на 2
TASM2 #3-5/Док = 204 =
Локальные идентификаторы
-----------------------------------------------------------------
Использование возможностей локальных идентификаторов Турбо
Ассемблера при переходе в улучшенный режим разрешается автомати-
чески (точно также, как если бы вы указали директиву LOCALS).
Сравнение программирования в режиме MASM и в улучшенном режиме
-----------------------------------------------------------------
Чтобы логически завершить данную главу и дать вам оконча-
тельное целостное представление о режиме MASM и улучшенном режи-
ме и различиях между ними, мы приведем пример одной и той же
программы, написанной для режиме MASM и для улучшенного режима.
Изучив эти примеры и прочитав комментарии в листингах, вы сможете
оценить преимущества, предоставляемые синтаксисом улучшенного ре-
жима.
Учтите только, что данные программы не являются примерами
хорошего стиля программирования: инструкции просто показывают
концепции улучшенного режима, о которых говорилось в данной гла-
ве, и использованы только в качестве образца для демонстрации на-
иболее общих возможностей улучшенного режима и его отличий от ре-
жима MASM.
Программа данного примера считывает строку с экрана, преоб-
разует текст в прописные буквы (верхний регистр), и перед тем,
как вернуться в DOS, выводит результат на экран. Чтобы отметить,
где исходный код данной программы различается в режимах MASM и в
улучшенном режиме (IDEAL), мы добавили в текст программы нумеро-
ванные комментарии (которые начинаются с точки с запятой). Напри-
мер, комментарий ; #4 показывает вам, что нужно читать соответс-
твующее описание с номером 4, следующее за листингом в разделе
"Анализ режима MASM и улучшенного режима". Кроме того, для выде-
ления отличий улучшенного режима, мы исключили из его примеров
большинство комментариев. Чтобы понять, как работает данный при-
мер, прочитайте первую программу. Для сравнения и выявления улуч-
шений улучшенного режима прочитайте также вторую программу.
Пример программы в режиме MASM
-----------------------------------------------------------------
; Файл
; Пример программы преобразования строки в прописные буквы
; в режиме MASM
TASM2 #3-5/Док = 205 =
TITLE Example MASM Program ; этот комментарий включает-
; ся в заголовок
.286
DataSize = 128 ; размер буферов ввода-
; вывода
dosint MACRO intmun
mov ah,intnum ; присвоить номер FN AH
int 21h ; вызвать функцию DOS
; &INTNUM
ENDM
stk SEGMENT STASK
db 100h DUP (?) ; зарезервировать
; пространство в стеке
stk ENDS
data SEGMENT WORD
inbuf db bufsize DUP (?) ; буфер ввода
outbuf db bufsize DUP (?) ; буфер вывода
data ENDS
DGROUP GROUP stk,data ; сгруппировать сегменты
; стека и данных
code SEGMENT WORD
ASSUME cs:code ; положим, CS - это
; сегмент кода
start:
mov ax,DGROUP ; присвоить адрес сегмента
mov ds,ax ; DGROUP DS
ASSUME DS:DGROUP ; по умолчанию сегментом
; данных будет DS
mov dx,OFFSET DGROUP:inbuf ; загрузить в DX
; смещение inbuf (буфер
; ввода)
xor bx,bx ; стандартный ввода
call readline ; считать одну строку
mov bx,ax ; присвоить BX длину
mov inbuf[bx],0 ; добавить завершающий
; нулевой символ
push ax ; сохранить регистр AX
; в стеке
call mungline ; преобразовать строку
; в верхний регистр
pop cx ; восстановить счетчик
mov dx,OFFSET DGROUP:outbuf ; загрузить в AX
TASM2 #3-5/Док = 206 =
; смещение outbuf (буфер
; вывода)
mov bx,1 ; стандартный вывод
dosint 40h ; функция записи файла
dosint 4ch ; выход в DOS
; Считать строку (dx => буфер), возвратить в AX значение
; счетчика
readline PROC near
mov cx,bufsize ; задать размер буфера
dosint 3fh ; функция чтения файла
and ax,ax ; установить для счетчика
; флаг нуля
ret ; возврат в вызывающую
; программу
readline ENDP
; Преобразовать строку в верхний регистр
mungline PROC NEAR
mov si,OFFSET DGROUP:inbuf ; адресация к inbuf
; через SI
mov di,0 ; инициализировать DI
@@loop:
cmp BYTE PTR[si],0 ; конец текста?
je @@done ; если да, то перейти
; на @@done
mov al,[si] ; иначе получить следующий
; символ
and al,not 'a' - 'A' ; преобразовать в верхний
; регистр
mov outbuf[di],al ; записать в буфер вывода
inc si ; лучше использовать lodsb,
; stosb ...
inc di ; это просто пример!
jmp @@loop ; продолжить преобразование
; текста
@@done: ret
mungline ENDP ; конец процедуры
code ENDS ; конец сегмента
END start ; конец текста и точка
; входа DOS
Пример программы в улучшенном режиме
-----------------------------------------------------------------
; Файл
TASM2 #3-5/Док = 207 =
; Пример программы преобразования строки в прописные буквы
; в улучшенном режиме
IDEAL ; #1
#TITLE Example MASM Program "пример режиме IDEAL" ; #2
улучшенном режиме
P286N ; #3
BufSize = 128
MACRO dosint intmun ; #4
mov ah,intnum
int 21h
ENDM
SEGMENT stk STASK ; #5
db 100h DUP (?)
ENDS ; #6
SEGMENT DATA WORD ; #7
inbuf db bufsize DUP (?)
outbuf db bufsize DUP (?)
ENDS DATA ; #8
GROUP DGROUP stk,data ; #9
SEGMENT CODE WORD ; #10
ASSUME cs:code
start:
mov ax,DGROUP
mov ds,ax
ASSUME DS:DGROUP
mov dx,OFFSET inbuf ; #11
xor bx,bx
call readline
mov bx,ax
mov [inbuf + bx],0 ; #12
push ax
call mungline
pop cx
mov dx,OFFSET outbuf ; #13
mov bx,1
dosint 40h
dosint 4ch
; Считать строку (dx => буфер), возвратить в AX значение
; счетчика
PROC readline near ; #14
TASM2 #3-5/Док = 208 =
mov cx,bufsize
dosint 3fh
and ax,ax
ret
ENDP ; #15
; Преобразовать строку в верхний регистр
PROC mungline NEAR ; #16
mov si,OFFSET inbuf ; #17
mov di,0
@@loop:
cmp [BYTE si],0 ; #18
je @@done
mov al,[si]
and al,not 'a' - 'A'
mov [outbuf + di],al ; #19
inc si
inc di
; LODSB/STOSB
jmp @@loop
@@done: ret
ENDP mungline ; #20
ENDS ; #21
END start
Анализ режима MASM и улучшенного режима
-----------------------------------------------------------------
Далее подробно описываются различия между конструкциями, ди-
рективами и операндами режима MASM и улучшенного режима в двух
предыдущих программах. Номера относятся к комментариям программы
для улучшенного режима. Сравните отмеченные комментариями строка-
ми с режимом MASM.
1. Используйте для перехода в улучшенный режим директиву
IDEAL. По умолчанию Турбо Ассемблер всегда начинает ас-
семблирование исходного файла в режиме MASM. Вам требует-
ся указывать директиву MASM только в том случае, когда вы
хотите переключиться обратно в режим MASM, перейдя ранее
в улучшенный режим.
2. Знак процента в директиве %TITLE напоминает вам, что эта
директива влияет на файл листинга (если вы решили создать
его, указав имя файла листинга или использовав параметр
TASM2 #3-5/Док = 209 =
командной строки /L при ассемблировании программы). В
улучшенном режиме используется директива %TITLE, а не
TITLE (без знака процента), и кроме того строку заголовка
требуется заключать в кавычки (""). Это позволяет вам
указать на данной строке комментарий, который в режиме
MASM стал бы частью заголовка.
3. Директива .286 режима MASM в улучшенном режиме принимает
вид P286N. Так как в улучшенном режиме идентификаторы не
могут начинаться с точки, все директивы назначения про-
цессора MASM и другие директивы, начинающиеся с точки,
изменены. Этот оператор в листинге не несет никакого по-
лезного смысла, он служит только для того, чтобы показать
различия между режимом MASM и улучшенным режимом. В дан-
ной программе инструкции процессора 80286 не используют-
ся.
4. В улучшенном режиме имя макрокоманды следует после дирек-
тивы MACRO, а не перед ней, как в режиме MASM.
5. Имя сегмента в директиве SEGMENT следует в улучшенном ре-
жиме за директивой.
6. В улучшенном режиме при использовании для закрытия сег-
мента ENDS вам не нужно указывать соответствующее имя
сегмента, как это требовалось в режиме MASM. Если вы хо-
тите, имя можно удалить (DATA).
7. То же, что и в 5. Перед именем также указывается ключевое
слово SEGMENT.
8. Если вы указываете в директиве ENDS соответствующее имя
сегмента, то это имя следует после директивы, а не перед
ней, как в режиме MASM.
9. В улучшенном режиме директива GROUP предшествует имени в
группе сегментов данных (DGROUP). Далее следует список
сегментов данных, которые вы объединяете под данным име-
нем. В MASM GROUP и имя зарезервированы.
10. Аналогично пункту 5. Ключевое слово SEGMENT предшествует
имени.
11. Здесь в операции OFFSET не нужно использовать квалифика-
тор группы. В улучшенном режиме INBUF рассматривается
относительно начала DGROUP, так как INBUF находится в од-
TASM2 #3-5/Док = 210 =
ном из сегментов, объединенных под данным именем группы.
В MASM, чтобы корректно определить смещения переменных в
сегментах группы, вам нужно было не забывать указывать
DGROUP:inbuf.
12. Операнд [INBUF+BX] допустим в обоих режимах, однако стро-
ку INBUF[BX] режима MASM в улучшенном режиме использовать
не допускается. В этом режиме все операнды со ссылкой на
память должны заключаться в квадратные скобки.
13. Аналогично пункту 11. Здесь вам также не нужно задавать
имя группы, чтобы зарезервировать переменную в сегменте
группы. В MASM, чтобы получить корректное смещение
OUTBUF, нужно было записывать: DGROUP:outbuf. Если забыть
о квалификаторе DGROUP, то в примере вывод был бы записан
в стек, при этом MASM не выдал бы никаких предупреждений
о неправильной работе.
14. Имя процедуры в директиве PROC следует после директивы, а
не перед ней, как в режиме MASM.
15. Когда вы в улучшенном режиме для завершения процедуры ис-
пользуете ENDP, вам не нужно указывать имя соответствую-
щей процедуры, как это нужно было делать в режиме MASM.
16. Аналогично пункту 14. Директива PROC предшествует имени
процедуры.
17. Аналогично пункту 11. Здесь вам также не нужно записывать
DGROUP:inbuf, как в MASM.
18. В улучшенном режиме при установке размера выражения вы
можете опускать oперацию PTR. Выражение режима MASM BYTE
PTR ABC идентично выражению BYTE ABC в улучшенном режиме.
19. Аналогично пункту 12. В улучшенном режиме при ссылке на
содержимое памяти нужно всегда заключать такое выражение
в квадратные скобки.
20. Имя процедуры можно (необязательно) указывать после ди-
рективы ENDP, а не перед ней, как в режиме MASM.
21. Аналогично пункту 6. В директиве ENDS не требуется указы-
вать имя соответствующего сегмента, хотя при желании вы
можете добавить это имя.
TASM2 #3-5/Док = 211 =
Литература
-----------------------------------------------------------------
CrawWord, John H., Patrick P. Gelsinger. Programming the 80386.
Alamedis: Sybex, Inc., 1987. (Книга по программированию для про-
цессора 80386).
Duncan, Ray. Advanced MS-DOS. Redmond: Microsoft Press, 1986.
(Развитое программирование для MS-DOS).
Lafore, Robert. Assembly Language Primer for the IBM PC & XT, New
York: The Waite Group, 1984. (Учебник по языку Ассемблера для
компьютеров IBM PC и XT).
Murray, William H., Chris Pappas. 80386/80286 Assembly Language
Programming. Berkeley: Osborne/McGraw-Hill, 1986. (Руководство по
программированию на языке Ассемблера для процессоров
80286/80386).
Norton, Peter, John Socha. Peter Norton's Assembly Lаnguage Book
for the IBM PC. New York: Brady Communications, 1986. (Руководс-
тва Нортона по программированию на языке Ассемблера).
Rector, Russel, George Alexy, The 8086 Book. Berkeley:
Osborne/McGraw-Hill, 1980. (Руководство по процессору 8086).
Sargent, Thomas P. An Introduction to Assembly Language
Programming for the 8086 Family. New York: John Willey & Sons,
Inc., 1985. (Введение в язык Ассемблера для процессоров серии
8086).
Turley, James L. Advanced 80386 Programming Techniques. Berkeley:
Osborne/McGraw Hill, 1988. (Развитые методы программирования на
Ассемблере для процессора 80386).
Wilton, Richard. Programmer's Guide to PC and PS/2 Video Systems.
Redmond: Microsoft Press, 1987. (Руководство программиста по ви-
деосистемам компьютеров PC и PS/2).
TASM2 #3-5/Док = 212 =
Приложение A. Интерфейс Турбо Ассемблера и Турбо Бейсика
-----------------------------------------------------------------
Программистам, работающим с Турбо Бейсиком, значительно об-
легчает жизнь совместимость (снизу вверх) Турбо Ассемблера с
Макроассемблером фирмы Microsoft. В данной главе мы приведем не-
которые примеры (как содержащиеся в руководстве по Турбо Бейсику,
так и другие) и покажем, как Турбо Ассемблер может расширить свои
возможности с помощью Турбо Бейсика.
Примечание: Когда мы говорим о Турбо Бейсике, это оз-
начает, что речь идет о версии 1.0 и выше.
В Турбо Бейсике предусмотрено три способа вызова подпрограм-
мы на Ассемблере:
1. Для вызова процедуры, содержащей встроенный код, вы мо-
жете использовать оператор CALL.
2. Для конкретной адресации к памяти можно использовать вы-
зов CALL ABSOLUTE:.
3. Для обработки прерывания процессора и перехода на под-
программы можно использовать вызов CALL INTERRUPT и
встроенную поддержку Турбо Бейсика.
При выборе того или иного метода нужно обеспечить сохранение
определенных регистров. Вызов CALL INTERRUPT отличается наимень-
шей требовательностью в этом плане: нужно обеспечивать сохранение
только регистров SS (сегмент стека) и SP (указатель стека). При
использовании других двух методов нужно сохранять также регистры
DS (сегмент данных) и BP (указатель базы).
Сохранение регистров (здесь и в предыдущем тексте термин
"сохранение регистров" означает на самом деле "сохранение регист-
ров перед вызовом (обычно в стеке) и восстановление их содержимо-
го после вызова подпрограммы или при ее завершении") не обяза-
тельно означает, что вы должны заносить все регистры в стек, хотя
это наиболее общепринятый способ обеспечения сохранности их со-
держимого. Простые подпрограммы могут не изменять никаких регист-
ров. В этом случае не нужно принимать никаких мер предосторожнос-
ти.
Мы говорим "могут" потому что лучше не делать никаких пред-
положений, особенно когда дело касается программирования на Ас-
семблере. Хотя в вашем руководстве по MS-DOS может специально
TASM2 #3-5/Док = 213 =
оговариваться, что конкретное прерывание не изменяет содержимого
указателя стека или указателя базы (или любого другого регистра),
это не всегда может иметь место. MS-DOS делает такие изменения, и
некоторые сочетания прерываний противоречат информации, приводи-
мой в руководстве по DOS. Поэтому лучше предохранить себя от воз-
можных неприятностей. Сохранение необходимых регистров не ухудшит
существенно производительности программы. При этом вы меньше рис-
куете и кроме того улучшите переносимость вашей программы на но-
вые версии MS-DOS.
Передача параметров
-----------------------------------------------------------------
Турбо Бейсик передает параметры подпрограммам на Ассемблере
в стеке. Все такие вызовы имеют дальний тип (FAR): последние 4
байта в стеке представляют собой адрес возврата, используемый
Турбо Бейсиком при завершении работы подпрограммы. Адресом перво-
го передаваемого подпрограмме параметра будет [SP+4]. Для каждого
заносимого в стек регистра к этому значению нужно добавить два.
Помните о том, что стек увеличивается в сторону младших адресов
(вниз).
Любая простая переменная (отличная от массива), передаваемая
в стеке, приведет к увеличению стека на 4 байта. Турбо Бейсик пе-
редает как сегмент (2 байта), так и смещение (2 байта) таких пе-
ременных. Как вы увидите далее, это дает важные преимущества.
Параметры, передаваемые по значению (такие как константы и
выражения), также занимают в стеке 4 байта. В этом случае значе-
ние в стеке не является значением выражения: это адрес той ячейки
в памяти, где значение хранится. Это может показаться усложнен-
ным, но дает два существенных преимущества. Во-первых, все под-
программы на Ассемблере могут обрабатывать передаваемые значения
одинаково. Во-вторых, подпрограмма, которая ошибочно модифицирует
значение параметра, передаваемого по значению или по ссылке, не
может изменить важную область памяти.
Таким образом, использование стека может улучшить стандарти-
зацию ваших программ. Следующий пример показывает, в чем состоит
это преимущество. Предположим, значение целой переменной %x равно
4, и у вас имеется подпрограмма на Ассемблере MYROUTINE, которая
воспринимает передаваемое целое значение. Эта подпрограмма будет
работать одинаково, если вы вызовете ее и оператором CALL MYROU-
TINE(%x), и оператором CALL MYROUTINE(4). Если подпрограмма была
вызвана с помощью CALL SUBROUTINE(4%) и пытается модифицировать
TASM2 #3-5/Док = 214 =
значение переданного параметра, то она без всякого ущерба модифи-
цировала бы область памяти, где временно хранится целое значение
4.
Заметим, что во втором случае тип был указан явно (%4). Это
не является абсолютно необходимым, хотя делать это неплохо. Если
бы Турбо Бейсик предположил, что значение 4 - это число с одинар-
ной точностью, то ваша подпрограмма использовала бы неверное зна-
чение (два байта из 4-байтового числа с одинарной точностью) и
выполнялась бы некорректно. Чтобы гарантировать передачу значения
корректного типа, лучше при каждом вызове подпрограммы указывать
тип переменных.
Если вы передаете подпрограмме массив, то стек увеличится на
60 байт (при этом большая часть переданной вам информации возмож-
но к делу не относится). В руководстве по Турбо Бейсику рекомен-
дуется передавать параметры типа массива, как целые значения, а
не передавать массив целиком. Передача вместо всего массива нес-
кольких выбранных параметров позволит сэкономить место на диске,
уменьшить время, необходимое для выполнения вашей программы, и
обеспечить переносимость вашей программы для работы с последующи-
ми версиями Турбо Бейсика.
Предположим, например, что у вас есть простая подпрограмма,
которой требуется занести в стек только указатель базы BP. В этом
случае значением или адресом первого параметра будет [SP+6]. Если
бы вы занесли в стек два регистра, то значением или адресом пер-
вого параметра было бы [SP+8].
Давайте предположим, что первый параметр представляет собой
целое значение и передается подпрограмме на Ассемблере по значе-
нию. В этом случае вы можете поместить целое значение в регистр
CX с помощью инструкций:
push bp ; сохранить указатель базы
mov bp,sp ; приравнять указатель базы к
; указателю стека
les di,[bp+6] ; ES содержит значение сегмента,
; DI - смещение значения
mov cx,ES:[DI] ; поместить значение в CX
Примечание: Значение не будет находиться в том же
сегменте, что и обычные переменные. Вы должны позаботиться
о том, чтобы для доступа к значению использовать коррект-
ный и полный адрес. Далее мы расскажем о переменных, кото-
рые не находятся в текущем сегменте данных, более подроб-
TASM2 #3-5/Док = 215 =
но.
С другой стороны, если бы вы знали, что целочисленная пере-
менная передана по ссылке, а не по значению, то [BP+6] содержал
бы адрес смещения переменной в ее сегменте данных. Чтобы помес-
тить это целое значение в регистр CX, можно было бы записать:
push bp ; сохранить указатель базы
mov bp,sp ; приравнять указатель базы
; к указателю стека
mov bx,[bp+6] ; поместить адрес значения в BX
mov cx,[bx] ; поместить переданное значение
; в CX
В данной программе предполагается, что переменная находится
в текущем сегменте данных, и для изменения значения переменной
необходимо только смещение переменной в этом сегменте.
Надежнее передавать параметры, если вы всегда будете предпо-
лагать, что передача осуществляется по значению. Если значение на
самом деле передается по ссылке, вы ничего не потеряете: полный
адрес переменной будет включать в себя текущий сегмент данных. С
другой стороны, если в вашей подпрограмме предполагается, что пе-
ременная передавалась по ссылке, а это не так, то полученный вами
адрес не будет содержать корректное значение полного адреса, так
как сегмент будет неправильным. Таким образом, подпрограмма будет
либо извлекать неправильное значение, либо (если вы пытаетесь из-
менить значение передаваемой переменой) будет изменять неверную
область памяти с непредсказуемыми результатами.
Передача параметров по ссылке значительно проще для таких
переменных, как строки, массивы и числа с плавающей точкой. Эти
переменные могут быть достаточно длинными, что вызывало бы проб-
лемы, если бы они действительно передавались в стеке. Затраты на
извлечение длинной переменной из стека примерно эквивалентны зат-
ратам на считывание из стека ее адреса и работу с переменной в
области памяти. Для строковых переменных (если, конечно, строка
не слишком короткая) маловероятно, что окажется достаточное для
обработки строки место в регистрах без выполнения доступа к памя-
ти.
TASM2 #3-5/Док = 216 =
Переменные, находящиеся вне текущего сегмента данных
-----------------------------------------------------------------
Если передаваемые переменные не находятся в текущем сегменте
данных, то для доступа к значению переменной в программе на Ас-
семблере вам потребуется как сегмент, так и смещение переменной.
Турбо Бейсик всегда передает в стеке для каждой переменной и сег-
мент, и смещение. Таким образом, программисту всегда доступен
полный адрес каждой переменной.
Часть адреса, определяющая сегмент, содержится в 2 байтах,
непосредственной следующих за смещением параметра. Наиболее удоб-
ный способ использования этой информации в вашей программе на Ас-
семблере - это использование инструкции LES.
Инструкция LES загрузит в указанный регистр значение смеще-
ния переменной, а регистр ES - часть адреса, представляющую собой
сегмент. Это обеспечит вам полную адресацию к любой перемен-
ной, независимо от того, в каком сегменте данных она находится.
Предположим опять, что в вашей подпрограмме нужно записать
значение целочисленной переменной в регистр CX. Поскольку регистр
ES сохранять не нужно, вы можете использовать инструкцию LES:
push bp ; сохранить указатель базы
mov bp,sp ; приравнять указатель базы к
; указателю стека
les di,[bp+6] ; ES содержит сегмент, DI -
; смещение
mov cx,ES:[DI] ; поместить значение переменной
; в CX
С помощью передачи полного адреса каждой переменной Турбо
Бейсик позволяет программисту, работающему на Ассемблере, писать
подпрограммы, независимые от того, где хранятся данные. Если вы
перепишете свою программу и поместите переменные или массивы в
другие сегменты данных, то в случае использования полного адреса
переменной и инструкции LES вам не придется переписывать подпрог-
раммы, реализованные на Ассемблере.
Тип оператора CALL
-----------------------------------------------------------------
Существует два типа оператора CALL - ближний (near) и даль-
ний (far). Дальний оператор CALL выходит за границы текущего сег-
TASM2 #3-5/Док = 217 =
мента кода, а ближний - нет.
В Турбо Бейсике привести к некоторым проблемам может только
оператор CALL ABSOLUTE, поскольку его целевой адрес может распо-
лагаться в любом месте памяти. Таким образом, в Турбо Бейсике
требуется, чтобы подпрограммы, вызываемые оператором CALL ABSO-
LUTE, завершали выполнение с помощью дальнего возврата и автома-
тически генерировали оператор CALL дальнего типа при передаче уп-
равления таким подпрограммам.
Вызов CALL INTERRUPT может неявно генерировать вызов дальне-
го типа, но Турбо Бейсик обрабатывает это внутренним образом.
Если вы пишете свои собственные обработчики прерываний, вам нужно
только для передачи управления обратно в программу Турбо Бейсика
использовать инструкцию IRET (возврат из прерывания).
Встроенный Ассемблер включается в программу при ее компиля-
ции. В общем случае код будет находиться в текущем сегменте кода,
но Турбо Бейсик не подразумевает этого, и такие подпрограммы так-
же завершаются дальним возвратом. Турбо Бейсик будет автоматичес-
ки генерировать вызов и возврат, поэтому вам не нужно использо-
вать в своем коде инструкцию RET. Если вы хотите завершить под-
программу до конца кода, то нужно просто перейти на метку в конце
кода.
Примечание: Поскольку Турбо Бейсик не использует
программу-компоновщик DOS LINK, вам можно не беспокоиться
о том, чтобы объявлять ваши подпрограммы общедоступными
(PUBLIC), или описывать их в своих программах, как внеш-
ние.
Извлечение из стека
-----------------------------------------------------------------
Перед завершением подпрограммы вы должны убедиться в том,
что все занесенные в стек регистры извлечены из стека. Здесь лег-
ко сделать ошибку, особенно если в вашей подпрограмме использует-
ся условное занесение в стек и извлечение из него регистров.
Если вы извлечете слишком мало регистров, то ваша программа
после вызова возможно никогда не возвратит управление, так как
Турбо Бейсик подразумевает, что последняя запись в стеке - это
адрес возврата. Если вы извлечете слишком много регистров, то мо-
жет наблюдаться тот же эффект.
TASM2 #3-5/Док = 218 =
Не выполняйте операцию загрузки и извлечения ненужных значе-
ний из сегментных регистров, так как это может привести к несов-
местимости вашего исходного кода с последующими версиями DOS
(например, операционной системой OS/2).
TASM2 #3-5/Док = 219 =
Создание программы на Ассемблере для Турбо Бейсика
-----------------------------------------------------------------
Если вы создали программу на Ассемблере и хотите преобразо-
вать ее в формат .COM, используемый в программе Турбо Бейсика, то
можно использовать пример командного файла из руководства по Тур-
бо Бейсику:
TASM %1;
TLINK /t %1;
Включать сегмент стека не нужно, так как программа на Ассем-
блере при запуске будет использовать стек, с которым завершил ра-
боту Турбо Бейсик.
Если вы не используете в начале программы оператор ORG 100h,
то Турбо Ассемблер будет по умолчанию начинать выполнение с адре-
са 100h. Тем не менее лучше указать оператор ORG явно (для после-
дующей возможной модификации).
Если ваша программа предназначена для работы на процессорах
80186, 80286 или 80286, то в начале кода Ассемблера можно указать
инструкции .186, .286 и .386. Тогда Турбо Ассемблер позволит вам
использовать коды операций, доступные для этих процессоров. Как
вы увидите, это может дать существенные преимущества.
Вызов встроенной процедуры Ассемблера
-----------------------------------------------------------------
Предположим, вы создали подпрограмму на Ассемблере и преоб-
разовали ее с помощью Турбо Ассемблера в формат .COM. Этот ре-
зультат в программе Турбо Бейсика можно использовать двумя спосо-
бами: с помощью директивы $INLINE COM или с помощью директивы
$INCLUDE.
Наиболее прямой метод состоит в использовании директивы
$INLINE COM имя_файла. При этом Турбо Бейсик включит файл .COM в
указанном вами месте. Этот метод относительно просто реализовать,
но у него имеется раз недостатков:
1. Турбо Бейсик имеет ограничение: допускается не более 16
директив $INLINE на процедуру. Это может привести к
проблемам, если вы делаете что-то очень сложное (но это
маловероятно).
TASM2 #3-5/Док = 220 =
2. Более серьезная проблема проистекает из того факта, что
файлы .СОМ не включают в себя документацию (коммента-
рии). Вы, конечно, можете включить примечания в вызываю-
щую программу, но было бы лучше, если бы сами файлы .СОМ
были документированы.
3. Использование директивы $INLINE .COM может оказаться
плодотворным. Размещение нескольких таких файлов в одном
файле было бы полезно особенно в том случае, если вы ис-
пользуете некоторое число таких файлов вместе. (В этом
заключается один из доводов в пользу использования биб-
лиотек программ на Ассемблере. К сожалению, создать биб-
лиотеку файлов .СОМ не так просто.)
4. Наконец, файлы $INLINE .COM должны модифицироваться и
переассемблироваться, если вы их изменяете. Если измене-
ния относительно невелики, то это не всегда желательно.
В соответствие с характером работы директивы $INCLUDE COM вы
можете захотеть преобразовать файлы .COM в последовательность
шестнадцатиричных цифр, которые вы можете включить в программы по
директиве $INCLUDE. Такие программы также могут считываться с
диска командой редактора Турбо Бейсика Read File (чтение файла) -
Ctrl-K R. Таким образом, вы будете явно видеть, что включается в
ваш исходный файл. Для программиста, работающего на Турбо Бейси-
ке, это существенное преимущество.
Так как редактируемый текст представляет собой шестнадцати-
ричные коды, вы можете включать или добавлять комментарии. Можно
также использовать редактор Турбо Бейсика для того, чтобы внести
во встроенный код минимальные изменения без необходимости его пе-
реассемблирования, и поместить несколько подпрограмм в один файл.
Сочетая эти методы, можно эффективно создавать библиотеку под-
программ на Ассемблере, использующихся с определенным семейством
программ. Обслуживать такую библиотеку проще, чем если бы вы
пользовались стандартным библиотекарем.
Если программа слишком длинная, то шестнадцатиричный файл
будет очень большим, и редактировать исходный файл может оказать-
ся неудобным. На редактирование файла (как одного блока) сущест-
вует ограничение 64К. Если это становится проблемой, вы можете
объединить шестнадцатиричный файл с вашей программой, как файл
$INCLUDE. (Иногда такой размер может сделать вашу программу весь-
ма трудной для чтения.)
Приведем пример простой программы на Турбо Бейсике, преобра-
TASM2 #3-5/Док = 221 =
зующей файлы .COM в шестнадцатиричные файлы:
'COM2INC.BAS
'Данная программа преобразует файлы СОМ в файлы $INCLUDE
'с помощью метакоманды Бейсика $INLINE (для обеспечения
'простого включения в программы на Бейсике).
DEFINT A-Z
'Все переменные будут целочисленными.
F$=COMMANDS
'Проверить, если ли командная строка.
WHILE F$=""
PRINT"Эта программа преобразует файлы СОМ в $INCLUDE"
PRINT"для использования в Турбо Бейсике. По умолчанию"
PRINT"исходные файлы имеют тип СОМ. Выходные файлы"
PRINT"по умолчанию будут иметь тип INC. Вы можете"
PRINT"переопределить эти типы, введя конкретную"
PRINT"спецификацию типа. Если вы не введете имя"
PRINT"выходного файла, он будет иметь то же имя,"
PRINT"что и входной файл, но спецификацию типа"
PRINT"INC."
LINE INPUT"Введите имя преобразуемого файла: ";F$
WEND
IF COMMAND$="" THEN
LINE INPUT"Введите имя выходного файла: ";O$
END IF
IF INSTR(F$,".")=0 THEN F$=F$+".COM" 'спецификация ввода
IF O$="" THEN
O$=LEFT$(F$,INSTR(F$,"."))+"INC" 'спецификация вывода
ELSE
IF INSTR(O$,".")=0 THEN O$=O$+".INC" ' обе
END IF
OPEN"R",#1,F$,1 'считывать по одному байту из входного
FIELD #1,1 AS A$ 'файла в A$
LASTBYTE&=LOF(1) 'конец файла
OPEN"O",2,O$ 'выходной файл открыт
FOR I&=1 TO LASTBYTE&-1
GET 1,I&
X%=ASC(A$)
IF ((I&-1) MOD 5=0) THEN PRINT #2,"":PRINT #2,"$INLINE";
PRINT #2,"&H";HEX$(X%);
IF ((I&-1) MOD 5<>4) THEN PRINT #2,",";
NEXT I&
TASM2 #3-5/Док = 222 =
GET 1,LASTBYTE&
PRINT #2,"&H";HEX$(ASC(A$))
PRINT"Преобразование выполнено. ";"Считано ";LASTBYTE&;"байт."
PRINT O$;" содержит ";LOF(2);" байт."
CLOSE
END
Эта программа выведет файл, на строке которого будет содер-
жаться до пяти шестнадцатиричных кодов. Каждая строка будет начи-
наться с директивы $INLINE, а результирующий файл должен содер-
жать достаточно места для комментарием, которые вы, возможно, за-
хотите добавить. Если вы хотите поместить на одну строку больше
или меньше шестнадцатиричных кодов, то вам потребуется только за-
менить MOD 5 на MOD N, где N больше или меньше 5.
TASM2 #3-5/Док = 223 =
Размещение подпрограммы Турбо Бейсика в памяти
-----------------------------------------------------------------
В общем случае имеется два способа для определения располо-
жения подпрограммы в памяти:
1. Ваша подпрограмма сама может возвращать свой адрес.
2. Вы можете сгруппировать ряд подпрограмм вместе, чтобы
одна программа возвращала адрес, относящийся ко всем
подпрограммам.
3. Можно искать в памяти компьютера определенную последова-
тельность байт.
Чтобы создать программу, возвращающую свой адрес, вы можете
использовать код, аналогичный следующему:
xy: mov ax,cs ; переместить сегментный регистр
; кода в AX
push bp ; сохранить указатель базы
mov bp,sp ; и скопировать указатель стека
; в BP
les di,[bp+6] ; ES содержит сегмент, DI -
; смещение
mov ES:[DI],AX ; сохранить значение CS в
; первом параметре
mov dx,offset xy ; получить текущее смещение
les di,[bp+0ah] ; адрес второго параметра
mov ES:[DI],DX ; сохранить значение смещения
; во втором параметре
jmp fin ; пропустить "реальный" код
; "реальный" код содержится
; здесь
fin: pop bp ; восстановить BP и выполнить
; возврат
Для переменных этой подпрограммы вам нужно передать два це-
лых значения. В первом она возвратит сегмент кода, в во втором -
смещение. Проблема здесь состоит в том, что весь этот код беспо-
лезен после того, как он будет один раз использован. Фактически,
это хуже чем бесполезность, так как этот код нужно удалить, преж-
де чем программа сможет нормально работать.
Пока ваша программа сможет приобрести благодаря модификациям
существенный выигрыш в скорости, весьма вероятно, что на измене-
TASM2 #3-5/Док = 224 =
ния у вас уйдет больше времени, чем вы выиграете. Модификация
должна быть разумной: если ваша программа не является полностью
перемещаемой, рабочему коду должно предшествовать большое число
операций NOP.
Тем не менее вы можете определить адрес подпрограммы. Если
вы группируете вместе несколько программ и помещаете метки в
программу Турбо Бейсика (благодаря чему вы можете вызывать нужную
подпрограмму), нельзя ли разместить среди этих подпрограмм под-
программу сообщения адреса?
Ответ здесь отрицательный. Если вы помните, Турбо Бейсик об-
рабатывает за вас инструкции RET. Так как подпрограммы имеют раз-
ные имена, Турбо Бейсик подразумевает, что каждая содержит пере-
мещаемый код. При этом нет гарантии, что в конечном файле .EXE
отдельные подпрограммы будут находиться в одной и той же области
памяти. Даже если подпрограммы находятся в одной области памяти и
следуют в том же порядке, вы не будете знать, сколько байт Турбо
Бейсик помещает между ними, и поэтому не сможете узнать, куда
нужно перейти, чтобы сделать нужные изменения.
Третий метод определения адреса подпрограммы - это метод
сигнатуры. Чтобы его использовать, нужно найти в памяти компьюте-
ра место, где содержится определенная последовательность байт,
идентифицирующая подпрограмму, которую вы хотите изменить.
В методе сигнатуры также существую свои проблемы. Первая
состоит в том, что для поиска требуется много времени. Вторая
заключается в том, что нет гарантии (даже если вы нашли сигнату-
ру) что вы находитесь в нужной подпрограмме. В третьих, каждая
подпрограмма должна содержать свою сигнатуру. Для этого требуется
дополнительное место и лишнее время, необходимое для модификации
программ.
Чтобы создавать подпрограммы, которые можно модифицировать,
вам потребуется лучший метод определения адреса подпрограммы и
более легкий способ изменения ее инструкций.
Чтобы решить эти проблемы, прочитайте следующий раздел, где
рассматривается специальный способ использования подпрограмм, ко-
торые можно модифицировать из программы на Турбо Бейсике.
"Скрытые" строки
-----------------------------------------------------------------
TASM2 #3-5/Док = 225 =
Турбо Бейсик позволяет использовать для строкового простран-
ства максимум 64К. Иногда вам будет нужен каждый байт этого
пространства, а заключенные в кавычки строковые константы (такие,
какие используются для меню и подсказок) также занимают строковое
пространство.
Пространство кода, однако, ограничено только 16 сегментами
(каждый до 64К). Жизнь бы значительно облегчилась, если бы неко-
торые из таких строковых констант можно было бы хранить в прост-
ранстве кода, где они не будут уменьшать область, доступную для
динамических строковых данных. К счастью, это не слишком трудно
сделать.
Рассмотрим следующую программу:
; Данная программа имеет два целых параметра и возвращает
; сегмент и смещение текста в теле программы
;
push sp
mov bp,sp
mov dx,offset show ; место, где находится
; строка
mov ax,cs ; сегмент кода а AX
les di,[bp+6] ; ES:DI указывает на
; параметр
mov ES:[DI],DX ; сообщить о месте
; строки
mov di,[bp+0ah] ; следующий параметр
mov ES:[DI],AX ; сообщить о сегменте
; кода
jmp fini ; и перейти назад
DB 'Здесь можно разместить любой текст'
DB 'и в любом объеме, завершающийся'
DB 'любым символом (здесь это 0).',0
fini pop bp
Эффект данной подпрограммы несколько отличается от того, ко-
торый мы предлагали ранее для программно-модифицируемого встроен-
ного кода. Однако, вы не записываете код (хотя Турбо Бейсик будет
обрабатывать полученный в результате файл .COM, как состоящий це-
ликом из кода). Вместо этого вы храните данные.
Подпрограмма возвращает текущий адрес хранящихся в ней дан-
ных. Если вы хотите знать длину данных, вы можете создать прог-
рамму, которая сообщает и об этом, хотя для этого потребуется пе-
TASM2 #3-5/Док = 226 =
редавать третий параметр.
Так как вы знаете, где находится текст в памяти, для чтения
строковых данных в строковое пространство каждый раз, когда вы
хотите напечатать сообщение, вы можете использовать инструкцию
PEEK. Когда вы завершите печать сообщения, вы можете отбросить
ненужную строку. Если она вам потребуется снова, она будет дос-
тупна в сегменте кода.
Вы можете определить число доступных в этой подпрограмме
байт. В частности, вы можете определить число байт, предшествую-
щих тексту. Замените просто все, кроме конечной инструкции вашим
собственным кодом: так как вы знаете, где находится подпрограмма
и как она велика, для ее перезаписи можно использовать BLOAD. Что
касается файла .EXE Турбо Бейсика, то ничего менять не придется,
хотя вся программа будет теперь другой.
Обычно этот метод не является необходимым. Сохранение строк
в пространстве кода иногда полезно, но замену целиком одной под-
программы другой лучше осуществлять с помощью CALL ABSOLUTE.
TASM2 #3-5/Док = 227 =
Оператор CALL ABSOLUTE
-----------------------------------------------------------------
По некоторым причинам в руководстве по Турбо Бейсику об опе-
раторе CALL ABSOLUTE рассказывается довольно кратко. Первая из
них состоит в том, что Турбо Бейсик не имеет существенного управ-
ления такими подпрограммами. Во-вторых, такие подпрограммы обычно
используются, потому что они были написаны для интерпретатора
Бейсика. Турбо Бейсик настолько существенно отличается от инте-
рпретатора Бейсика, что такие программы могут в нем не работать.
В-третьих, будущие операционные системы могут не допускать ис-
пользования подпрограмм CALL ABSOLUTE. В частности, операционные
системы, где четко различается пространство кода и данных, могут
не позволять процессору выполнять инструкции, находящиеся в
пространстве данных. В-четвертых, подпрограммы, вызываемые с по-
мощью CALL ABSOLUTE, могут воспринимать только простые целочис-
ленные параметры-переменные. Это не такое уж серьезное ограниче-
ние, как кажется, весь простые целые переменные могут содержать
сегмент и смещение переменной любого типа. Однако, при этом пере-
дача параметров может потребовать несколько больше времени.
По этой причине мы будем предполагать, что вы используете
операционную систему MS-DOS версии 2.0 или старше и что эта опе-
рационная система допускает выполнение процессором инструкций,
находящихся в любом месте памяти.
TASM2 #3-5/Док = 228 =
Обращение CALL ABSOLUTE к фиксированным адресам памяти
-----------------------------------------------------------------
Если у вас имеется семейство программ, которые совместно ис-
пользуют один и тот же набор подпрограмм, то имеет смысл размес-
тить эти подпрограммы по фиксированному адресу памяти. Турбо Бей-
сик позволяет вам для этой цели зарезервировать адреса памяти (в
области старших адресов) с помощью команды MEMSET.
Вместе с командой MEMSET часто используется ENDMEM. ENDMEM
возвратит длинное целое, представляющее собой последнюю ячейку
памяти, которую может использовать скомпилированная программа
Турбо Бейсика. Подпрограммы обычно размещаются в старших адресах
памяти по некоторому фиксированному адресу, начиная с адреса,
расположенного ниже этого предела.
Если у вас есть такой набор подпрограмм, то вызывать их нуж-
но будет с помощью вызова CALL ABSOLUTE. Чтобы поместить подпрог-
раммы в старшие адреса памяти, используйте команду BLOAD. Чтобы
задать адрес сегмента, по которому должна загружаться подпрограм-
ма, используйте определение DEF SEG и явно укажите адрес смеще-
ния, по которому ее нужно загрузить.
Когда вы создаете эти подпрограммы с помощью Турбо Ассембле-
ра, нужно соблюдать следующие правила:
1. Если ваша программа не предназначена для запуска по од-
ному (и только по одному) адресу, все передачи управле-
ния в программе (JMP и CALL) должны быть полностью пере-
мещаемы. (Полное обсуждение перемещаемого кода выходит
за рамки данной главы.)
2. Если ваша программа предназначена для запуска только по
одному адресу, вы должны указать этот адрес в директиве
ORG исходного кода Ассемблера.
TASM2 #3-5/Док = 229 =
Обращение CALL ABSOLUTE к другим адресам памяти
-----------------------------------------------------------------
Турбо Бейсик позволит вам использовать обращение CALL ABSO-
LUTE к ячейкам памяти, которые при каждом запуске программы могут
быть различными. Типичный способ сделать это заключается в заг-
рузке подпрограммы Ассемблера в массив, расположенный вне обычно-
го пространства данных.
Рассмотрим следующий фрагмент программы:
DEFINT A-Z
$DYNAMIC 'массивы будут динамическими
DIM ROUTINEARRAY(10000) 'выделено 20002 байт
'здесь содержится различный код
WHERESEG%=VARSEG(ROUTINEARRAY(0)) 'адрес сегмента
WHEREOFFSET%=VARPTR(ROUTINEARRAY(0)) 'адрес смещения
DEF SEG=WHERESEG% 'задать сегмент по умолчанию
BLOAD"COMFILE",WHEREOFFSET% 'считать программу
CALL ABSOLUTE WHEREOFFSET%(PARAMETER%) 'вызвать подпрограмму
DEF SEG 'вернуться к значению по
'умолчанию
Если вы хотите использовать несколько программ, то можно по
очереди загружать каждую из них в один и тот же массив. Наконец,
если вы хотите изменить часть массива, это можно сделать, просто
изменив значения выбранных элементов массива.
Как вы можете видеть, подпрограммы, созданные для вызова с
помощью CALL ABSOLUTE, можно значительно проще находить и модифи-
цировать, чем те, для которых используется директива $INLINE.
Трудность состоит только в том, что такие подпрограммы должны
быть полностью перемещаемыми. Для небольших подпрограмм это может
не представлять собой проблему. Для сложных же подпрограмм писать
полностью перемещаемый код может оказаться делом непростым.
Вы можете также использовать BLOAD для загрузки подпрограмм
в строковые переменные. Здесь нужно соблюдать особую осторож-
ность. Если вы пытаетесь загрузить программу, длина которой пре-
вышает длину строковой переменной, то вы запортите какую-то дру-
гую строку. Если эта строка модифицируется, часть вашей загружен-
ной программы также может модифицироваться.
Строковые переменные также можно перемещать. Даже если под-
программа загружена в строку корректно, вам следует позаботиться
об использовании VARSEG и VARPTR для того, чтобы установить адрес
TASM2 #3-5/Док = 230 =
строки непосредственно перед попыткой вызова этой подпрограммы.
Строки Турбо Бейсика хранятся не так, как численные перемен-
ные. Если вы выполняете оператор VARPTR(A%), то получите адрес
целой переменной A%. VARPTR(A$) даст адрес описателя строки для
A$. Ячейка памяти, отстоящая на 2 байта, будет содержать действи-
тельный адрес строки в строковом пространстве. Чтобы получить тот
же результат, что и с VARPTR(A%), вам пришлось бы сделать что-то
вроде следующего:
A%=VARPTR(A$)
A%=A%+2
STRINGADDRESS=CVI(CHR$(PEEK(A%)+CHR$(PEEK(A%+1)))
Размещение подпрограмм Ассемблера в символьных строках поль-
зуется популярностью, ведь на самом деле есть опасность, что це-
лочисленные массивы могут быть индексированы и стерты. Но для
подпрограмм, вызываемых с помощью CALL ABSOLUTE, лучше использо-
вать целочисленные массивы, что поможет избежать трудностей при
доступе к постоянно перемещаемым строковым данным.
Если вы хотите избежать использования BLOAD, то можно также
загрузить файлы .COM в строки с помощью ввода-вывода двоичного
файла, то есть, открытия файла .COM, как двоичного файла, и счи-
тывания в строку корректного числа байт. Тот же подход можно ис-
пользовать для считывания данных в целочисленный массив. Однако
BLOAD выполняется быстрее и пользоваться этой командой проще.
TASM2 #3-5/Док = 231 =
Другие проблемы, возникающие при использовании CALL ABSOLUTE
-----------------------------------------------------------------
Считывание кода с диска с помощью CALL ABSOLUTE связано с
несколькими существенными недостатками, наиболее существенный из
которых относится к требованию переместимости (как мы уже отмеча-
ли ранее).
Другие серьезные проблемы заключаются в том, подпрограммы
должны считываться с диска отдельно от основной программы (при
этом появляются дополнительные возможности возникновения ошибок).
Требуемый код может отсутствовать на диске, или может быть пов-
режден (запорчен).
Третья проблема состоит в том, что время, необходимое для
считывание кода с диска, может свести на нет саму причину исполь-
зование подпрограммы на Ассемблере вместо Турбо Бейсика.
Вопреки этим камням преткновения гибкость, которую можно по-
лучить при считывании различных подпрограмм или наличие кода, ко-
торый можно модифицировать под управлением программы, а также
уменьшение объема кода, который должен присутствовать в памяти -
это достаточно веские доводы в пользу применения конструкции CALL
ABSOLUTE.
CALL INTERRUPT
-----------------------------------------------------------------
Третий и последний способ обращения к подпрограммам на Ас-
семблере из Турбо Бейсика - это возможно самый сложный метод ис-
пользования Ассемблера и одновременно самый простой способ его
использования избежать.
Большинство программистов используют CALL INTERRUPT для дос-
тупа к обычным обслуживающих средств MS-DOS. В этой ситуации об
ассемблировании можно не беспокоиться. Но необходимо помнить сле-
дующее:
-----------------------------------------
Название Регистр
-----------------------------------------
REG0 Флаги
REG1 AX
REG2 BX
REG3 CX
TASM2 #3-5/Док = 232 =
REG4 DX
REG5 SI
REG6 DI
REG7 BP
REG8 DS
REG9 ES
-----------------------------------------
Чтобы задать значение регистра, используйте оператор REG:
REG 3,&H0F01
Этот оператор установит регистр CX в шестнадцатиричное зна-
чение 0F01. Регистр CH получит шестнадцатиричное значение 0F, а
регистр CL - 01.
Чтобы считать значение регистра, используйте функцию REG:
A%=REG(3)
Этот оператор присвоит переменной A% текущее значение ре-
гистра CX.
Следующий пример приведет к обратной прокрутке изображения
на экране (со строки 1 до строки 24):
REG 3,0 'строка 0, позиция 0 ("вершина")
REG 4,&H175F 'строка 23, позиция 79 ("дно")
REG 2,&H70 'цвет 7,0
REG 1,&H0701 'функция BIOS 7, сдвинуть на 1 строку
CALL INTERRUPT &H10 'видеопрерывание 10h
Написать эквивалентную программу на Ассемблере гораздо труд-
нее, к тому же она лучше работать не будет.
Вся процедура обычно очень проста. Однако более квалифициро-
ванные программисты прерывания для других целей, а не только для
вызова обычных обслуживающих средств MS-DOS.
Прерывания часто используются для управления устройствами
(например, датчиками измерения температуры, удаленными записываю-
щими устройствами, таймерами и т.д.). Чтобы использовать такое
прерывание, вы должны найти неиспользуемое прерывание. (Многие из
них используют MS-DOS, а другие могут использовать такие устройс-
тва, как накопители на магнитной ленте или устройство памяти типа
"ящика Бернулли").
TASM2 #3-5/Док = 233 =
В программе Турбо Бейсика нужно сделать так, чтобы вектор
прерываний указывал на программу, написанную на Турбо Ассемблере.
Как отмечено в руководстве по Турбо Бейсику, подпрограмма обслу-
живания прерывания должна сохранять значения регистров SS и SP
(любой другой регистр может модифицироваться). В конце подпрог-
раммы управление через инструкцию IRET передается обратно Турбо
Бейсику.
Для определения адреса подпрограммы и занесения его в вектор
прерываний можно использовать уже описанные выше методы, но лучше
помещать подпрограммы обслуживания прерываний в старшие адреса
памяти или загружать их (с помощью BLOAD), как подпрограммы для
CALL ABSOLUTE.
Подпрограммы обслуживания прерываний, включенные в вашу
программу с помощью команды $INLINE, должны быть каким-то образом
локализованы. Подпрограммы, загруженные с помощью BLOAD и запи-
санные в целочисленные массивы должны находиться по определенному
адресу, но он по крайней мере должен быть известен. Однако, раз-
мещение обработчиков прерываний в таких массивах означает, что
код подпрограммы обслуживания прерывания должен быть полностью
переместимым.
По этой причине подпрограммы обслуживания прерываний помеща-
ются обычно в фиксированные адреса (старшие адреса памяти). Если
вы решили использовать такой подход, убедитесь, что вы не забыли
включить в исходный код Турбо Ассемблера инструкцию ORG.
TASM2 #3-5/Док = 234 =
Пример программы
-----------------------------------------------------------------
FILLIT2$ = CHR$(&HFC)+CHR$(&HF3)+CHR$(&HAB)+CHR(&HCB)
' cld rep stow ret
DIM a%(100) 'целочисленный массив с нулевыми
'элементами
WHERE%=VARPTR(FILLIT2$) 'в этой ячейке хранится длина
WHERE%=WHERE%+2 'а это то место, где находится
'адрес строки
CLS:PRINT PEEK(WHERE%),PEEK(WHERE%+1)
HERE%=PEEK(WHERE%)+256*PEEK(WHERE%+1) 'место, где находится
'строка
DEF SEG 'не является здесь необходимым,
'но это хороший стиль программи-
'рования
WHERE%=PEEK(0)+256*PEEK(1)
DEF SEG=WHERE% 'сегмент строки - первое слово
'(по умолчанию - DS)
REGES%=VARSEG(a%(0))
REGSI%=VARPTR(a%(0))
REG 1,5% 'поместить в AX значение-
'заполнитель
REG 3,101% 'число заполняемых элементов
'(от 1 до 100) - в CX
REG 9,REGES% 'сегмент заполняемого массива
' -> в ES
REG 6,REGSI% 'смещение первого элемента
'массива -> в SI
CALL ABSOLUTE HERE% 'заполнить массив значением 5
DEF SEG
FOR i%=0 TO 100:PRINT a%(i%);:HEXT i%
PRINT
PRINT REG(1),REG(3),REG(9),REG(6):STOP
CALL FILLIT(a%(0),-1%,101%) 'заполнить массив значением
'-1
FOR i%=0 TO 100:PRINT a%(i%);:NEXT i%
PRINT
END
SUB FILLIT INLINE
$INLINE &H55,&H88,&HD,&H7E
TASM2 #3-5/Док = 235 =
$INLINE &HE,&H26,&H8B,&HD,&HC4
$INLINE &H7E,&HA,&H26,&H8B,&H5
$INLINE &HC4,&H7E,&H6,&HFC,&HF3
$INLINE &HAB,&H5D
END SUB
;Подпрограмма для пересылки произвольного числа элементов
;с произвольным значением в целочисленный массив для
;call absolute. Синтаксис вызова следующий:
;REG 1,FILLVALUE% 'AX - значение-заполнитель
;REG 3,FILLCOUNT% 'CX - число заполняемых
; 'элементов
;REG 9,VARSEG(ARRAY(0)) 'ES содержит сегмент массива
;REG 6,VARPTR(ARRAY(0)) 'DI - смещение первого элемента
; 'массива
;CALL ABSOLUTE FILLIT2
;FILLIT2 - адрес абсолютной подпрограммы, а DEG SEG будет
;по умолчанию устанавливать сегмент программы перед вызовом
;CALL ABSOLUTE в соответствии с FILLIT2.
PROGRAM SEGMENT
START PROC FAR ; дальний тип вызова
ASSUME cs:PROGRAM
push bp ; сохранить указатель базы
cld ; очистить флаг направления
rep ; следующая инструкция повторя-
; ется, пока CX не станет равным
; 0
stosw ; записать AX в ES:DI и увеличить
; DI на 2
pop bp ; восстановить указатель базы
ret ; межсегментный (дальний) возврат
START ENDP
PROGRAM ENDS
END
;Подпрограмма для пересылки произвольного числа элементов
;с произвольным значением в целочисленный массив Синтаксис
;вызова следующий:
;CALL FILLIT(ARRAY(0),FILLVALUE,NUMTIMES)
ORG 100h
PROGRAM SEGMENT
ASSUME cs:PROGRAM
push bp ; сохранить указатель базы
mov bp,sp ; переместить указатель стека
TASM2 #3-5/Док = 236 =
; в BP
les di,[bp+0eh] ; получить смещение адреса числа
; элементов для заполнения
mov ax,es:[di] ; поместить в AX значение-
; заполнитель
les di,[bp+6] ; смещение адреса заполняемого
; массива
cld ; очистить флаг направления
rep ; следующая инструкция повторяется,
; пока CX не будет = 0
stow ; записать AX в ES:DI и увеличить
; DI на 2
pop bp ; восстановить указатель базы
PROGRAM ENDS ; конец сегмента - инструкция RET
; не нужна
END
TASM2 #3-5/Док = 237 =
Приложение B. Интерфейс Турбо Ассемблера с Турбо Прологом
-----------------------------------------------------------------
Турбо Пролог предлагает программисту большое количество пре-
дикатов, обеспечивающих расширенный набор функций высокого уров-
ня, от управления окнами экрана до управления базами данных. Тур-
бо Ассемблер позволяет добавить к Турбо Прологу средства програм-
мирования нижнего уровня.
В данной главе мы рассмотрим сначала интерфейс Турбо Пролога
с Турбо Ассемблером, затем приведем некоторые простые примеры ор-
ганизации интерфейса подпрограмм Ассемблера с Турбо Прологом. На-
конец, мы обсудим вызов предикатов Турбо Пролога из кода Ассем-
блера, использование библиотечных вызовов Турбо Пролога и переда-
чу сложных структур.
Примечание: Когда мы говорим о Турбо Прологе, то речь
идет о версии 1.0 или более старшей.
Объявление внешних предикатов
-----------------------------------------------------------------
Турбо Пролог позволяет организовать интерфейс с другими язы-
ками с помощью описаний глобальных предикатов. В описании указы-
вается спецификация языка, благодаря которой Турбо Пролог может
определить, что глобальный предикат реализован на другом языке:
global pridicate
add(integer,integer,integer) - (i,i,o) language asm
scanner(string,token) - (i,o) language Pascal
В Турбо Прологе соответствующий язык указывается явно, чтобы
упростить проблемы с записью активации и форматом параметров,
соглашениями по вызову и возврату управления, определениями сег-
ментов и инициализацией.
Соглашения по вызову и параметры
-----------------------------------------------------------------
Семейство процессоров 8086 предоставляет программисту выбор
между ближним (near) и дальним (far) вызовом подпрограмм. Турбо
Пролог создает программы с большой моделью памяти и требует, что-
бы все вызовы и возвраты управления имели дальний тип.
TASM2 #3-5/Док = 238 =
Турбо Пролог поддерживает ряд соглашений по вызовам: согла-
шения Си, Паскаля и Ассемблера. При организации интерфейса с под-
программой, написанной с использованием соглашений по вызовам,
принятых в языке Си, параметры заносятся в стек в обратном поряд-
ке и после возврата управления указатель стека автоматически вы-
равнивается. При организации интерфейса с другими языками пара-
метры заносятся в стек в обычном порядке, а ответственность за
извлечение параметров из стека возлагается на вызываемую функцию.
Во многих компиляторах языков, предназначенных для работы на
процессорах серии 8086, есть выбор между 16-разрядными и
32-разрядными указателями (где 16-разрядные указатели ссылаются
на используемый по умолчанию сегмент). Турбо Пролог для доступа
ко всей памяти всегда использует 32-разрядные указатели.
Типы Турбо Пролога реализованы следующим образом:
integer (целый) 2 байта
real (вещественный) 8 байт (формат IEEE)
char (символьный) 1 байт (два байта при занесении
в стек)
string (строковый) 4-байтовый указатель (двойное
слово) на завершающуюся нулем
строку
symbol (идентификатор) 4-байтовый указатель (двойное
слово) на завершающуюся нулем
строку
compound (сложный) 4-байтовый указатель (двойное
слово) на запись
Выходной параметр заносится в стек, как 32-битовый указатель
на ячейку, которой должно быть присвоено возвращаемое значение.
Для входных параметров значение заносится в стек непосредственно,
а размер параметра зависит от его типа.
TASM2 #3-5/Док = 239 =
Соглашения по именам
-----------------------------------------------------------------
Один и тот же предикат в Турбо Прологе может иметь несколько
вариантов с различными типами и потоками ввода-вывода. Каждому
такому варианту должна соответствовать его собственная процедура,
которой присваивается уникальное имя. Это осуществляется с по-
мощью нумерации различных процедур в предикате (начиная с 0).
Например, если у нас есть описание:
global predicates
add(integer,integer,integer) - (i,i,0),(i,i,i) language asm
то первый вариант (с потоком ввода-вывода (i,i,o)) получит имя
add_0, в второй (с потоком ввода-вывода (i,i,i)) - add_1.
Турбо Пролог позволяет также программисту объявлять явное
имя глобального предиката. Это делается с помощью указания за
описанием "as общедоступное_имя". В следующем примере глобальный
предикат pred получит имя идентификатора my_pred, а не pred_0.
global predicates
pred(integer,integer) - (i,o) language asm as "my_pred"
Данный метод хорош, когда вы присваиваете предикату имя, и у
вас есть только один поток ввода-вывода. Если имеется два и более
потока ввода-вывода, то вам придется предусмотреть имя для каждо-
го варианта. Если использовать предикат add из предыдущего приме-
ра, то определение предиката будет выглядеть так:
global predicates
add(integer,integer,integer)-(i,i,0) language asm as "doadd"
add(integer,integer,integer)-(i,i,i) language asm as "addcheck"
Первый вариант (с первым потоком ввода-вывода (i,i,o)) полу-
чает имя doadd, а второй (с потоком ввода-вывода (i,i,i)) - имя
add_check. Заметим, что этот метод требует отдельного описания
для каждого варианта.
TASM2 #3-5/Док = 240 =
Разработка предикатов на Ассемблере
-----------------------------------------------------------------
Возможно, простейшие предикаты - это те, которые имеют толь-
ко поток ввода. Предположим, вы хотите выполнить горизонтальную
прокрутку содержимого текущего окна Турбо Пролога. Можно написать
предикат scroll_left, который прокручивает область на экране на
одну позицию влево. В примере SCROLLH.PRO scroll_left имеет четы-
ре целых аргумента и один поток ввода-вывода.
Модуль Турбо Пролога SCROLLH.PRO содержит описание глобаль-
ного предиката для предиката scroll_left. Предикат scroll_left
определен, как предикат Ассемблера.
/* SCROLLH.PRO */
global predicates
scroll_left(integer,integer,integer,integer) - (i,i,i,i)
language asm
predicates
scrollh
clauses
scrollh :-
makewindow(_,_,_,Row,Col,Nrows,Ncols),
scroll_laft(Row,Col,Nrows,Ncols),
readchar(C),
char_int(C,CI),
not(CI = 27),
scrollh.
goal
makewindow(1,7,7," Сообщение для прокрутки ",10,20,4,60),
write("Это сообщение будет прокручено в окне"),nl,
write("Взгляните на него!"),
readchar(_),
scrollh,
readchar(_).
Следующий исходный код на языке Ассемблера представляет со-
бой реализацию предиката scroll_left. Заметим, что этому предика-
ту дается имя SCROLL_LEFT_0, что соответствует описанным ранее
соглашениям по именам.
; SCROL.ASM
TASM2 #3-5/Док = 241 =
;
name scrol
; scroll_left(integer,integer,integer,integer) - (i,i,i,i)
; language asm
SCROL_TEXT SEGMENT BYTE PUBLIC 'CODE'
ASSUME CS:SCROL_TEXT
PUBLIC SCROLL_LEFT_0
SCROLL_LEFT_0 PROC FAR
;
; параметры
arg NCOLS:WORD, NROWS:WORD, COL:WORD, ROW:WORD = ARGLEN
;
; локальная переменная
local SSEG : WORD = LSIZE
push bp
mov bp,sp
sub sp,lsize ; пространство для локальных
; переменных
push si
push di
mov SSEG, 0B800h
sub NCOLS,3 ; NCOLS = NCOLS - 3
mov ax,ROW ; DEST = ROW*160 + (COL + 1)*2
mov dx,160
mul dx
mov dx,COL
inc dx ; сложили
shl dx,1
add dx,ax
push ds
push es
mov bx,NROWS ; цикл NROWS раз (BX используется,
; как счетчик)
dec bx ; NROWS = NROWS - 2
dec bx
Top: cmp bx,0
je Done
add dx,160 ; приемник = приемник + 160
mov ax,NCOLS ; посл_символ = приемник + NCOLS*2
shl ax,1
add ax,dx
push ax ; занести в стек смещение
; последнего символа
mov ax,SSEG ; загрузить сегмент экрана в ES, DS
mov es,ax
TASM2 #3-5/Док = 242 =
mov ds,ax
mov di,dx ; установить значения SI и DI для
; инструкций mov
mov si,di
add si,2
mov ax,[di] ; сохранить символ позиции 0 в AX
mov cx,NCOLS ; переместить NCOLS слов
cld
rep movsw
pop di ; извлечь в DI смещение последнего
; символа
mov [di],ax ; поместить символ в AX в последнюю
; позицию
dec bx
jmp TOP
Done: pop es
pop ds
pop di
pop si
mov sp,bp
pop bp
ret ARGLEN
SCROLL_LEFT_0 ENDP
SCROL_TEXT ENDS
END
Чтобы создать из файлов SCROLLH.PRO и SCROL.ASM выполняемый
файл, нужно сначала скомпилировать файл Турбо Пролога в файл .OBJ
(с помощью Турбо Пролога). (Когда Турбо Пролог компилирует мо-
дуль, он создает файл .OBJ и файл .SYM.) Затем с помощью Турбо
Ассемблера нужно ассемблировать файл SCROL.ASM в файл .OBJ, после
чего скомпоновать модули с помощью следующей командной строки
утилиты TLINK:
TLINK init scrollh scrol scrollh.sym,scroll,,prolog
Полученный в результате выполняемый файл будет называться
SCROLL.EXE.
TASM2 #3-5/Док = 243 =
Реализация предиката double
-----------------------------------------------------------------
Предположим, подпрограмма на языке Ассемблера вызывается с
помощью оператора:
double(MyInVar,MyOutVar)
где MyInVar перед вызовом связывается с целым значением, а после
вызова MyOutVar оказывается связанной с удвоенной величиной дан-
ного значения.
Запись активации, помещаемая в стек при активизации double,
имеет вид, представленный на Рис. 9.1:
------------- ---------------------------------------------
| [BP] + 10 |------>| Значение, с которым связана MyInVar. |
| | | SIZE = 2 байта (размер) |
| | |-------------------------------------------|
| [BP] + 6 |------>| Адрес, по которому нужно разместить зна- |
| | | чение для MyOutVar. SIZE = 4 байта |
| | |-------------------------------------------|
| [BP] + 2 |------>| Адрес, с которого должно продолжаться вы- |
| | | полнение после завершения работы double. |
| | | SIZE = 4 байта |
| | |-------------------------------------------|
| [BP] + 0 |------>| Ранее установленное значение BP (до нача- |
| | | ла выполнения double). |
| | | SIZE = 2 байта |
------------- ---------------------------------------------
Рис. 9.1. Запись активации для double.
Предикат double реализует следующая функция на языке Ассем-
блера:
;
; MYASM.ASM
;
A_PROC SEGMENT BYTE
ASSUME CS:a_prog
PUBLIC double_0
double_0 PROC FAR
push bp
mov bp,sp
mov ax,[bp]+6 ; получить значение, с
TASM2 #3-5/Док = 244 =
; которым связана MyInVar
add ax,ax ; удвоить это значение
lds si,DWORD PTR [bp]+10
mov [si],ax ; сохранить значение, с
; которым должно быть
; связано MyOutVar по
; соответствующему адресу
pop bp
mov sp,bp
ret 6
double_0 ENDP
A_PROC ENDS
Программа Турбо Пролога, содержащая обращение к double,
должна содержать также следующее описание глобального предиката:
global predicates
double(integer,integer) - (i,o) language asm
Во всем остальном программа эта Турбо Пролога не будет отли-
чаться от любой другой программы.
Предикат double используется в следующей программе:
/* MYPROLOG.PRO */
global predicates
double(integer,integer) - (i,o) language asm
goal
write("Введите целое значение "),
readint(I),
double(I,Y),
write(I," удвоенное значение = ",Y).
Программа на языке Ассемблера ассемблируется в файл
MYASM.OBJ, а вызывающая программа на Турбо Прологе транслируется
в модуль MYPROLOG.OBJ. Затем эти два модуля компонуются с помощью
командной строки:
TLINK init myprolog myasm myprolog.sym,double,,prolog
При этом создается выполняемая автономная программа
DOUBLE.EXE (для чего используется библиотека Турбо Пролога
PROLOG.LIB). Важно, что MYPROLOG.SYM указывался в команде TLINK в
качестве последнего имени файла перед первой запятой.
TASM2 #3-5/Док = 245 =
В общем случае формат записи активации будет зависеть от
числа параметров предиката Турбо Пролога и типов доменов, соот-
ветствующих этим параметрам. Например, если вы хотите определить:
add(Val1,Val2,Sum)
где Val1, Val2 и Sum принадлежат целым (integer) доменам, то за-
пись активации выглядела бы так, как показано на Рис. 9.2:
------------- ---------------------------------------------
| [BP] + 10 |------>| Адрес, по которому должно быть записано |
| | | значение Sum. |
| | | SIZE = 4 байта (размер) |
| | |-------------------------------------------|
| [BP] + 8 |------>| Значение, с которым связана Val2. |
| | | SIZE = 2 байта |
| | |-------------------------------------------|
| [BP] + 6 |------>| Значение, с которым связана Val1. |
| | | SIZE = 2 байта |
| | |-------------------------------------------|
| [BP] + 2 |------>| Адрес, с которого должно продолжаться вы- |
| | | полнение после завершения работы add. |
| | | SIZE = 4 байта |
| | |-------------------------------------------|
| [BP] + 0 |------>| Ранее установленное значение BP (до нача- |
| | | ла выполнения add). |
| | | SIZE = 2 байта |
------------- ---------------------------------------------
Рис. 9.2. Запись активации для предиката add.
Отметим, что каждый параметр занимает соответствующее число
байт. Для выходных параметров размер (SIZE) всегда равен 4 байтам
(которые используются для адреса сегмента и смещения). Для вход-
ных параметров размер определяется значением, которое действи-
тельно заносится в стек, поэтому оно зависит от соответствующего
домена.
Val1 и Val2 принадлежат к домену целых и оба занимают 2 бай-
та (для них используется указатель потока ввода-вывода (i) -
ввод). Параметр Sum занимает 4 байта (для него используется ука-
затель потока ввода-вывода (o) - вывод).
Заметим также, что при использовании компилятора Турбо Про-
лога обращение к внешнему предикату имеет вид:
TASM2 #3-5/Док = 246 =
mov ax,SEGMENT data
mov ds,ax
call FAR PTR external_predicate_inplementation
поэтому сегмент данных, адресуемый во время выполнения процедуры
или внешнего предиката, называется DATA.
TASM2 #3-5/Док = 247 =
Реализация предикатов с несколькими потоками ввода-вывода
-----------------------------------------------------------------
При реализации предикатов с несколькими потоками ввода-выво-
да нужно внимательно соблюдать для функций на языке Ассемблера
принятые в Турбо Прологе соглашения по именам. Предположим, нап-
ример, что вы хотите реализовать предикат add, у которого имеется
несколько потоков ввода-вывода. При каждом обращении к add он бу-
дет находить пропущенное значение в уравнении X + Y = Z, где два
из трех аргументов при обращении к add являются связанными.
Глобальный предикат на языке Ассемблера объявляется в прог-
рамме на Турбо Прологе ADDPRO.PRO. Заметим, что предикат add име-
ет три возможных потока ввода-вывода: (i,i,o), (i,o,i) и (o,i,i).
/* ADDPRO.PRO */
global predicates
add(integer,integer,integer) - (i,i,0),(i,0,i),(o,i,i)
language asm
goal
add(2,3,X), write("2 + 3 = ",X),nl,
add(2,Y,5), write("5 - 2 = ",Y),nl,
add(2,3,5), write("5 - 3 = ",Z),nl.
Следующая программа на Ассемблере ADD.ASM содержит код реа-
лизации предиката add. ADD_0 соответствует потоку ввода-вывода
(i,i,o), ADD_1 соответствует потоку (i,o,i), а ADD_2 - (o,i,i).
name add
ADD_TEXT SEGMENT BYTE PUBLIC 'CODE'
ASSUME CS:ADD_TEXT
PUBLIC ADD_0 ; поток (i,i,o)
ADD_0 PROC FAR
arg 2:dWORD, Y:WPRD, X:WORD = ARGLEN1
push bp
mov bp,sp
mov ax,X
add ax,Y
les bx,Z
mov WORD PTR ES:[BX],ax
pop bp
ret ARGLEN1
ADD_0 ENDP
PUBLIC ADD_1 ; поток (i,o,i)
TASM2 #3-5/Док = 248 =
ADD_1 PROC FAR
arg 2:WORD, Y:WORD, X:WORD = ARGLEN2
push bp
mov bp,sp
mov ax,Z
sub ax,X
les bx,Y
mov WORD PTR ES:[BX],AX
pop bp
ret ARGLEN2
ADD_1 ENDP
PUBLIC ADD_2 ; поток (o,i,i)
ADD_2 PROC FAR
arg Z:WORD, Y:WORD, X:WORD = ARGLEN3
push bp
mov bp,sp
mov ax,Z
sub ax,X
les bx,Y
mov WORD PTR ES:[BX],AX
pop bp
ret ARGLEN3
ADD_2 ENDP
ADD_TEXT ENDS
END
После того, как исходные файлы ADDPRO.PRO и ADD.ASM будут
оттранслированы в файлы .OBJ, вы можете с помощью командной стро-
ки:
TLINK init addpro add addpro.sym,addpro,,prolog
создать выполняемый файл .EXE.
TASM2 #3-5/Док = 249 =
Вызов предикатов Турбо Пролога из функций Ассемблера
-----------------------------------------------------------------
Теперь, когда мы обсудили вызов функций на языке Ассемблера
из Турбо Пролога, давайте обсудим обратную проблему: вызов преди-
катов Турбо Пролога из языка Ассемблера.
Когда предикат объявляется глобальным, варианты предиката
становятся глобальными функциями, которые могут вызываться любым
другим модулем. Соглашения по именами и соглашения по вызовам ос-
таются теми же, что и для предикатов, определенных в языке Ассем-
блера.
Следующий модуль Турбо Пролога определяет два глобальных
предиката: popmessage и from_asm.popmessage, которые объявляются,
как предикаты на языке Си, и предикат from_asm, который объявля-
ется, как предикат на языке Ассемблера.
Чтобы сформировать программу SHOWMESS, скомпилируйте из ин-
терактивной среды разработки программ Турбо Пролога файл
SHOWMESS.PRO в файл .OBJ. Затем с помощью команды:
tasm from_asm
ассемблируйте файл FORM_ASM.ASM и выполните компоновку:
TLINK init showmess fromsm showmess.sym,showmess,,prolog
Исходный текст SHOWMESS имеет вид:
/* SHOWMESS */
global predicates
popmessage(string) - (i) language c /* предикат вызывается
из процедуры на языке Ассемблера */
from_asm - language asm /* процедура на языке Ассемблера */
clauses
popmessage(S) :- /* может вызываться из функции Си
с именем popmessage_0 */
str_len(S,L),
LL = L + 4,
makewindow(13,7,7,"",10,10,3,LL),
write(S),
readchar(_),
removewindow.
goal
TASM2 #3-5/Док = 250 =
form_asm. /* external */
Следующий код на Ассемблере реализует from_asm и вызывает
popmessage:
EXTRN PopMessage_0:FAR
DGROUP GROUP _DATA
ASSUME CS:SENDMESS_TEXT,DS:DGROUP
_DATA SEGMENT WORD PUBLIC 'DATA'
messl DB "Report: Condition Red",0
_DATA ENDS
SENDMESS_TEXT SEGMENT BYTE PUBLIC 'CODE'
PUBLIC FROM_ASM_0
FROM_ASM_0 PROC FAR
push ds
mov ax,OFFSET DGROUP:messl
push ax
call FAR PTR PopMessage_0
pop cx
pop cx
ret
FROM_ASM_0 ENDP
SENDMESS_TEXT ENDS
END
Следующая программа для построения той же выполняемой прог-
раммы использует расширения высокого уровня на языке Ассемблера.
Чтобы построить ее, скомпилируйте из интерактивной среды Турбо
Пролога программу SHOWNEW.PRO (получив файл .OBJ), затем ассем-
блируйте FROM_ASM.ASM, используя команду:
tasm /jmasm51 /jquirks from_new
После этого выполните компоновку:
TLINK init shownew fromew shownew.sym,show2,,prolog
Программа SHOWNEW имеет вид:
/* SHOWNEW.PRO */
global predicates
popmessage(string) - (i) language c /* предикат вызывается
из процедуры на Ассемблере */
prom_asm - language c as "_from_asm" /* определить обще-
TASM2 #3-5/Док = 251 =
доступное имя процедуры на языке
Ассемблера */
clauses
popmessage(S) :-
str_len(S,L),
LL=L+4,
makewindow(13,7,7,"Окно",10,10,3,LL),
write(S),
readchar(_),
removewindow.
goal
from_asm. /* вызвать процедуру на языке Ассемблера */
Следующий исходный код Ассемблера реализует from_asm и обра-
щается к popmessage (как и в предыдущем примере).
; FORM_NEW.ASM
extrn PopMessage_0:FAR
.MODEL LARGE,C
.CODE
FROM_ASM proc
push ds
mov ax,OFFSET DGROUP:messl
push ax
call FAR PTR PopMessage_0
pop cx
pop cx
ret
FROM_ASM ENDP
.DATA
messl DB "Report: Condition Red",0
END
TASM2 #3-5/Док = 252 =
Списки и функторы
-----------------------------------------------------------------
В данном разделе мы обсудим метод, используемый для передачи
в процедуры на языке Ассемблера списков и функторов. Как уже упо-
миналось ранее, сложные объекты Турбо Пролога не передаются не-
посредственно. Вместо этого Турбо Пролог передает 4-байтовый ука-
затель на структуру.
Структура записи, которая используется для списков и функто-
ров, проста и понятна. Предположим, имеются следующие домены Тур-
бо Пролога:
domains
ilist = integer*
ifunc = int(integer)
Соответствующая структура для узла списка домена ilist имела
бы следующий вид:
STRUCT ILIST
NodeType DB ?
Value DW ?
NexNode DD ?
ENDS
Как можно видеть из этой структуры, узел списка содержит три
части:
- тип узла (байт);
- значение узла (зависит от типа);
- указатель на следующий узел (4 байта).
Тип узла содержит два имеющих определенный смысл значение:
Value 1 означает, что узел представляет собой узел списка, а
Value 2 означает, что узел - это узел конца списка (который не
содержит другой значащей информации). Значение узла может отно-
ситься к любому домену Турбо Пролога.
Соответствующая структура для функтора ifunc была бы следую-
щей:
STRUC IFUNC
FuncType DB ?
Value DW ?
ENDS
TASM2 #3-5/Док = 253 =
Структура функтора состоит из двух частей: типа функтора и
записи функтора. Тип функтора - это целое, связанное с позицией
варианта функтора в списке альтернатив. Первая альтернатива имеет
тип 1, вторая - тип 2 и т.д.
В следующих модулях Турбо Пролога и Турбо Ассемблера мы реа-
лизовали предикат и возвратили функтор Турбо Прологу.
Модуль Турбо Пролога имеет вид:
/* FUNC.PRO */
domains
ifunc = int(integer)
global predicates
makefunk(integer,ifunc) - (i,o) language c
goal
makefunc(4,H),
write(H).
Модуль Турбо Ассемблера имеет следующий вид:
;
; IFUNC.ASM
;
EXTRN _alloc_qstask:FAR ; _alloc_qstack возвращает
; указатель на блок памяти
STRUCT IFUNC
FuncType DB ?
Value DW ?
ENDS
IFUNC_TEXT SEGMENT WORD PUBLIC 'CODE'
ASSUME CS:IFUNC_TEXT
PUBLIC Makeifunc_0
Makefunc_0 PROC FAR
arc __inval:WORD, __outp:dWORD
push bp
mov bp,sp
mov ax,3 ; выделить 3 байта
push ax
TASM2 #3-5/Док = 254 =
call FAR PTR_alloc_qstack
pop cx
les bx,__outp
mov [WORD PTR ES:BX+2],DX
mov [WORD PTR ES:BX],AX
mov ax,__inval
;; les bx,__outp
les bx,[DWORD PTR ES:BX]
mov [(IFUNC PTR ES:BX).VALUE],AX
; значение = __inval
mov [(IFUNC PTR ES:BX).FUNCTYPE],1
; тип = 1
pop bp
ret
Makeifunc_0 ENDP
IFUNC_TEXT ENDS
END
В данном примере для ifunc используется только один тип
функтора. Если бы вы объявили другой функтор, типа:
myfunc = int(integer); char(char); r(real); d2(integer,real)
то структура была бы более сложной. Тем не менее структура также
имела бы две части, только вторая часть была бы объединением
структур данных, необходимых для определения всех вариантов
myfunc. Следующая структура представляет собой возможную реализа-
цию myfunc на Турбо Ассемблере:
STRUCT MyFunc
FuncType DB ?
UNION
STRUC
_int DW ?
ENDS
STRUC
_char DB ?
ENDS
STRUC
_real DQ ?
ENDS
STRUC
v1 DW ?
v2 DQ ?
ENDS
ENDS
TASM2 #3-5/Док = 255 =
ENDS
Типами, связанными с альтернативами функтора были бы следую-
щие типы:
int(integer) 1
char(char) 2
r(real) 3
d2(integer,real) 4
Чтобы лучше понять списки и функторы, взгляните на более ра-
нее описание доменов для ilist. Почему допустимы типы узла 1 и 2?
Потому что Турбо Пролог интерпретирует ilist, как структуру, ко-
торую можно было бы описать просто как:
ilist = listnode(integer,listnode); end_of_list.
Имейте в виду, что когда вы передаете сложные объекты, вы
передаете указатель на структуру. Более конкретно: во входном по-
токе список или функтор передается по ссылке, в выходном потоке
список или функтор передается, как указатель на ссылку на струк-
туру. (Турбо Пролог передает адрес указателя на возвращаемую
структуру.) Все структуры, возвращаемые Турбо Прологу, должны ис-
пользовать память, выделенную с помощью функций распределения па-
мяти Турбо Пролога. (См. "Руководство пользователя по Турбо Про-
логу" и "Справочное руководство по Турбо Прологу".)
TASM2 #3-5/Док = 256 =
Приложение C. Ответы на общие вопросы
-----------------------------------------------------------------
Приведенные далее рекомендации и замечания могут быть вам
полезны при использовании Турбо Ассемблера.
Вопрос: Как установить Турбо Ассемблер в системе?
Ответ: Запустите программу установки INSTALL с установочного
дистрибутивного диска Турбо Ассемблера. Чтобы начать установку,
измените текущий диск на тот, где содержатся программы установки,
и введите INSTALL. В рамке в нижней части экрана вам будут выво-
диться инструкции. Например, если вы выполняете установку с диска
A:, введите:
A:
INSTALL
После этого программа INSTALL выведет меню выбора и описа-
ния, которые помогут вам выполнить процесс установки.
Вопрос: Когда следует использовать различные режимы ассемб-
лирования, предусмотренные для поставляемых на диске программ?
Ответ:
Режим Условия использования
-------------------------------------------------------------
Обычный(MASM) - Программы ассемблируются под версией
MASM 4.00 или MASM 5.00.
Quirks - Программы ассемблируются под версией
MASM 4.00 или MASM 5.00, но не будут
ассемблироваться в TASM без указания
директив MASM51 или QUIRKS.
Masm51 - Программы требуют для ассемблирования
MASM версии 5.1.
Masm51 и Quirks - Программы требуют для ассемблирования
MASM 5.1, но не будут ассемблироваться
в TASM, когда задано только MASM51.
Вопрос: Нужно ли использовать MASM51 для ассемблирования
файлов, написанных для MASM версии 5.1?
Ответ: Большинство файлов будут ассемблироваться даже без
использования директивы MASM51. Однако, если вы ассемблируете ис-
ходный код, в котором используются средства, применяемые только в
TASM2 #3-5/Док = 257 =
MASM версии 5.1, вам потребуется использовать режим MASM51.
Чтобы определить, какие средства эмуляции MASM51 разрешены
при сочетании режимов MASM51 и QUIRKS, проверьте следующую далее
таблицу.
Вопрос: Какие элементы управляются с помощью режимов QUIRKS
и MASM51?
Ответ: В следующей таблице указывается, что делают различные
сочетания режимов QUIRKS и MASM51:
Режим Операции
-------------------------------------------------------------
Quirks - Если это допускает CS, разрешает гене-
рацию переходов типа FAR, как NEAR или
SHORT.
- Допускает, чтобы инструкции всех ра-
зопределялись в двоичной операции
только регистром (если он имеется).
- Отменяет OFFSET, переопределение сег-
тов и т.д. Информацию по '=' или чис-
ловым присваиваниям 'EQU'.
- Приводит к тому, что присваивания EQU
для выражений, содержащих "PTR" или
":", будут текстовыми.
Masm51 - Instr, Catstr, Substr, Sizestr, и
"\" (продолжение строки) разрешаются.
- EQU для ключевых слов будут TEXT, а не
ALIAS.
- В %textmacro в аргументах макрокоманды
предшествующие пробелы не отбрасывают-
ся.
Masm51 и Quirks - Все, что указано ранее для QUIRKS.
- Все, что указано ранее для MASM51.
- разрешаются локальные метки @@,
@F, и @B.
- В расширенных моделях имена процедур
автоматически становятся общедоступны-
ми.
- Ближние метки процедур могут переопре-
деляться в других процедурах.
- Для определения идентификатора, кото-
рый достижим вне текущей процедуры,
TASM2 #3-5/Док = 258 =
разрешена операция "::".
Masm51 и Ideal - Поддерживается синтаксис улучшенного
режима и текстовые макродирективы
Masm51 (то есть Instr, Catstr, Substr,
и Sizestr).
Вопрос: Когда следует использовать директивы DOSSEG или
.STACK?
Ответ: Когда вы разрабатываете модули Турбо Ассемблера,
предназначенные для компоновки с языками высокого уровня (типа
Турбо Си или Турбо Паскаля), то директивы DOSSEG или .STACK не
требуются, так как порядок сегментов и стек определяются компиля-
торами этих языков. Эти директивы определяют имена и порядок сег-
ментов, которые могут привести к конфликту м другим языком высо-
кого уровня. Однако, их следует задать (один раз) в каком-либо из
модулей автономной программы на Ассемблере. Директива DOSSEG не-
обходима только в том случае, если вы хотите, чтобы ваши сегменты
упорядочивались в соответствии с соглашениями фирмы Microsoft. Вы
можете задать собственный порядок сегментов, обеспечив, что ваши
сегменты будут обнаружены утилитой TLINK в том порядке, который
вы хотите. Полное описание этого процесса содержится в разделах
руководства, касающихся утилиты TLINK.
Вопрос: Какие параметры следует использовать для ассемблиро-
вания с помощью Турбо Ассемблера файлов, поставляемых с компиля-
тором языка Си фирмы Microsoft.
Ответ: При компиляции таких модулей на языке Ассемблера нуж-
но убедиться, что используются режимы MASM51 и QUIRKS. Например:
tasm /jmasm51 /jquirks filename
Вопрос: Как можно создать файл .COM?
Ответ: Исходный код ассемблера нужно компилировать с исполь-
зованием сверхмалой модели памяти (.MODEL TINY), а за сегментом
кода в программу нужно включить ORG 100h, например:
.MODEL TINY
.CODE
ORG 100h
start:
.... ; тело программы
ENDS start ; определяет точку входа при
TASM2 #3-5/Док = 259 =
; запуске
END
В программу, которая используется для формирования файла
.COM, не следует включать директиву .STACK.
Утилита TLINK будет создавать вместо файла .EXE файл .COM,
если задан параметр /t, например, команда:
tlink -t SHOW87
будет вместо файла SHOW87.EXE создавать файл SHOW87.COM.
При преобразовании файла .EXE в файл .COM имеются некоторые
ограничения. Эти ограничения описаны в руководстве по операцион-
ной системе DOS фирмы IBM (EXE2BIN).
Вопрос: Как с помощью Турбо Ассемблера можно ассемблировать
несколько файлов?
Ответ: Это можно сделать, используя в имени файла трафарет-
ные символы или разделив задаваемые в команде имена файлов знаком
"плюс" (+). Например, в следующей команде:
tasm filt + o*
будет ассемблироваться файл FILT.ASM, а также файлы .ASM, которые
начинаются с буквы 'o'.
Вопрос: Как можно ассемблировать несколько файлов, используя
для них разные параметры командной строки?
Ответ: В качестве разделителя в командной строке Турбо Ас-
семблера используется точка с запятой (;). Поэтому в одной ко-
мандной строке DOS вы можете в действительности задать несколько
команд ассемблирования. Например, следующая командная строка:
tasm /zi filt; o*
будет ассемблировать файл FILT.ASM с включением в него информации
для отладки, а затем ассемблировать все файлы .ASM, начинающиеся
с буквы 'o, без включения информации для отладки.
Вопрос: Макроассемблер фирмы Microsoft позволяет определять
переменные операционной среды, поэтому их не нужно вводить на
каждой командной строке. Можно ли это сделать при работе с Турбо
TASM2 #3-5/Док = 260 =
Ассемблером?
Ответ: Нет, однако, чтобы избежать ввода параметров при каж-
дом наборе командной строки, в Турбо Ассемблере предусмотрен еще
более гибкий способ. Каждый раз при запуске Турбо Ассемблера он
ищет в текущем каталоге, а затем в том каталоге, из которого он
запускается, (в DOS версии 3.х и старше) специальный файл с име-
нем TASM.CFG. Этот файл может содержать любую информацию, которая
содержится в командной строке. Он обрабатывается первым, после
чего обрабатывается командная строка, поэтому параметры командной
строки имеют более высокий приоритет по сравнению с параметрами,
которые содержатся в файле конфигурации TASM.CFG. Например, если
в командной строке вы всегда используете параметры:
/t /ml /zi /jJUMPS /jLOCALS
то следует создать файл TASM.CFG, содержащий следующие строки:
/t
/ml
/zi
/jJUMPS
/jLOCALS
Заметим, что при каждом запуске Турбо Ассемблера эти пара-
метры будут использоваться по умолчанию. Это означает, что если
это необходимо, вы можете для каждого из своих проектов иметь
различные файлы TASM.CFG. Если у вас имеется несколько проектов
(программ), содержащихся в одном подкаталоге, то вы можете для
каждого из них создать отдельный файл конфигурации и использовать
их, как косвенные командные файлы Турбо Ассемблера.
Вопрос: Что представляют собой косвенные командные файлы
Турбо Ассемблера?
Ответ: Это файлы, которые содержат частичные или полные ко-
мандные строки Турбо Ассемблера, и перед которыми указывается
знак @. Например, если у вас имеется файл с именем "FILE.CMD",
который содержит следующее:
/t
/ml
/zi
/jJUMPS
/jLOCALS
file1 +
TASM2 #3-5/Док = 261 =
file2 +
file3 +
file4
то вас следует использовать командную строку:
tasm @FILE.CMD
а не командную строку:
tasm /t /ml /zi /jJUMPS /jLOCALS file1+file2+file3+file4
Заметим, что символ @ в действительности не является частью
имени файла. Фактически, если вы присвоите файлу имя, которое на-
чинается с символа @, то Турбо Ассемблер будет обрабатывать его,
как косвенный командный файл.
Вопрос: Я выполняю компоновку своих функций на языке Ассемб-
лера с Турбо Си. Почему компоновщик сообщает, что все мои функции
неопределены?
Ответ: Убедитесь, что перед всеми именами функций, реализо-
ванных на Ассемблере и вызываемых в Турбо Си, вы указали символ
подчеркивания (_). Если вы используете упрощенные определения
сегментов и включаете в директиву .MODEL спецификатор языка Си,
то Турбо Ассемблер будет добавлять эти символы автоматически. Ва-
ша программа на языке Ассемблера должна ассемблироваться с разли-
чием в буквах верхнего и нижнего регистров (параметры /ML или
/MX). Подробности можно узнать в Главе 6 "Организация интерфейса
Турбо Ассемблера и Турбо Си".
Вопрос: Можно ли в качестве спецификатора параметра указы-
вать не прямую (/), а обратную косую черту (\)?
Ответ: Нет. Турбо Ассемблер (и MASM) будут обрабатывать это,
как имя файла, который находится в корневом каталоге на использу-
емом по умолчанию диске. Так как оба ассемблера обрабатывают сим-
вол пробела идентично запятой, это может привести к потере фай-
лов. Если вы случайно зададите следующую командную строку:
tasm \zi prid&joy.asm
то Турбо Ассемблер (и MASM) восприняли бы эту командную строку,
как указание ассемблировать файл с именем ZI.ASM, который можно
найти в корневом каталоге, и создать выходной файл (в текущем ка-
талоге) с именем PRID&JOY.ASM. (Заметим, что используемое по
TASM2 #3-5/Док = 262 =
умолчанию расширение объектного файла .OBJ явно переопределено в
.ASM.) Файл PRID&JOY.ASM будет перезаписан объектным файлом, либо
будет удален, если файл \ZI.ASM невозможно найти и успешно ас-
семблировать. В любой случае исходное содержимое файла
PRID&JOY.ASM будет потеряно.
я
|
|