=================================================================
Авторский коллектив "*.*"
под руководством Орлова С.Б.
ПРОГРАММА-СПРАВОЧНИК по системе
программирования ТУРБО АССЕМБЛЕР 2.0
РУКОВОДСТВО ПОЛЬЗОВАТЕЛЯ
#1/5 (Главы 1-5)
г.Москва, 1990 г.
=================================================================
Оглавление
Введение........................................................6
Требования к программному и аппаратному обеспечению.............7
О данном руководстве............................................7
Руководство пользователя........................................7
Соглашения по обозначениям......................................9
Глава 1. Установка Турбо Ассемблера в системе..................12
Файлы на дистрибутивном диске..................................12
Установка Турбо Ассемблера.....................................14
Глава 2. Начало работы с Турбо Ассемблером.....................16
Ваша первая программа на Турбо Ассемблере......................18
Ассемблирование вашей первой программы.........................19
Компоновка программы...........................................21
Запуск вашей первой программы..................................21
Что происходит?................................................22
Модификация вашей первой программы на Турбо Ассемблере.........23
Вывод информации на устройство печати..........................25
Ваша вторая программа на Турбо Ассемблере......................27
Запуск программы REVERSE.ASM ..................................28
Глава 3. Работа с командной строкой Турбо Ассемблера...........30
Запуск Турбо Ассемблера из DOS...............................30
Параметры командной строки.....................................34
Параметр /A..................................................35
Параметр /B..................................................35
Параметр /C..................................................35
Параметр /D..................................................36
Параметр /E..................................................36
Параметр /H или /?...........................................37
Параметр /I..................................................38
Параметр /J..................................................39
Параметр /KH.................................................39
Параметр /KS.................................................40
Параметр /L..................................................40
Параметр /LA.................................................41
Параметр /M..................................................41
Параметр /ML.................................................42
Параметр /MU.................................................43
Параметр /MV#................................................43
Параметр /MX.................................................44
Параметр /N..................................................44
Параметр /P..................................................45
Параметр Q...................................................46
Параметр /R..................................................46
Параметр /S..................................................47
Параметр /T..................................................47
Параметр /V..................................................48
Параметр /W..................................................48
Параметр /X..................................................50
Параметр /Z..................................................50
Параметр /ZD.................................................50
Параметр /ZI.................................................51
Косвенные командные файлы......................................53
Файлы конфигурации.............................................54
Глава 4. Природа языка Ассемблера..............................55
Архитектура компьютера.........................................55
Язык Ассемблера...............................................58
Процессоры 8088 и 8086.........................................61
Возможности процессора 8086....................................62
Память.........................................................63
Ввод и вывод...................................................66
Регистры.......................................................68
Регистр флагов.................................................70
Регистры общего назначения.....................................72
Регистр AX.....................................................73
Регистр BX.....................................................74
Регистр CX.....................................................75
Регистр DX.....................................................77
Регистр SI.....................................................78
Регистр DI.....................................................79
Регистр BP.....................................................81
Регистр SP.....................................................82
Указатель инструкций...........................................86
Сегментные регистры............................................87
Регистр CS.....................................................92
Регистр DS.....................................................92
Регистр ES.....................................................92
Регистр SS.....................................................93
Набор инструкций процессора 8086...............................94
Компьютеры IBM PC и XT.........................................99
Устройства ввода и вывода.....................................100
Системное программное обеспечение для семейства IBM PC........101
Операционная система DOS......................................103
Получение символов с клавиатуры...............................105
Вывод символов на экран.......................................106
Вывод символов на экран.......................................108
Базовая система ввода-вывода..................................110
Выбор режима экрана...........................................110
Иногда необходимо обратиться к аппаратным средствам...........112
Другие ресурсы................................................112
Глава 5. Основные элементы программы на языке Ассемблера......113
Элементы и структура программы на языке Ассемблера............113
Зарезервированные слова.......................................116
Формат строки.................................................118
Метки.........................................................119
Мнемоники инструкций и директивы..............................123
Директива END.................................................124
Операнды......................................................127
Регистровые операнды..........................................128
Операнды-константы............................................129
Выражения.....................................................132
Операнды-метки................................................133
Режимы адресации к памяти.....................................136
Комментарии...................................................147
Директивы определения сегментов...............................151
Упрощенные директивы определения сегментов....................151
Директивы .STACK, .CODE и .DATA...............................152
Директива DOSSEG..............................................157
Директива .MODEL..............................................158
Другие упрощенные директивы определения сегментов.............161
Стандартные директивы определения сегментов...................162
Директива SEGMENT.............................................164
Директива ENDS................................................164
Директива ASSUME..............................................164
Стандартные или упрощенные директивы определения сегментов?...169
Выделение данных..............................................169
Биты, байты и основания.......................................171
Представление числовых значений...............................175
Выбор основания по умолчанию..................................181
Инициализированные данные.....................................183
Инициализация массивов........................................185
Инициализация строк символов..................................187
Инициализация выражений и меток...............................189
Неинициализированные данные...................................191
Именованные ячейки памяти.....................................193
Перемещение данных............................................197
Выбор размера данных..........................................199
Данные со знаком и без знака..................................202
Преобразование размеров данных................................204
Доступ к сегментным регистрам.................................207
Перемещение данных в стек и из стека..........................209
Обмен данными.................................................210
Ввод-вывод....................................................211
Операции......................................................213
Арифметические операции.......................................213
Сложение и вычитание..........................................214
32-разрядные операнды.........................................215
Увеличение и уменьшение.......................................218
Умножение и деление...........................................220
Изменение знака...............................................224
Логические операции...........................................225
Сдвиги и циклические сдвиги...................................228
Циклы и переходы..............................................233
Безусловные переходы..........................................233
Условные переходы.............................................238
Циклы.........................................................242
Подпрограммы..................................................247
Выполнение подпрограмм........................................248
Передача параметров...........................................253
Возвращаемые значения.........................................254
Сохранение регистров..........................................254
Пример программы на языке Ассемблера..........................256
Введение
-----------------------------------------------------------------
Турбо Ассемблер фирмы Borland представляет собой многопро-
ходный ассемблер с разрешением опережающих ссылок, скоростью ас-
семблирования до 48000 строк в минуту (на компьютере IBM PS/2,
модель 60), совместимый с макроассемблером фирмы Microsoft MASM и
дополнительной возможностью использования режима расширенного
синтаксиса. Независимо от вашего опыта в программировании вы, не-
сомненно, оцените эти особенности, а также ряд других средств,
которые значительно облегчают программирование на Ассемблере.
Среди таких средств можно кратко упомянуть следующие (подробно
они будут описаны позднее):
- полная поддержка процессора 80386;
- улучшенная синтаксическая проверка типов;
- упрощенные директивы определения сегментов;
- улучшенное управление листингом;
- расширения инструкций POP и PUSH;
- расширенный оператор CALL с аргументами и необязательным
параметром языка;
- локальные метки;
- локальные идентификаторы в стеке и аргументы вызова в про-
цедурах;
- структуры и объединения;
- вложенные директивы;
- режим QUIRK, эмулирующий MASM;
- полная отладка на уровне исходного текста с помощью Турбо
отладчика;
- встроенная утилита генерации перекрестных ссылок (TCREF);
- файлы конфигурации и командные файлы.
Турбо Ассемблер является мощным ассемблером, работающим с
командной строкой, который воспринимает ваши исходные файлы (фай-
лы с расширением .ASM) и создает из них объектные модули (файлы с
расширением .OBJ). После этого вы можете использовать програм-
му-компоновщик фирмы Borland TLINK.EXE, отличающуюся высокой ско-
ростью компоновки, для компоновки полученных объектных модулей и
создания выполняемых файлов (файлов с расширением .EXE).
Турбо Ассемблер создан для работы с процессорами серии 80х86
и 80х87 (более подробно набор инструкций процессоров серии
80х86/80х87 описан в соответствующих руководствах фирмы Intel).
Требования к программному и аппаратному обеспечению
-----------------------------------------------------------------
Турбо Ассемблер работает на компьютерах семейства IBM PC,
включая модели XT, AT и PS/2, а также на полностью совместимых с
ними компьютерах. Для работы Турбо Ассемблера требуется операци-
онная система MS-DOS (версии 2.0 или более поздняя) и не менее
256К оперативной памяти.
Турбо Ассемблер генерирует инструкции процессоров 8086,
80186, 80286 и 80386, а также инструкции с плавающей точкой для
арифметических сопроцессоров 8087, 80287 и 80287.
О данном руководстве
-----------------------------------------------------------------
Описание Турбо Ассемблера поставляется в виде двух пособий:
"Руководства пользователя по Турбо Ассемблеру" (данный текст) и
"Справочного руководства по Турбо Ассемблеру". В "Руководстве
пользователя" даются основные инструкции по использованию Турбо
Ассемблера и приводится исчерпывающее руководство по программиро-
ванию на Турбо Ассемблере. В "Справочном руководстве" описываются
операторы, предопределенные символы и директивы, используемые
Турбо Ассемблером.
Рассмотрим содержание "Руководства пользователя" более под-
робно.
Руководство пользователя
-----------------------------------------------------------------
В Главе 1 "Установка Турбо Ассемблера в системе" рассказы-
вается о файлах, содержащихся на дистрибутивном диске, и о том,
что нужно делать, чтобы установить в системе Турбо Ассемблер.
В Главе 2 "Начало работы с Турбо Ассемблером" содержится
введение в язык программирования Ассемблер и приводится несколько
простых программ, чтобы познакомить вас с параметрами, используе-
мыми в командной строке.
В Главе 3 "Работа с командной строкой" подробно описываются
все параметры командной строки, а также рассказывается о файле
конфигурации и командных файлах.
В Главе 4 "Природа языка Ассемблера" обсуждаются компьютеры
в целом и процессор 8088 в частности.
В Главе 5 "Основные элементы программы на Ассемблере" описы-
ваются основные компоненты Ассемблера, приводится некоторая необ-
ходимая информация о его директивах, инструкциях, обращению к па-
мяти, сегментах и т.д.
В Глава 6 "Более подробно о программировании на Ассемблере"
развивается тема Главы 5: более подробно рассказывается о прог-
раммировании на Турбо Ассемблере, обсуждаются некоторые преиму-
щества Турбо Ассемблера, более детально описываются директивы,
строковые инструкции и т.д. В данной главе приводятся также неко-
торые типичные ошибки, с которыми вы можете встретиться при прог-
раммировании.
В Главе 7 "Интерфейс между Турбо Ассемблером и Турбо Си"
описывается, как использовать совместно с языком Ассемблера язык
программирования высокого уровня Турбо Си. Уточняется, как можно
компоновать модули Ассемблера с модулями Турбо Си, а также как
вызывать из Турбо Си функции Турбо Ассемблера.
В Главе 8 "Взаимодействие Турбо Ассемблера с Турбо Паскалем"
рассказывается, как можно организовать в ваших программах на язы-
ке Ассемблера интерфейс с Турбо Паскалем. В качестве примеров
приводятся простые программы.
В Главе 9 "Развитое программирование на Турбо Ассемблере"
более подробно освещается все то, о чем рассказывалось в предыду-
щих частях (префиксы переопределения сегментов, макрокоманды,
директивы определения сегментов и т.д.).
В Главе 10 "Процессор 80386 и другие процессоры" описывается
программирование с использованием процессора 80386.
В Главе 11 "Улучшенный режим Турбо Ассемблера" рассказывает-
ся об улучшенном режиме (Ideal Mode) и для чего его желательно
использовать.
Руководство дополнено также тремя приложениями. В первых
двух приложениях описывается интерфейс Турбо Ассемблера с Турбо
Бейсиком и Турбо Прологом, а последнее посвящено ответам на общие
вопросы.
Соглашения по обозначениям
-----------------------------------------------------------------
В данном руководстве используются следующие соглашения:
Обозначение | Описание обозначения
--------------------------------------------------------------
| Столбец из точек перед строками, где описыва-
. | ется синтаксис или приводится пример програм-
. | мы, говорит о том, что фрагмент программы
. | опущен.
|
выражение | Слова, указанные в примерах строчными буква-
| ми, показывают, что вместо них должны быть
| подставлены значения. Например, ниже при-
| веден синтаксис оператора ОFFSET:
|
| OFFSET выражение
|
| Он показывает, что за оператором OFFSET мо-
| жет следовать любое выражение. При записи
| исходного кода в соответствии с этим синтак-
| сисом вы можете записать:
|
| OFFSET here+6
|
| где here+6 является выражением.
|
[[необ_элем]] | В двойные квадратные скобки заключается не-
| обязательный синтаксический элемент. Напри-
| мер, синтаксис индексного оператора показан
| следующим образом:
|
| [[выраж.1]][выраж.2]
|
| Это указывает на то, что "выраж.1" является
| необязательным, поскольку оно заключено в
| двойные квадратные скобки. Однако "выраж.2"
| является обязательным и должно быть заключено
| в скобки.
|
| При записи кода, соответствующего данному
| синтаксису, вы должны записать [bx], отбросив
| необязательное "выраж.1", или ввести test(5),
| используя test в качестве "выраж.1".
|
{выбор1|выбор2}| Фигурные скобки и вертикальные разделители
| указывают на необходимость выбора между двумя
| или более элементами. Варианты выбора заклю-
| чаются в фигурные скобки и разделяются верти-
| кальной чертой. Вы должны выбрать один из ва-
| риантов.
|
| Например, необязательный параметр /W (уровень
| предупреждающих сообщений об ошибке) имеет
| следующий синтаксис:
|
| /W{0|1|2}
|
| Вы можете ввести /W0, /W1 или /W2, указав та-
| ким образом желаемый уровень предупреждений.
| Однако указывать /W3 не допускается, посколь-
| ку 3 не содержится ни в одном из вариантов
| выбора, которые указаны в фигурных скобках.
|
Повторяющиеся | Три точки, следующие за элементами, показыва-
элементы... | ют, что можно в таком же виде ввести большее
| количество элементов. Ниже, например, приве-
| ден синтаксис директивы PUBLIC:
|
| PUBLIC имя[[,имя]]...
|
| Точки за вторым элементом "имя" указывают,
| что вы можете ввести столько имен, сколько
| захотите, пока каждому из них будет предшест-
| вовать запятая. Однако, поскольку первое имя
| не заключено в квадратные скобки, вы должны
| ввести по крайней мере одно имя.
|
Определяемые | В кавычки заключаются определяемые в тексте
термины и | термины. Например, термин "промежуточный",
"подсказки" | если он определяется в первый раз, заключает-
| ся в кавычки.
|
НАЗВАНИЯ КЛАВИШ| Заглавными буквами указываются также названия
| клавиш и последовательностей клавиш, которые
| вы должны нажимать. В качестве примеров можно
| привести ENTER и CONTROL+C.
--------------------------------------------------------------
Пример: В следующем примере показано, как в данном руковод-
стве используются соглашения по обозначениям.
TASМ[[необяз_параметры]] исх_файл[[, [[объектн_файл]][[,
[[файл_листинга]][[, [[файл_перекр_ссылок]]]]]]]][[;]]
Этот синтаксис показывает, что вы должны сначала ввести имя
программы (TASM), затем можно ввести какое-то количество необяза-
тельных параметров, обязательно нужно ввести имя исходного файла
"исх_файл", затем можно ввести имя объектного файла
"объектн_файл", перед которым ставится запятая, и можно ввести
также имена файла листинга "файл_листинга" (перед ним также
должна указываться запятая, отделяющая это имя от имен объектного
и исходного файлов) и имя файла перекрестных ссылок
"файл_перекр_ссылок" (перед ним также нужно указать запятую, от-
деляющую это имя от имен остальных файлов).
Когда в руководстве речь идет о компьютерах IBM PC и совмес-
тимых с ними, то под этим мы подразумеваем любой компьютер, в
котором используется процессор 8088, 8086, 80186, 80286 или 80386
(все процессоры этой серии мы обозначаем обычно, как 80х86).
Глава 1. Установка Турбо Ассемблера в системе
-----------------------------------------------------------------
Перед тем, как познакомиться с программированием на Турбо
Ассемблере, вам нужно сделать следующее. Возьмите дистрибутивные
диски Турбо Ассемблера и сделайте для каждого из них (с помощью
утилиты DOS) рабочую копию. После этого исходные (дистрибутивные)
диски уберите в надежное место.
Если вы собираетесь использовать Турбо Ассемблер вместо MASM
(макроассемблер фирмы Microsoft), прочитайте Приложение В в
"Справочном руководстве" и посмотрите, в чем поведение Турбо Ас-
семблера отличается от MASM.
Примечание: Перед началом работы ознакомьтесь с содер-
жимым файла READ.ME, в котором может содержаться информация
о последних изменениях, а также дополнения к руководствам.
Файлы на дистрибутивном диске
-----------------------------------------------------------------
На дистрибутивных дисках Турбо Ассемблера содержатся следую-
щие файлы:
- TASM.EXE: Турбо Ассемблер;
- TLINK.EXE: Турбо компоновщик;
- MAKE.EXE: Утилита MAKE, работающая в режиме командной
строки;
- TLIB.EXE: Турбо библиотекарь;
- README.COM: Программа для вывода на экран текста в файле
README;
- README: последняя информация о программном обеспечении и
документации;
- TCREF.EXE: Утилита генерации перекрестных ссылок исходных
файлов;
- OBJXREF.COM: Утилита генерации перекрестных ссылок объек-
тных файлов;
- GREP.COM: Утилита GREP;
- TOUCH.EXE: Утилита обновления файлов;
- INSTALL.EXE: программа установки;
- MMACROS.MAC: архивный файл макрокоманд режима MASM.
Тексты использованных в руководстве примеров содержатся в
следующих файлах:
HELLO.ASM
HELLO2.ASM
HELLOPRN.ASM
REVERSE.ASM
ECHOCHAR.ASM
MODCHAR.ASM
DELAY.ASM
DSLYSTR.ASM
USE_ES.ASM
STDSEG.ASM
STRINGS.ASM
PRNTSTR.ASM
CNTWORDS.ASM
MAIN.ASM
SUB1.ASM
PLUSONE.C
PLUSONE.ASM
SQRETBLE.C
SQRTBLE2.C
STRINGUP.C
DOTOTAL.ASM
SHOWTOT.C
DOTOTAL2.ASM
TOGLFLAG.C
TOGFLAG.ASM
CALLCT.C
COUNT.ASM
COUNTLG.ASM
CALCAVG.C
AVERAGE.ASM
SAMPLE.PAS
ASMPROC.ASM
TSAMPLE.PAS
HEXTEST.PAS
HEXSTR.ASM
XCHANGE.PAS
XCHANGE.ASM
ENVTEST.PAS
ENVSTR.ASM
Установка Турбо Ассемблера
-----------------------------------------------------------------
На диске INSTALL имеется программа с именем INSTALL.EXE, ко-
торая может помочь вам установить в системе Турбо Ассемблер. Эта
программа имеет две возможности:
1. Установка на жесткий диск. При этом вы можете выбрать
подкаталоги, в которые будут загружены файлы.
2. Установка на гибкий диск. Эта возможность позволяет вам
установить на гибкий диск необходимые для использования Турбо Ас-
семблера файлы при наличии в системе двух дисководов для гибких
дисков.
Чтобы начать процесс установки, измените текущий дисковод на
тот, где содержится программа INSTALL, и наберите INSTALL. В рам-
ке в нижней части экрана вам будут выводиться подсказки и инс-
трукции. Например, если вы выполняете установку с диска A, введи-
те:
INSTALL
Перед началом установки ознакомьтесь с информацией о данной
реализации Турбо Ассемблера (файл READ.ME).
Примечание: Если вы работаете в системе, где использу-
ется дисплей на жидких кристаллах, то перед запуском прог-
раммы INSTALL нужно установить черно-белый режим с помощью
команды:
mode bw80
Можно также указать программе INSTALL, что нужно рабо-
тать в черно белом режиме. Для этого используется параметр
/b:
INSTALL /b
Можно установить Турбо Ассемблер и без помощи утилиты
INSTALL. Если у вас имеется жесткий диск, создайте каталог для
TASM.EXE (где вы будете наиболее часто его использовать). Затем
скопируйте TASM.EXE в этот каталог. Если вы используете систему
только с гибкими дисками, скопируйте TASM.EXE на один из гибких
дисков.
После этого скопируйте в тот же каталог все утилиты, с кото-
рыми вы собираетесь работать. Это все. В следующей главе вы нау-
читесь основам программирования с помощью Турбо Ассемблера TASM.
Глава 2. Начало работы с Турбо Ассемблером
-----------------------------------------------------------------
Если вы никогда ранее не программировали на языке Ассембле-
ра, то начните с данной главы. Возможно вам приходилось слышать,
что программирование на Ассемблере - это дело темное, доступное
только посвященным и мудрецам. Не верьте этому. Язык Ассемблера -
это не более чем человеческая форма языка самого компьютера, а
он, как и можно было предположить, в высшей степени логичен. Как
можно также догадаться, язык Ассемблера - это очень мощный язык.
Фактически, программирование на Ассемблере представляет собой
единственный способ реализации всего спектра возможностей процес-
соров серии 80х86 фирмы Intel, являющихся "сердцем" всех компь-
ютеров семейства IBM PC и совместимых с ними компьютеров.
Вы можете писать программы целиком на языке Ассемблера или,
если захотите, использовать язык Ассемблера в программах, напи-
санных на Турбо Си, Турбо Паскале, Турбо Прологе, Турбо Бейсике,
и других языках. В любом случае с помощью языка Ассемблера вы
сможете разрабатывать компактные и быстрые программы. Наряду со
скоростью большое значение в программе на языке Ассемблера имеет
также возможность управления всеми аспектами работы компьютера,
до последнего такта системного таймера.
В данной главе вы познакомитесь с языком Ассемблера и опро-
буете уникальные свойства программирования на нем. Сначала вы
введете и запустите несколько работающих программ, написанных на
Ассемблере. Это даст вам возможность как почувствовать сам язык,
так и познакомиться с работой на Ассемблере. Затем вы познакоми-
тесь с общими характеристиками компьютеров, в частности, с про-
цессором 8086, что позволить вам оценить достоинства языка Ас-
семблера в плане того, что касается процессора 8086. Мы коснемся
также отдельных аспектов программирования на Ассемблере, специ-
фичных для компьютеров IMP PC.
Тему данной главы продолжает Глава 5 ("Основные элементы
программы на языке Ассемблера"), в которой описывается структура
программы на Ассемблере, основные ее элементы, и все, что вы уже
узнали в этих двух главах суммируется в исчерпывающей програм-
ме-примере.
В Главе 6 ("Более подробно о программировании на Ассембле-
ре") и Главе 9 ("Развитое программирование на Турбо Ассемблере")
продолжается описание программирования на Ассемблере (продвинутый
этап). При этом рассказывается о моделях памяти, макрокомандах и
других вопросах развитого программирования.
На самом деле, изучив несколько глав, вы, конечно, не сможе-
те стать экспертом в программировании на Ассемблере. Просто ус-
воите основы языка и сможете начать писать свои собственные прог-
раммы. Мы настоятельно рекомендуем вам дополнительно к данной до-
кументации использовать одну из превосходных книг, посвященных
программированию на языке Ассемблера и архитектуре IBM PC (см.
перечень в конце данного руководства). Кроме того, мы рекомендуем
вам использовать "Техническое справочное руководство по операци-
онной системе DOS", "Справочник по интерфейсу с базовой системой
ввода-вывода" и "Справочное руководство по персональному компью-
теру XT" фирмы IBM. (Можно воспользоваться также одной из немно-
гочисленных книг, изданных по данной тематике в СССР, например
книгой по Ассемблеру для процессоров 8088 или книгой Бредли.) В
руководствах по DOS и BIOS или компьютеру IBM часто также описы-
вается интерфейс Ассемблера с системным программным обеспечением
и аппаратным обеспечением персональных компьютеров фирмы IBM.
Перед дальнейшим изучением данной главы вам может потребо-
ваться обратиться к Главе 3 "Справочник по командным строкам",
чтобы познакомиться с параметрами командной строки. Вам понадо-
биться также установить в системе Турбо Ассемблер (сделать рабо-
чие копии дисков Турбо Ассемблера или скопировать файлы с дистри-
бутивных дисков на жесткий диск), как описано в Главе 1 "Установ-
ка Турбо Ассемблера в системе".
Наконец, нужно упомянуть о том, что язык Ассемблера - это
сложная тема и вам потребуется много знать для того, чтобы напи-
сать даже относительно простую программу на этом языке. Иногда в
примерах будут использоваться те средства, которые перед этим не
обсуждались (ведь надо же с чего-то начать). Пусть это вас не
смущает, все будет объяснено позднее. Если же, однако, вас заин-
тересует какое-то конкретное средство, обратитесь к главе "Дирек-
тивы" "Справочного руководства".
Теперь пора приступить к первой программе.
Ваша первая программа на Турбо Ассемблере
-----------------------------------------------------------------
В программировании первой программой традиционно является
программа, выводящая на экран сообщение "Привет!". Не будет иск-
лючением и наша программа, поскольку это является хорошей отправ-
ной точкой. Войдите в текстовый редактор (один из тех редакторов,
которые формируют файлы в коде ASCII) и введите следующие строки
программы под названием HELLO.ASM:
DOSSEG
.MODEL SMALL
.STACK 100h
.DATA
Message DB 'Привет!',13,10,'$'
.CODE
mov ax,@Data
mov dx,ax ; установить регистр DS таким
; образом, чтобы он указывал
; на сегмент данных
mov ah,9 ; функция DOS вывода строки
mov dx,OFFSET Message ; ссылка на сообщение "Привет!"
int 21h ; вывести "Привет!" на экран
mov ah,4ch ; функция DOS завершения
; программы
int 21h ; завершить программу
END
После того, как вы введете эту программу, сохраните ее на
диске.
Если вы знакомы с языками Си или Паскаль, вам может пока-
заться, что эта версия программы несколько длинновата. Это дей-
ствительно так, программы на Ассемблере длиннее, поскольку каждая
инструкция Ассемблера выполняет меньше функций, чем инструкция
Паскаля или Си. С другой стороны, вам предоставляется свобода, и
вы можете комбинировать эти инструкции Ассемблера так, как захо-
тите. Это означает, что в отличие от языков Си и Паскаль, Ассем-
блер позволяет вам программировать компьютер таким образом, что
он будет делать все, на что способен. Часто это стоит нескольких
дополнительных строк.
Ассемблирование вашей первой программы
-----------------------------------------------------------------
После того, как вы сохранили файл HELLO.ASM, вы захотите за-
пустить программу. Однако, перед тем, как вы сможете ее запус-
тить, вам потребуется преобразовать программу в выполняемый вид.
Как показано на Рис. 2.1, где изображен полный цикл создания
программы (редактирование, ассемблирование, компоновка и выполне-
ние), это потребует двух дополнительных шагов - ассемблирования и
компоновки.
Создание новой программы
|
-------------------------------->|
| |
| Редактирование
| |
| V
| ------------------------------------------------
| | Исходный файл Ассемблера HELLO.ASM |
| ------------------------------------------------
| |
| Ассемблирование
| |
| V
| -----------------------------------------------
| | Объектный файл HELLO.OBJ |
| -----------------------------------------------
| |
| Компоновка
| |
| V
| -----------------------------------------------
| | Выполняемый файл HELLO.EXE |
| -----------------------------------------------
| |
| Выполнение
| ---------------------- |
----( Если нужны изменения )------
----------------------
Рис. 2.1 Редактирование, ассемблирование, компоновка и вы-
полнение программы.
На этапе ассемблирования ваш исходный код (текст программы)
превращается в промежуточную форму, которая называется объектным
модулем, а на этапе компоновки один или несколько модулей комби-
нируются в выполняемую программу. Ассемблирование и компоновку вы
можете выполнять с помощью командной строки.
Для ассемблирования файла HELLO.ASM наберите команду:
TASM hello
и нажмите клавишу ENTER. Если вы не задали другое имя, файл
HELLO.ASM будет ассемблирован в файл HELLO.OBJ. (Заметим, что
расширение имени файла вводить не требуется. Турбо Ассемблер под-
разумевает в этом случае, что файл имеет расширение .ASM.) На эк-
ране вы увидите следующее:
Turbo Assembler Version 2.0 Copyright (C) 1990 (1)
by Borland International
Inc.
Assembling file: HELLO.ASM (2)
Error messages: None (3)
Warning messages: None (4)
Remaining memory: 266K (5)
1 - Турбо Ассемблер, версия 2.0; авторские права фирмы Borland,
1990 г.; 2 - ассемблирован файл HELLO.ASM; 3 - сообщения об ошиб-
ках: нет; 4 - предупреждающие сообщения: нет; 5 - остается памя-
ти: 266К
Если вы введете файл HELLO.ASM в точности так, как показано,
то вы не получите никаких предупреждающих сообщений или сообщений
об ошибках. Если вы получаете такие сообщения, они появляются на
экране наряду с номерами строк, указывающими строки, где содер-
жатся ошибки. При получении сообщений об ошибках проверьте исход-
ный код (текст) программы и убедитесь, что он выглядит точно так,
как исходный код в нашем примере, а затем снова ассемблируйте
программу.
Компоновка программы
-----------------------------------------------------------------
После ассемблирования файла HELLO.ASM вы продвинулись только
на один шаг в процессе создания программы. Теперь, если вы ском-
понуете только что полученный объектный код в выполняемый вид, вы
сможете запустить программу.
Для компоновки программы используется программа TLINK,
представляющая собой поставляемый вместе с Турбо Ассемблером ком-
поновщик. Введите командную строку:
TLINK HELLO
Здесь опять не требуется вводить расширение имени файла.
TLINK по умолчанию предполагает, что этим расширением является
расширение .OBJ. Когда компоновка завершится (самое большее через
несколько секунд), компоновщик автоматически присвоит файлу с
расширением .EXE имя, совпадающее с именем вашего объектного фай-
ла (если вы не определили другое имя). При успешной компоновке на
экране появляется сообщение:
Turbo Linker Version 3.0 Copyright (c) 1987, 1990 by by Bor-
land International Inc.
В процессе компоновки могут возникнуть ошибки (в данной
программе это маловероятно). Если вы получили сообщения об ошиб-
ках компоновки (они выводятся на экран), измените исходный код
программы так, чтобы он в точности соответствовал тексту програм-
мы в приведенном выше примере, а затем снова выполните ассембли-
рование и компоновку.
Запуск вашей первой программы
-----------------------------------------------------------------
Теперь программу можно запустить на выполнение. Для этого в
ответ на подсказку операционной системы DOS введите hello и наж-
мите ENTER. На экран выведется сообщение:
Привет!
Пока это все. Вы только что создали и выполнили свою первую прог-
рамму на Ассемблере!.
Что происходит?
-----------------------------------------------------------------
Теперь, когда вы получили и выполнили программу HELLO.ASM,
давайте вернемся назад и рассмотрим подробно, что происходит с
момента ввода текста программы до ее выполнения.
Когда вы первый раз вводите исходный код программы на ассем-
блере, ее текст сохраняется текстовым редактором в памяти. Если
питание компьютера в этот момент по какой-то причине будет выклю-
чено, исходный код будет потерян, поэтому мы рекомендуем вам по-
чаще сохранять исходный код, чтобы избежать такой трагедии. После
того, как вы сохраните исходный код на диске, постоянная копия
текста будет записана в файл HELLO.ASM, который сохранится и пос-
ле выключения или сбоя питания (однако этот файл может быть поте-
рян в результате порчи диска, поэтому рекомендуется регулярно де-
лать резервные копии файлов). Файл HELLO.ASM - это стандартный
текcтовый файл в коде ASCII. Вы можете вывести его на экран, вве-
дя в ответ на подсказку DOS команду:
type hello.asm
Его можно также отредактировать с помощью текстового редактора.
Когда вы ассемблируете файл HELLO.ASM, Турбо Ассемблер прев-
ращает текст инструкций в этом файле в их двоичный эквивалент в
объектном файле HELLO.OBJ. Этот файл является промежуточным фай-
лом (промежуточным звеном в процессе перехода от текстового к вы-
полняемому файлу). Файл HELLO.OBJ содержит всю информацию, необ-
ходимую для создания выполняемого кода из инструкций, содержащих-
ся в файле HELLO.ASM, но она записана в виде, который позволяет
комбинировать ее с другими объектными файлами для создания одной
программы. В Главе 5 ("Более подробно о программировании на Турбо
Ассемблере") вы увидите, насколько полезным это может оказаться
при разработке больших программ.
При компоновке файла HELLO.OBJ утилита TLINK преобразует его
в выполняемый файл HELLO.EXE, который вы запускаете, введя hello
в ответ на подсказку DOS.
Теперь введите команду:
dir hello.*
При этом будет выведен список файлов HELLO на диске. Это будут
файлы HELLO.ASM, HELLO.OBJ, HELLO.EXE и HELLO.MAP.
Модификация вашей первой программы на Турбо Ассемблере
-----------------------------------------------------------------
Теперь снова войдем в редактор и модифицируем программу та-
ким образом, чтобы она могла воспринимать какие-то данные из
внешней среды (этой "внешней средой" будете вы, а вводимые данные
будут набираться на клавиатуре). Измените программу следующим об-
разом:
DOSSEG
.MODEL SMALL
.STACK 100h
.DATA
TimePrompt DB 'Это время после полудня? (ДА/НЕТ) - [Y/N]$'
GoodMorningMessage LABEL BYTE
DB 13,10,'Доброе утро!',13,10,'$'
GoodAfternoonMessage LABEL BYTE
DB 13,10,'Здравствуйте!',13.10,'$'
.CODE
mov ax,@Data
mov dx,ax ; установить регистр DS таким
; образом, чтобы он указывал
; на сегмент данных
mov dx,OFFSET TimePrompt ; ссылка на сообщение-запрос
mov ah,9 ; функция DOS вывода строки
int 21h ; получить ответ из одного
; символа
cmp al,'Y' ; указано время после полудня
; (прописная буква Y)
jz IsAfternoon ; да, время указано после
; полудня
cmp al,'y' ; указано время после полудня
; (строчная буква y)
jnz IsMorning ; нет, время указано до
; полудня
IsAfternoon:
mov dx,OFFSET GoodAfternoonMessage ; указывает на
; приветствие "Здравствуйте"
jmp DisplayGreeting
IsMorning:
mov dx,OFFSET GoodMorningMessage ; указывает на
; приветствие "Доброе утро"
DisplayGreeting:
mov ah,9 ; функция DOS вывода сообщения
int 21h ; вывести соответствующее
; сообщение
mov ah,4ch ; функция DOS завершения
; программы
int 21h ; завершить программу
END
Таким образом вы добавили в программу два очень важных новых
средства: возможность ввода и принятие решений. Эта программа
запрашивает у вас, является ли вводимое время временем после по-
лудня, воспринимая ответ (один символ) с клавиатуры. Если таким
ответом будет буква Y в верхнем или нижнем регистре (что означает
ответ ДА), то программа выводит сообщение "Здравствуйте!", в про-
тивном случае выводится сообщение "Доброе утро!". В данной прог-
рамме имеются все основные элементы полезной программы: ввод из
информации внешней среды, обработка данных и принятие решения.
Сохраните эту модифицированную программу на диске. (При этом
исходная версия файла HELLO.ASM заменится модифицированным кодом,
поэтому старая версия будет потеряна.) После этого заново ассем-
блируйте и скомпонуйте программу, как в предыдущем примере. За-
пустите ее снова, введя hello в ответ на подсказку DOS. Выведется
сообщение:
Это время после полудня? (ДА/НЕТ) - [Y/N]
Курсор будет мерцать у последнего символа в ожидании ввода
ответа. Нажмите Y. Программа ответит:
Здравствуйте!
Таким образом HELLO.ASM стала теперь интерактивной програм-
мой с принятием решений.
В ходе ассемблирования вы, конечно, получите различные сооб-
щения об ошибках из-за неправильного набора программы и ошибках в
синтаксисе. Турбо Ассемблер перехватывает такие ошибки, сообщая о
них. Выводимые сообщения об ошибках разбиваются на две категории:
предупреждения и ошибки. Если Турбо Ассемблер обнаруживает что-то
подозрительное, но необязательно неверное, он выводит предупреж-
дающее сообщение. Иногда предупреждающие сообщения можно игнори-
ровать, но всегда лучше их проверить и убедиться в том, что вы
понимаете суть проблемы. При обнаружении чего-либо явно непра-
вильного в вашей программе, что делает невозможным завершение ас-
семблирования и формирование объектного файла, Турбо Ассемблер
выводит сообщение об ошибке.
Другими словами, предупреждающие сообщения не свидетельству-
ют о критических ошибках, в то время как ошибки, о которых гово-
риться в сообщениях об ошибках, должны быть исправлены перед за-
пуском программы. В Приложении E "Справочного руководства" содер-
жится полный перечень сообщений об ошибках и предупреждающих со-
общений.
Как и любой язык программирования, Турбо Ассемблер не может
распознавать ошибки в логике программы. Турбо Ассемблер только
сообщает вам, может ли ваш ассемблируемый код быть выполнен в том
виде, как он введен, но он не может сделать вывод о том, будет ли
программа работать так, как вы этого хотите. Об этом можете су-
дить только вы сами.
Не беспокойтесь, если вы сейчас не совсем улавливаете смысл
приведенной в примере программы на Ассемблере. Даже программис-
там, имеющим опыт работы на других языках, требуется некоторое
время, чтобы освоиться с языком Ассемблера процессора 8086. Сей-
час важно, чтобы вы просто получили представление о том, как выг-
лядит программа на Ассемблере. Далее в этой главе и в главе "Ос-
новные элементы программы на языке Ассемблера" мы опишем каждый
элемент представленной программы.
Чтобы получить распечатку программы (вывести ее на устройс-
тво печати), обратитесь к руководству по редактору текстов. Ис-
ходные файлы Турбо Ассемблера представляют собой обычные тексто-
вые файлы в коде ASCII (американский стандартный код обмена
информацией), поэтому вы можете также напечатать исходный текст
программы на Ассемблере (на устройстве печати) с помощью команды
PRINT, введя ее в ответ на подсказку операционной системы DOS.
Вывод информации на устройство печати
-----------------------------------------------------------------
Устройство печати (принтер) - это очень полезное устройство.
Вам может не только потребоваться распечатать текст программы, но
и передать на принтер выводимую информацию. Следующая версия
программы выводит информацию вместо экрана на принтер:
DOSSEG
.MODEL SMALL
.STACK 100h
.DATA
Message DB 'Привет!',13,10,'$'
Message_Length EQO $ - Message
.CODE
mov ax,@Data
mov dx,ax ; установить регистр DS таким
; образом, чтобы он указывал
mov ah,40h ; функция DOS вывода строки
; на устройство
mov bx,4 ; описатель принтера
mov cx,Message_Length ; число печатаемых символов
mov dx,OFFSET Message ; ссылка на "Привет!"
int 21h ; вывести "Привет!" принтер
mov ah,4ch ; функция DOS завершения
; программы
int 21h ; завершить программу
END
В данной версии программы функция DOS вывода строки на экран
заменена на функцию DOS, которая передает информацию на выбранное
устройство или в файл, в данном случае - на принтер. Введите и
запустите программу. Посмотрите, как она напечатает на принтере
слово "Привет!". (Перед запуском программы не забудьте ее сохра-
нить при завершении работы в редакторе. При этом программа будет
сохранена в файле HELLO.ASM, а предыдущая версия программы будет
потеряна.)
Вы можете модифицировать эту программу таким образом, чтобы
она снова посылала выходные данные на экран, а не на устройство
печати, заменив просто строку:
mov bx,4 ; описатель принтера
на строку:
mov bx,1 ; описатель стандартного вывода
Сделайте такое изменение, а затем снова выполните перекомпи-
ляцию и перекомпоновку программы перед ее запуском. Запустив
программу, вы увидите, что сообщение появится на экране, а пос-
ледним символом будет графический символ перевода формата (кружок
с крестиком внизу). Этот символ программа передает на принтер,
чтобы вынудить его после вывода сообщения выполнить перевод стра-
ницы. Поскольку на экране страниц нет, он ничего не знает о пере-
воде формата и просто выводит на экран символ из набора символов
компьютера РС.
Ваша вторая программа на Турбо Ассемблере
-----------------------------------------------------------------
Теперь вы готовы к тому, чтобы ввести и запустить программу,
которая действительно что-то делает. Вернитесь в текстовый редак-
тор и введите следующую программу REVERSE.ASM:
DOSSEG
.MODEL SMALL
.STACK 100h
.DATA
MAXIMUM_STRING_LENGTH EQU 1000
StringToReverse DB MAXIMUM_STRING_LENGTH DUP (?)
ReverseString DB MAXIMUM_STRING_LENGTH DUP (?)
.CODE
mov ax,@Data
mov dx,ax ; установить регистр DS таким
; образом, чтобы он указывал
mov ah,3fh ; функция DOS чтения ввода
mov bx,0 ; описатель стандартного ввода
mov cx,MAXIMUM_STRING_LENGTH ; считать до максималь-
; ного числа символов
mov dx,OFFSET StringToReverse ; сохранить строку
int 21h ; получить строку
and ax,ax ; были считаны символы?
jz Done ; нет, конец
mov cx,ax ; поместить длину строки в
; регистр СХ, который можно
; использовать, как счетчик
push cx ; сохранить в стеке длину
; строки
mov bx,OFFSET StringToReverse
mov si,OFFSET ReverseString
add si,cx
dec si ; указывает на конец буфера
; строки
ReverseLoop:
mov al,[bx] ; получить следующий символ
mov [si],al ; сохранить символы в
; обратном порядке
inc bx ; указатель на следующий
; символ
dec si ; указатель на предыдущую
; ячейку buffer
loop ReverseLoop ; переместить следующий
; символ, если он имеется
pop cx ; извлечь длину строки
mov ax,40h ; функция записи DOS
mov bx,1 ; описатель стандартного
; вывода
mov dx,OFFSET ReverceString ; напечатать строку
Done:
mov ah,4ch ; функция DOS завершения
; программы
int 21h ; завершить программу
END
Скоро вы увидите, что сможет делать эта программа. Для нача-
ла не забудьте ее сохранить (под именем REVERSE.ASM).
Запуск программы REVERSE.ASM
-----------------------------------------------------------------
Для запуска программы REVERSE.ASM вы должны сначала ассем-
блировать ее:
TASM reverse
а затем ввести:
TLINK reverse
для создания выполняемого файла. Запустите программу, введя в от-
вет на подсказку DOS слово reverse. В случае вывода при ассембли-
ровании сообщений об ошибках, внимательно проверьте исходный код,
сравните его с приведенным текстом, а затем попытайтесь повторить
процесс.
После запуска программы на экране останется мерцающий кур-
сор. Очевидно, программа ожидает, что вы что-нибудь введете. Поп-
робуйте ввести:
ABCDEFG
а затем нажмите клавишу ENTER. Программа выведет на экран:
GFEDCBA
и завершит работу. Снова введите reverse в командной строке. На
этот раз введите:
0123456789
и нажмите клавишу ENTER. Программа выведет на экран:
9876543210
Теперь ясно, что делает программа REVERSE.ASM: она изменяет
порядок символов во введенной строке на обратный. Быстрая работа
со строками и символами - эта одна из областей, где язык Ассем-
блера превосходно демонстрирует свои качества. Вы увидите это в
следующих нескольких главах.
Вас можно поздравить! Вы только что ввели, ассемблировали и
скомпоновали несколько программ на Ассемблере и, таким образом,
ознакомились в действии с основами программирования на Ассембле-
ре: вводом, обработкой данных и выводом.
Если вы не хотите создавать объектный файл, но хотите полу-
чить файл листинга, или если вы хотите получить файл перекрестных
ссылок, но не хотите создавать файл листинга или объектный файл,
задайте в качестве имени файла пустое (нулевое) устройство
(NULL). Например, команда:
TASM FILE1,,NUL,
ассемблирует файл FILE1.ASM в объектный файл FILE1.OBJ, не созда-
вая файла листинга, и создает файл перекрестных ссылок FILE1.XRF.
Теперь вы готовы к тому, чтобы изучить основные элементы
программирования на языке Ассемблер, о которых рассказывается в
Главе 5 "Элементы программы на Ассемблере".
Глава 3. Работа с командной строкой Турбо Ассемблера
-----------------------------------------------------------------
Данная глава посвящена ознакомлению вас с необязательными
параметрами командной строки Турбо Ассемблера. Мы опишем каждый
параметр командной строки, которые вы можете использовать для
того, чтобы изменить поведение Ассемблера, и покажем, как и где
используются командные файлы. Наконец, мы опишем также файл кон-
фигурации.
Запуск Турбо Ассемблера из DOS
-----------------------------------------------------------------
В Турбо Ассемблере имеется очень мощный и гибкий синтаксис
командной строки. Если вы запустите Турбо Ассемблер, не задав ни-
каких аргументов, например:
TASM
то на экран выведется справочная информация, (на английском язы-
ке) описывающая множество параметров командной строки и синтаксис
для спецификации ассемблируемых файлов. На Рис. 3.1 показано, как
она выглядит.
-----------------------------------------------------------------
Turbo Assembler Version 2.0 Copyright (C) 1990
by Borland International, Inc
Usage:
TASM [параметры] исх_файл [,объект_файл] [,листинг] [,пер_ссылки]
/a,/s Упорядочивание сегментов по алфавитному порядку
или порядку исходного кода
/c Генерация в листинге перекрестных ссылок
/dSYM[=VAL] Определяется SYM = 0 или SYM = VAL
/e,/r Эмулируемые или действительные инструкции с плаваю-
щей точкой
/h,/? Выводится данная справочная информация
/lPATH Включаемые файлы ищутся по маршруту, определяемому
PATH
/jCMD Определяет начальную директиву Ассемблера (напри-
мер, jIDEAL)
/kh#,/ks# Мощность хеш-таблицы #, мощность объема строки #
/l,/la Генерация листинга: l=обычный листинг, la=расширен-
ный
/ml,/mx,/mu Различимость в регистре букв идентификаторов:
ml=все, mx=глобальные, mu=не различаются
/mv# Задает максимальную длину идентификаторов
/m# Разрешает выполнение нескольких проходов для удов-
летворения опережающих ссылок
/n Подавление в листингах таблицы символов
(идентификаторов)
/p Проверка перекрытия сегмента кода в защищенном
режиме
/q Подавление записей .OBJ, не требующиеся при компо-
новке
/t Подавление сообщений при успешном ассемблировании
/w0,/w1,/w2 Задание уровня предупреждение: w0=нет
предупреждений, w1=w2=есть предупреждения
/w-xxx,/w+xxx Запрещение или разрешение предупреждения типа xxx
/x Включение в листинги блоков условного ассемблирова-
ния
/zi,/zd Информация об идентификаторах для отладки: zi=пол-
ная, zd=только о номерах строк
-----------------------------------------------------------------
Рис. 3.1 Командная строка Турбо Ассемблера.
С помощью параметров командной строки вы можете задавать имя
одного или нескольких ассемблируемых файлов, а также параметры,
управляющие их ассемблированием.
Общий вид командной строки выглядит следующим образом:
TASM файлы [; файлы]...
Точка с запятой после левой квадратной скобки позволяет вам
в одной командной строке ассемблировать несколько групп файлов.
По желанию вы можете задать для каждой группы файлов различные
параметры, например:
TASM /E FILE1; /A FILE2
В общем случае группа файлов в командной строке может иметь
вид:
[параметр]...исх_файл [[+] исходный_файл]...
[,[объектный_файл] [, [файл_листинга],
[, [файл_перекрестных_ссылок]]
Этот синтаксис показывает, что группа файлов может начинать-
ся с любого параметра, который вы хотите применить к этим файлам,
а затем могут следовать файлы, которые вы хотите ассемблировать.
Именем файла может быть одно имя файла, либо вы можете использо-
вать обычные трафаретные символы DOS * и ? для задания группы ас-
семблируемых файлов. Если расширение имени файла не указано, Тур-
бо Ассемблер использует по умолчанию расширение .ASM.
TASM MYFILE,,,MYXREF
По этой команде файл MYFILE.ASM ассемблируется в файл
MYFILE.OBJ, листинг выводится в файл с именем MYFILE.LST, а пе-
рекрестные ссылки - в файл MYXREF.XRF.
Если при спецификации ассемблируемых исходных файлов вы ис-
пользуете трафаретные символы, их можно использовать также для
задания имен файла листинга и объектного файла. Например, если в
текущем каталоге содержатся файлы XX1.ASM и XX2.ASM, то командная
строка:
TASM XX*,YY*
ассемблирует все файлы, начинающиеся с букв XX, генерирует объек-
тные файлы, имена которых будут начинаться с YY, а остальную
часть имени формирует в соответствии с именем исходного файла.
Результирующие объектные файлы получат, таким образом, имена YY1,
OBJ и YY2.OBJ.
Если вы не хотите создавать объектный файл, но хотите полу-
чить файл листинга, или если вы хотите получить файл перекрестных
ссылок, но не хотите создавать файл листинга или объектный файл,
можно в качестве имени файла задать нулевое (фиктивное) устройс-
тво. Например:
TASM FILE1,,NUL,
Эта команда ассемблирует файл FILE1.ASM в объектный файл
FILE1.OBJ. При этом файл листинга не создается, а создается файл
перекрестных ссылок FILE1.XRF.
Параметры командной строки
-----------------------------------------------------------------
Необязательные параметры командной строки позволяют вам уп-
равлять поведением Ассемблера, а также тем, какую информацию он
выводит на экран, в листинг и объектный файл. В Турбо Ассемблере
предусмотрены некоторые параметры, которые не выполняют никаких
действий, а используются только для совместимости текущей версии
TASM с предыдущими версиями MASM (макроассемблер фирмы
Microsoft):
/B Задает размер буфера
/V Выводит на экран дополнительную статистику
Вы можете задавать параметры, представляющие собой любую
комбинацию букв в верхнем и нижнем регистре. Кроме того, парамет-
ры можно задавать в любом порядке (кроме параметров /I и /J), они
будут при этом обрабатываться последовательно. При использовании
параметра /D нужно быть внимательным: идентификаторы надо опреде-
лить до того, как они будут использованы в последующих параметрах
/D.
Примечание: С помощью директив, указанных в вашем ис-
ходном коде, вы можете отменить эквивалентные им параметры
Ассемблера.
На Рис. 3.1 (см. выше) приведен список параметров Турбо Ас-
семблера. Далее эти параметры описаны подробно (их можно также
задавать буквами в нижнем регистре).
Параметр /A
-----------------------------------------------------------------
Функция: Задает упорядочивание сегментов по алфавитному по-
рядку.
Синтаксис: /A
Примечания: Параметр /A указывает Турбо Ассемблеру, что сег-
менты в объектном файле должны быть размещены в алфавитном поряд-
ке. Это эквивалентно использованию в исходном коде директивы
.ALPHA.
Этим параметром обычно приходится пользоваться тогда, когда
вы хотите ассемблировать исходный файл, написанный для ранних
версий ассемблеров фирм Microsoft или IBM.
Параметр /S изменяет действие данного параметра на обратное,
сохраняя используемое по умолчанию последовательное упорядочива-
ние сегментов.
Если в исходном файле вы задаете с помощью директивы .SEQ
последовательное упорядочивание сегментов, то она отменит дей-
ствие параметра /A, задаваемого в командной строке.
Пример:
TASM /A TEST1
Данная командная строка создает объектный файл TEST1.OBJ,
сегменты которого упорядочиваются в алфавитном порядке.
Параметр /B
-----------------------------------------------------------------
Синтаксис: /B
Примечания: Параметр /B используется только в целях совмес-
тимости с другими версиями. Он не приводит ни к каким действиям и
не оказывает влияния на ассемблирование.
Параметр /C
-----------------------------------------------------------------
Функция: Разрешает включать в листинг перекрестные ссылки.
Синтаксис: /C
Примечания: Параметр /C разрешает включение в файл листинга
информации о перекрестных ссылках. Турбо Ассемблер включает ин-
формацию о перекрестных ссылках в таблицу идентификаторов в конце
файла листинга. Чтобы получить информацию о перекрестных ссылках,
вам нужно также явно задать в командной строке генерацию файла
листинга или использовать для разрешения формирования файла лис-
тинга параметр /L.
Для каждого идентификатора в перекрестных ссылках указывает-
ся строка, в которой он определен и все строки, где имеется на
него ссылка.
Параметр /D
-----------------------------------------------------------------
Функция: Определяет идентификатор.
Синтаксис: /Dидентификатор[=значение или выражение]
Примечания: Параметр /D определяет идентификатор для исход-
ного файла, точно также, как если бы он определялся на первой
строке исходного файла с помощью директивы =. В командной строке
этот параметр можно использовать любое число раз.
Вы можете только определить идентификатор, равный другому
идентификатору, или постоянному значению. Справа от знака ра-
венства (=) не допускается использовать выражение с операциями.
Например, допустимо /DX=9 и /DX=Y, но параметр /DX=Y-4 не допус-
кается.
Пример:
TASM /DMAX=10 /DMIN=2 TEST1
В данной командной строке определяются два идентификатора
MAX и MIN, на которые могут ссылаться другие операторы в исходном
файле TEST1.ASM.
Параметр /E
-----------------------------------------------------------------
Функция: Генерирует инструкции эмуляции работы с плавающей
точкой.
Синтаксис: /E
Примечания: Параметр /E указывает Турбо Ассемблеру, что нуж-
но генерировать инструкции работы с плавающей точкой, которые бу-
дут выполняться с помощью программного обеспечения (эмулятора
операций с плавающей точкой). Используйте этот параметр, если
ваша программа содержит библиотеку эмуляции работы с плавающей
точкой, которая эмулирует функции арифметического сопроцессора
80х87.
Обычно этот параметр следует использовать только в том слу-
чае, если ваш модуль на Ассемблере является частью программы, на-
писанной на языке высокого уровня, в которой используется библио-
тека эмуляции работы с плавающей точкой (эмуляцию операций с пла-
вающей точкой поддерживают Турбо Си, Турбо Паскаль, Турбо Бейсик
и Турбо Пролог). Вы не можете просто скомпоновать программу на
Ассемблере с библиотекой эмуляции, так как предполагается, что
библиотека должна инициализироваться начальным кодом компилятора.
Параметр /R изменяет действие данного параметра на обратное,
разрешая ассемблирование действительных инструкций с плавающей
точкой, которые могут выполняться арифметическим сопроцессором.
Если в исходной файле вы используете директиву NOEMUL, то
она отменит действие параметра /E в командной строке.
Параметр командной строки /E оказывает то же действие, что и
использование в начале исходного файла директивы EMUL, и эквива-
лентен параметру командной строки /JEMUL.
Пример:
TASM /E SEGANT
TCC -f TRIG.C SEGANT.OBJ
Параметр /H или /?
-----------------------------------------------------------------
Функция: Выводит на экран дисплея справочную информацию.
Синтаксис: /H или /?
Примечания: Параметр /H указывает Турбо Ассемблеру, что на
экран дисплея нужно вывести справочную информацию, описывающую
синтаксис командной строки. Эта справочная информация включает в
себя список параметров, а также различные задаваемые имена фай-
лов. Параметр /? делает то же самое.
Параметр /I
-----------------------------------------------------------------
Функция: Задает маршрут доступа к включаемому файлу.
Синтаксис: /Iмаршрут
Примечания: Параметр /I указывает Турбо Ассемблеру, где нуж-
но искать файлы, включаемые в исходный файл по директиве INCLUDE.
В командной строке можно указать несколько параметров /I (их чис-
ло ограничено только размерами оперативной памяти).
Когда Турбо Ассемблер обнаруживает директиву INCLUDE, то
место, где он будет искать включаемый файл определяется тем, яв-
ляется ли имя файла в директиве INCLUDE маршрутом доступа к ката-
логу, или это просто имя файла.
Если вы в качестве части имени файла указываете маршрут, то
сначала делается попытка поиска по данному маршруту, а затем Тур-
бо Ассемблер выполняет поиск в каталогах, заданных в параметрах
командной строки /I (в том порядке, как они указаны в командной
строке). Затем он ищет файл по всем каталогам, заданным в пара-
метрах /I файла конфигурации.
Если в спецификации имени файла вы не указываете маршрут, то
Турбо Ассемблер выполняет сначала поиск в каталогах, заданных в
параметрах командной строки /I, затем - в каталогах, заданных в
параметрах /I файла конфигурации, и, наконец, в текущем каталоге.
Пример:
TASM /I\INCLUDE /ID:\INCLUDE TEST1
Если исходный файл содержит оператор:
INCLUDE MYMACS.INC
то Турбо Ассемблер сначала ищет файл \INCLUDE\MYMACS.INC, затем
D:\INCLUDE\MYMACS.INC. Если он еще не нашел файл, то файл
с именем MYMACS.INC ищется в текущем каталоге. Если бы в исходном
файле содержался оператор:
INCLUDE INCS\MYMACS.INC
то Турбо Ассемблер сначала искал бы включаемый файл
\INCS\MYMACS.INC, затем \INCLUDE\MYMACS.INC, и, наконец
D:\INCLUDE\MYMACS.INC.
Параметр /J
-----------------------------------------------------------------
Функция: Определяет директиву инициализации Ассемблера.
Синтаксис: /Jдиректива
Примечания: Параметр /J позволяет вам определить директиву,
которая будет ассемблироваться перед первой строкой исходного
файла. "Директива" может представлять собой любую директиву Турбо
Ассемблера, не требующую аргументов, например, .286, IDEAL,
%MACS, NOJUMP и т.д. Полное описание директив Турбо Ассемблера
содержится в "Справочном руководстве" в Главе 3.
В командной строке вы можете указать более одного параметра
/J. При этом они будут обработаны слева направо.
Пример:
TASM /J.286 .JIDEAL TEST1
При этом ассемблируется файл TEST1.ASM с разрешенными инст-
рукциями процессора 80286 и разрешением синтаксического анализа
выражений в режиме IDEAL.
Параметр /KH
-----------------------------------------------------------------
Функция: Задает максимально допустимое число идентификато-
ров.
Синтаксис: /KHnидентификаторов
Примечания: Параметр /KH задает максимально допустимое число
идентификаторов, которое может содержать программа. Если вы не
используете данный параметр, ваша программа может содержать толь-
ко до 8192 идентификаторов. Использование этого параметра позво-
ляет увеличить число идентификаторов до значения "nидентификато-
ров" (это значение не должно превышать 32768).
Используйте данный параметр, если при ассемблировании прог-
раммы вы получаете сообщение "Out of hash space" (буферное прост-
ранство исчерпано).
Данный параметр можно также использовать для уменьшения об-
щего числа идентификаторов до значения, меньшего назначенного по
умолчанию (8192). Это позволит освободить некоторое количество
памяти, что может оказаться полезным, когда вы пытаетесь ассем-
блировать программу, а у вас не хватает памяти.
Пример:
TASM /KH10000 BIGFILE
Параметр /KS
-----------------------------------------------------------------
Функция: Данный параметр задает максимальный размер строко-
вого пространства Турбо Ассемблера.
Синтаксис: /KHkбайт
Примечания: Обычно размер строки определяется автоматически
и настраивать его не требуется. Однако если у вас имеется исход-
ный файл, который приводит к сообщению "Out of string space" (не
хватает строкового пространства), то с помощью данного параметра
вы можете увеличить строковое пространство. Попытайтесь начать со
значения 100 и увеличивать его, пока ваша программа не будет ас-
семблироваться без ошибки. Максимально допустимое значение (в ки-
лобайтах) - 255.
Пример:
TASM /KS150 SFILE
Параметр /L
-----------------------------------------------------------------
Функция: Генерирует файл листинга.
Синтаксис: /L
Примечания: Параметр /L указывает, что вы хотите создать
файл листинга, даже если вы его не задаете в командной строке
явно. Файл листинга имеет то же имя, что и исходный файл, и рас-
ширение .LST.
Пример:
TASM /L TEST1
Данная командная строка приводит к созданию файла листинга с
именем TEST1.LST.
Параметр /LA
-----------------------------------------------------------------
Функция: Показывает в исходной файле код интерфейса с языком
высокого уровня.
Синтаксис: /LA
Примечания: Параметр /LA указывает Турбо Ассемблеру, что в
файле листинга нужно отразить весь генерируемый код, включая код,
который генерируется в результате директивы языка высокого уровня
.MODEL.
Пример:
TASM /LA FILE1
Параметр /M
-----------------------------------------------------------------
Функция: Задает максимальное число проходов Ассемблера.
Синтаксис: /M[число_проходов]
Примечания: Обычно Турбо Ассемблер работает, как однопроход-
ный ассемблер. Необязательный параметр /m позволяет вам задать
максимальное число проходов, которые Ассемблер должен выполнять в
процессе ассемблирования. Турбо Ассемблер TASM автоматически оп-
ределяет, что он может выполнить меньше заданного числа проходов.
Если вы не указываете явно число проходов, то по умолчанию ис-
пользуется значение 5.
Некоторые модули содержат конструкции, которые правильно ас-
семблируются только при выполнении двух проходов. Если не
разрешено выполнять несколько проходов, то такой модуль приведет
к генерации по крайней мере одного предупреждающего сообщения:
"Pass-dependent construction encountered"
(обнаружена конструкция, зависящая от прохода)
Если указан параметр /m, то Турбо Ассемблер будет правильно
ассемблировать такой модуль, но не будет оптимизировать код прог-
рамму, удаляя операции NOP (независимо от указанного числа прохо-
дов). В этом случае выводится сообщение:
"Module is pass dependent - compatibility pass was done"
(модуль зависит от прохода - выполнен проход для совмести-
мости)
Пример:
TASM /M2 TEST1
Эта команда указывает TASM, что ассемблирование модуля TEST1
нужно выполнять в два прохода.
Параметр /ML
-----------------------------------------------------------------
Функция: Интерпретирует различие в регистрах букв идентифи-
каторов.
Синтаксис: /ML
Примечания: Параметр /ML указывает Турбо Ассемблеру, что во
всех идентификаторах нужно различать буквы разного регистра
(строчные и прописные). Обычно строчные и прописные буквы рас-
сматриваются, как эквивалентные, поэтому имена ABCxyz, ABCXYZ и
abcxyz обозначают один и тот же идентификатор. Если вы задаете
параметр /ML, то эти три идентификатора будут считаться различны-
ми. Тем не менее, даже после задания параметра /ML ключевые слова
Ассемблера можно вводить как в верхнем, так и в нижнем регистре.
Ключевые слова представляют собой идентификаторы, встроенные в
Ассемблер, которые имеют специальное значение (мнемоники инструк-
ций, директивы и операторы).
Пример:
TASM /ML TEST1
где TEST1.ASM содержит следующие операторы:
ABC DW 1
abc DW 0 ; это не дублирующий идентификатор
Mov Ax,[Bp] ; в ключевых словах допускается использо-
; вать разный регистр
Параметр /MU
-----------------------------------------------------------------
Функция: Преобразует идентификаторы в верхний регистр.
Синтаксис: /MU
Примечания: Параметр /MU указывает Ассемблеру, что нужно иг-
норировать регистр во всех идентификаторах. По умолчанию в Турбо
Ассемблере задано, что в идентификаторах все буквы нижнего ре-
гистра должны преобразовываться в верхний регистр (если это не
отменено с помощью директивы /ML).
Пример:
TASM /MU TEST1
При этом все идентификаторы будут преобразованы в верхний регистр
(что задано по умолчанию):
EXTRN myfunc:NEAR
call myfunc ; не важно, как была
; определена функция:
; MYFUNC, Myfunk,...
Параметр /MV#
-----------------------------------------------------------------
Функция: Задает максимальную длину идентификаторов.
Синтаксис: /MV#
Примечания: Данный параметр задает максимальную длину иден-
тификаторов, которые будет различать TASM. Например, при задании
параметра /mv3 TASM будет интерпретировать идентификаторы ABCC и
ABCD, как один и тот же идентификатор.
Параметр /MX
-----------------------------------------------------------------
Функция: Задает различимость на на строчные и прописные бук-
вы (верхний и нижний регистр) во внешних и общедоступных иденти-
фикаторах.
Синтаксис: /MX
Примечания: Параметр /MX сообщает Турбо Ассемблеру, что раз-
личать регистр букв нужно только во внешних (External) и общедос-
тупных (Public) идентификаторах. Все другие идентификаторы в ис-
ходном файле будут интерпретироваться, как набранные в верхнем
регистре.
Использовать данную директиву следует при вызове процедур из
других модулей, которые ассемблировались или компилировались так,
что сохранилось различие в строчных и прописных буквах (например,
модулей, которые компилировались в Турбо Си).
Пример:
TASM /MX TEST1
где TEST1 содержит следующие исходные строки:
EXTRN Cfunc:NEAR
myproc PROC NEAR
call Cfunc
.
.
.
Параметр /N
-----------------------------------------------------------------
Функция: Подавляет в файле листинга таблицу идентификаторов.
Синтаксис: /N
Примечания: Параметр /N показывает, что в конце файла лис-
тинга вы не хотите использовать обычную таблицу идентификаторов.
Обычно в конце файла листинга содержится полная таблица идентифи-
каторов, где показаны все идентификаторы, их имена и значения.
Вы должны задать файл листинга либо явным образом (в коман-
дной строке), либо с помощью параметра /L. В противном случае па-
раметр /N не приведет ни к каким действиям.
Пример:
TASM /L /N TEST1
Параметр /P
-----------------------------------------------------------------
Функция: Проверяет наличие "некорректного" кода в защищенном
режиме.
Синтаксис: /P
Примечания: Параметр /P определяет, что вы хотите получить
предупреждение при любой инструкции, генерирующей в защищенном
режиме "некорректный" (impure) код. Инструкции, перемещающие дан-
ные в память путем переопределения регистра CS: в защищенном ре-
жиме рассматриваются, как некорректные, поскольку они в защищен-
ном режиме могут работать неверно, если не принять специальных
мер.
Этот параметр нужно использовать только в том случае, если
вы пишете программу, выполняемую на процессоре 80286 или 80286 в
защищенном режиме.
Пример:
TASM /P TEST1
где TEST1 содержит следующие операторы:
.286P
CODE SEGMENT
temp DW ?
mov CS:temp,0 ; в защищенном режиме может выпол-
; няться некорректно
Параметр Q
-----------------------------------------------------------------
Функция: Подавляет записи .OBJ, не требующиеся при компонов-
ке.
Синтаксис: /Q
Примечание: Данный параметр удаляет из получаемого в резуль-
тате файла (файлов) .OBJ записи об авторских правах и зависимости
файлов. Этот параметр не следует указывать, если вы используете
утилиту MAKE или аналогичные программы, которые при работе учиты-
вают эти записи.
Параметр /R
-----------------------------------------------------------------
Функция: Генерирует реальные инструкции с плавающей точкой.
Синтаксис: /R
Примечания: Параметр /R указывает Турбо Ассемблеру, что нуж-
но генерировать реальные инструкции с плавающей точкой (вместо
генерации эмулируемых инструкций с плавающей точкой). Используйте
этот параметр, если вы хотите выполнять свою программу на маши-
нах, оснащенных арифметическим сопроцессором 80х87.
Действие данного параметр изменяет на обратное параметр /E
(при этом генерируются эмулируемые инструкции с плавающей точ-
кой).
Если в исходном файле вы используете директиву EMUL, то она
отменит действие инструкции /R, указанной в командной строке.
Параметр командной строки /R имеет тот же эффект, что и ис-
пользование в начале исходного файле директивы NOEMUL и совпадает
с действием параметра командной строки /JNOEMUL.
Пример:
TASM /R SEGANT
TPC /$N+ /$E- TRIG.PAS
Первая команда ассемблирует модуль с реальными инструкциями
с плавающей точкой. Вторая командная строка компилирует исходный
модуль Паскаля с реальными инструкциями с плавающей точкой, кото-
рый компонуется с объектным файлом Ассемблера.
Параметр /S
-----------------------------------------------------------------
Функция: Задает последовательное упорядочивание сегментов.
Синтаксис: /S
Примечания: Параметр /S указывает Турбо Ассемблеру, что сег-
менты в объектном файле нужно разместить в том порядке, в котором
Турбо Ассемблер обнаруживает их в исходном коде. По умолчанию
Турбо Ассемблер использует именно такое упорядочивание сегментов,
если вы не изменили его с помощью параметра /A в командной строке
или в файле конфигурации.
Если с помощью директивы .ALPHA в исходном коде вы задали
упорядочивание сегментов в алфавитном порядке, то эта директива
отменит параметр /S, задаваемый в командной строке.
Пример:
TASM /S TEST1
По данной команде создается объектный файл (TEST1.OBJ), сег-
менты которого упорядочены в том порядке, как они содержатся в
исходном файле.
Параметр /T
-----------------------------------------------------------------
Функция: Подавляет вывод сообщений при условном ассемблиро-
вании.
Синтаксис: /T
Примечания: Параметр /T подавляет всю выводимую Турбо Ассем-
блеру на экран информацию, кроме предупреждений и сообщений об
ошибках, возникающих в результате ассемблирования.
Вы можете использовать данный параметр при ассемблировании
нескольких модулей, когда на экран желательно выводить только со-
общения об ошибках.
Пример:
TASM /T TEST1
Параметр /V
-----------------------------------------------------------------
Синтаксис: /V
Примечания: Параметр /V используется в целях совместимости.
Он не приводит ни к каким действиям и не оказывает влияния на ас-
семблирование.
Параметр /W
-----------------------------------------------------------------
Функция: Управляет генерацией предупреждающих сообщений.
Синтаксис: /W
W-[класс предупреждений]
W+[класс предупреждений]
Примечания: Параметр /W управляет выводом Турбо Ассемблером
предупреждающих сообщений.
Если вы просто укажете параметр /W, то будут выводиться
"слабые" предупреждения. Такие предупреждения показывают, что вы
можете несколько улучшить эффективность вашей программы.
Если вы зададите параметр /W- без класса предупреждений, то
все предупреждения запрещаются. Если за параметром указывается
класс предупреждений, то запрещаются только эти предупреждения.
Каждое предупреждающее сообщение имеет идентификатор из трех
букв:
ASS - подразумевается использование 16-разрядного сегмента;
BRK - требуются квадратные скобки;
ICG - неэффективная генерация кода;
LCO - переполнение счетчика адреса;
OPI - открытый блок условия IF;
OPP - открытая процедура;
OPS - открытый сегмент;
OVF - арифметическое переполнение;
PDC - конструкция, зависящая от прохода;
PRO - запись в память в защищенном режиме требует
переопределения регистра CS;
RES - слово зарезервировано;
TPI - недопустимо для Турбо Паскаля.
Если вы указываете параметр /W+ без класса предупреждения,
то все предупреждения будут разрешены. Если вы задаете параметр
/W+ с классом предупреждений из предыдущего списка, то будут раз-
решены только эти предупреждения.
По умолчанию Турбо Ассемблер сначала начинает ассемблирова-
ние исходного файла с разрешением всех предупреждений, кроме пре-
дупреждений о неэффективности кода (ICG).
Для управления выводом определенных сообщений на заданном
участке программы в файле с исходным кодом вы можете использовать
директивы WARN или NOWARN. Более подробно об этих директивах рас-
сказывается в Главе 3 "Справочного руководства".
Пример:
TASM /W TEST1
Следующий оператор в программе TEST1.ASM выведет предупреж-
дающее сообщение, которое не появится на экране, если не указан
параметр /W:
mov bx,ABC ; предупреждение о неэффективности кода
ABC = 1
При задании командной строки:
TASM /W-OVF TEST2
если файл TEST2.ASM содержит директиву:
DW 1000h = 20h
предупреждения генерироваться не будут.
Параметр /X
-----------------------------------------------------------------
Функция: Включает в листинг блоки условного ассемблирования.
Синтаксис: /X
Примечания: Если при вычислении блоков IF, IFNDEF, IFDEF и
т.д. получается значение FALSE, то параметр /X приводит к тому,
что операторы, содержащиеся внутри условного блока, будут включе-
ны в листинг ассемблирования. по данной директиве в листинг будут
также включены сами директивы условного ассемблирования (обычно
они в листинг не включаются).
Вы должны в командной строке или с помощью параметра /L за-
дать также необходимость генерации файла листинга, иначе параметр
/X действовать не будет.
Пример:
TASM /X TEST1
Параметр /Z
-----------------------------------------------------------------
Функция: Выводит на экран наряду с сообщениями об ошибке со-
ответствующие строки исходного текста.
Синтаксис: /Z
Примечания: Параметр /Z указывает Ассемблеру, что при гене-
рации сообщения об ошибке на экран нужно вывести соответствующую
строку исходного файла (где эта ошибка возникла). Вызвавшая ошиб-
ку строка выводится перед сообщением об ошибке. При запрещении
данного параметра Турбо Ассемблер просто выводит сообщение, опи-
сывающее ошибку.
Пример:
TASM /Z TEST1
Параметр /ZD
-----------------------------------------------------------------
Функция: Разрешает включение в объектные файлы информации о
номерах строк.
Синтаксис: /ZD
Примечания: Параметр /ZD приводит к тому, что Турбо Ассемб-
лер будет помещать в объектные файлы информацию о номерах строк.
Это позволяет автономному отладчику фирмы Borland (Турбо отладчи-
ку) выводить на экран текущее место в исходном коде, но не позво-
ляет ему осуществлять доступ к элементам данных.
Если при попытке выполнения отладки программы с помощью Тур-
бо отладчика вам не хватит памяти, вы можете использовать пара-
метр /ZD для одних модулей и параметр /ZI - для других.
Пример:
TASM /ZD TEST1
Параметр /ZI
-----------------------------------------------------------------
Функция: Разрешает включение в объектный файл информации для
отладки.
Синтаксис: /ZI
Примечания: Параметр /ZI указывает Турбо Ассемблеру, что в
объектный файл нужно вывести полную информацию для отладки. Эта
информация включает в себя записи о номерах строк (для синхрони-
зации вывода на экран исходного текста) и информацию о типах дан-
ных, позволяющую модифицировать и проверить данные программы.
Параметр /ZI позволяет вам использовать все средства Турбо
отладчика для прохождения программы и проверки и изменения ваших
элементов данных. Вы можете использовать параметр /ZI для всех
модулей программы или только для тех из них, отладка которых вас
интересует. Поскольку параметр /ZI добавляет информацию в объект-
ные и выполняемые файлы, может оказаться нежелательным его ис-
пользование для всех модулей программы при выполнении программы
Турбо отладчиком (например, может возникать ситуация нехватки па-
мяти).
Пример:
TASM /ZI TEST1
Косвенные командные файлы
-----------------------------------------------------------------
В любой момент, когда вы вводите командную строку, Турбо Ас-
семблер позволяет вам задавать косвенный командный файл, с по-
мощью указания перед его именем символа @. Например:
TASM /DTESTMODE @MYPROJ.TA
Эта команда приводит к тому, что содержимое файла MYPROJ.TA
становится частью командной строки (как если бы вы ввели ее со-
держимое непосредственно).
Это полезное средства позволяет вам поместить наиболее часто
используемые командные строки и списки файлов в отдельный файл.
При этом нет необходимости помещать всю командную строку в один
косвенный файл, поскольку в одной командной строке допускается
использовать несколько исходных файлов с обычными аргументами,
например:
TASM @MYFILES @IOLIBS /DBUF=1024
Таким образом вы можете использовать длинный список стандар-
тных файлов и параметров, благодаря чему можно легко изменять по-
ведение Ассемблера при каждом ассемблировании.
Вы можете либо поместить все имена и параметры файлов в одну
строку командного файла, либо разбить их на несколько строк, как
это необходимо.
Файлы конфигурации
-----------------------------------------------------------------
Турбо Ассемблер позволяет вам также поместить наиболее часто
используемые параметры в файл конфигурации в текущем каталоге.
Таким образом, когда вы запускаете Турбо Ассемблер, он будет в
текущем каталоге искать файл TASM.CFG. Если Турбо Ассемблер нахо-
дит этот файл, то он будет интерпретировать его, как косвенный
файл, и обрабатывать его в командной строке первым.
Это может оказаться полезным, когда вы формируете "проект"
программы (то есть программа включает в себя несколько файлов), и
все файлы проекта находятся в одном каталоге. При этом вы хотите,
например, всегда выполнять ассемблирование с использованием эму-
лирования инструкций с плавающей точкой (параметр /E). Для этого
вы можете поместить параметр в файл TASM.CFG, после чего его не
нужно будет задавать каждый раз при запуске Турбо Ассемблера.
Содержимое файла конфигурации имеет тот же формат, что и
косвенный файл. Этот файл может содержать любую допустимую в ко-
мандной строке информацию и содержать столько строк, сколько не-
обходимо. Параметры обрабатываются так, как если бы они содержа-
лись на одной строке.
Содержимое файла конфигурации обрабатывается до всех других
аргументов командной строки. Это позволяет вам отменить любой па-
раметр, заданный в файле конфигурации, просто указав в командной
строке параметр, который имеет противоположное действие. Напри-
мер, если ваш файл конфигурации содержит параметры:
/A /E
и вы вызываете Турбо Ассемблер командой:
TASM /S /R MYFILE
где MYFILE - файл вашей программы, то ассемблирование будет вы-
полнено с последовательным упорядочиванием сегментов (/S) и ре-
альными инструкциями с плавающей точкой (/R), хотя в файле конфи-
гурации содержатся директивы /A и /E, задающие упорядочивание
сегментов по алфавитному порядку и эмулирование инструкций с пла-
вающей точкой.
Глава 4. Природа языка Ассемблера
-----------------------------------------------------------------
Как мы уже говорили ранее, язык Ассемблера - это "родной"
язык компьютера, Чтобы понять, что это означает, нужно сначала
разобраться, что же представляет собой сам компьютер. Затем мы
расскажем вам о том, что делает язык Ассемблера уникальным среди
других языков программирования.
В данной главе мы рассмотрим компьютеры вообще и процессор
8086 в частности. Это позволит вам понять сильные стороны прог-
раммирования на языке Ассемблера для процессора 8086. Мы также
затронем те аспекты программирования, которые относятся конкретно
к компьютерам IBM PC.
Архитектура компьютера
-----------------------------------------------------------------
На нижнем уровне компьютер - это не что иное, как устройство
для перемещения данных из одного места в памяти (или на устройс-
тве) в другое, при котором иногда выполняются также логические
или арифметические преобразования данных. Для наших целей полез-
нее, однако, рассматривать компьютер, как систему, состоящую из
пяти функциональных подсистем: ввода, управления, арифметической
и логической обработки, памяти и ввода (см. Рис. 4.1).
------------------------------------
| Арифметическая подсистема |
| (сложение, вычитание, умножение, |
| деление, операции "И", "ИЛИ", |<----
| "исключающее ИЛИ" и т.д.) | |
------------------------------------ |
|
---------------------- ------------------------- |
| Подсистема ввода | | Подсистема управления |<-----
| (клавиатура, |<------>| (координация всех |
| "мышь", манипуля- | ---->| функций) |<-----
| тор "джойстик" и | | ------------------------- |
| т.д.) | | |
---------------------- | ------------------------- |
| | Подсистема памяти | |
---------------------- | | (до 1 мегабайта па- |<-----
| Подсистема вывода | | | мяти с прямым досту- |
| (дисплей, принтер, |<--- | пом или памяти, дос- |
| графопостроитель, | | тупной только по чте- |
| диск) | | нию - ПЗУ) |
---------------------- -------------------------
Рис. 4.1 Пять подсистем компьютера.
(В данном случае мы говорим о компьютерах вообще, компьюте-
ров с процессорами 8088 мы кратко коснемся далее.)
Арифметическая подсистема компьютера - это тот аспект, в ко-
тором большинство людей привыкли рассматривать весь компьютер.
Ведь что такое, компьютер, как не вычислительное устройство? Ока-
зывается, однако, что на операции с числами большинство компьюте-
ров тратит очень мало времени. Тем не менее арифметическая под-
система очень важна. Кроме того, она выполняет не только
арифметические операции (сложение, вычитание, умножение и деле-
ние), но и такие логические операции, как "И" (and), "ИЛИ" (or) и
"исключающее ИЛИ" (xor).
Арифметические операции - это, конечно, хорошо, но откуда
поступают исходные значения для операций и куда записывается их
результат? Здесь выполняет свои функции подсистема памяти компью-
тера, предоставляя постоянно доступную для хранения многих тысяч
символов и чисел память. В компьютерах имеются также дисководы на
жестких и гибких дисках, обеспечивающие постоянную, но относи-
тельно медленную память для данных большого объема. Однако такие
дисководы представляют собой устройства ввода-вывода, а не часть
подсистемы памяти.
Подсистема ввода позволяет программам обрабатывать данные
из внешней среды, начиная от отдельных нажатий клавиш и перемеше-
ний манипулятора типа "мышь", до целых баз данных, хранящихся на
дисках в виде файлов. Подсистема вывода позволяет программам вы-
водить данные и результаты на экран или принтер, записывать дан-
ные в файлы на дисках или магнитных лентах. Программы без ввода
или вывода довольно редки, поскольку они не могут воспринимать
данные из внешней среды и не могут ничего сделать с полученными
результатами.
Наконец, подсистема управления объединяет работу остальных
четырех подсистем и управляет перемещением данных.
Подсистема управления и арифметическая подсистема вместе об-
разуют то, что известно, как обрабатывающее устройство или про-
цессор. Процессор - это ядро любого компьютера, обеспечивающее
обработку данных и управление подсистемами памяти, ввода и выво-
да. Процессор задает тон всей работе компьютера, так как именно
процессор управляет работой каждой из подсистем и координирует их
в один согласованно работающий модуль. В настоящее время весь
процессор часто представляет собой всего лишь одну интегральную
схему. Именно таким процессором является процессор 8088, выполня-
ющий полную арифметическую обработку, управление и имеющий интер-
фейс с вводам, выводом и памятью.
Поняв, что такое процессор, мы выясним связь между архитек-
турой компьютера и уникальной природой языка Ассемблера.
Язык Ассемблера
-----------------------------------------------------------------
Как мы уже сказали, процессор управляет деятельностью всех
пяти подсистем компьютера: сложением значений, перемещением их из
памяти на устройство вывода, и т.д. Однако здесь возникает воп-
рос, откуда процессор знает, какие именно операции нужно выпол-
нять? То есть, компьютер обладает всеми необходимыми нам свойст-
вами, но у него нет сценария, по которому он может работать.
Ответ здесь удивительно прост: процессор извлекает данные из
памяти, и эти данные указывают ему, что нужно делать. Такие дан-
ные обычно называются инструкциями, однако инструкции - это прос-
то хранимые в памяти значения, аналогичные любым другим данным.
Набор инструкций, которые может выполнять процессор (набор инст-
рукций процессора) в точности соответствует тем действиям, кото-
рые может выполнять аппаратное обеспечение процессора. Другими
словами, инструкции процессора охватывают все операции, выполнить
которые может указать процессору программное обеспечение.
Например, если отсутствует инструкция умножения, то аппарат-
ное обеспечение компьютера не может выполнять умножение. Вместо
этого умножение выполняется программным обеспечением с помощью
операций сдвига и сложения, но такое умножение выполняется значи-
тельно медленнее. Ключевой момент здесь состоит в том, что набор
инструкций процессора отражает действия, которые присущи аппарат-
ному обеспечению. Поэтому язык ассемблера каждого процессора уни-
кален для этого процессора (каждый процессор имеет уникальные
особенности и, таким образом, уникальный набор инструкций).
Значение каждой инструкции имеет для данного процессора
конкретный, строго определенный смысл. Например, значение инст-
рукции 4 говорит процессору 8088 (или 8086), что нужно сложить
значение, хранящееся в следующей ячейке памяти, с регистром AL
(далее мы рассмотрим, что это за регистр). В итоге можно указать
процессору, чтобы он выполнил желаемую последовательность дейс-
твий с помощью соответствующего набора значений инструкций. В
действительности программа представляет собой просто последова-
тельность инструкций и ничего более.
Откуда же процессор знает, какую инструкцию нужно выполнить
следующей? С помощью отслеживания внутреннего указателя, который
указывает на то место в памяти, где хранится значение следующей
выполняемой инструкции. Когда из памяти считывается и выполняется
следующая инструкция, указатель перемещается на следующую инст-
рукцию. Некоторые инструкции могут устанавливать указатель инст-
рукций в новое значение, это дает процессору возможность выпол-
нять ряд инструкций не строго последовательно и даже выполнять
различные группы инструкций, в зависимости от определенных усло-
вий.
Прекрасно, но какое все это имеет отношение к языку Ассем-
блера?. А вот какое: набор инструкций процессора представляет со-
бой его язык Ассемблера. Или, точнее говоря, язык Ассемблера яв-
ляется ориентированной на человека формой набора инструкций про-
цессора (который называется также машинным языком). Ассемблер
преобразуется в машинный язык. Поскольку машинный язык и язык Ас-
семблера функционально эквивалентны, на языке Ассемблера намного
проще программировать. Кроме того, вам конечно больше понравится
программировать с помощью инструкций типа:
add al,1
чем
4
1
не правда ли? Обе формы работают одинаково, но язык Ассемблера
позволяет вам иметь дело с мнемоническими именами инструкций на
машинном языке. При этом Ассемблер транслирует инструкции из мне-
монического вида в их машинный эквивалент. Это, несомненно, боль-
шое преимущество, так как люди просто не могут достаточно эффек-
тивно думать на числовых языках. В основном язык Ассемблер предс-
тавляет собой прямой аналог машинного языка, но реализованный в
том виде, с которым люди могут более эффективно работать.
Неплохим качеством Ассемблера является то, что он позволяет
вам управлять действиями процессора поэтапно (по операциям) и с
максимальной эффективностью. К числу его недостатков можно отнес-
ти тот факт, что при каждом отдельно взятом действии процессора
выполняется совсем немного функций, что отражает ограниченные
возможности того, на что в действительности способен процессор.
Например, процесс сложения двух длинных целых чисел и сохранения
результата в третьем целом значении занимает на языке Си только
одну строку:
i = j + k;
а на Ассемблере процессора 8088 это потребует шести строк:
mov ax,[j]
mov dx,[j+2]
add ax,[k]
addc dx,[k+2]
mov [i],ax
mov [i+2],dx
Конечно, объем скомпилированного кода на языке Си будет не
меньше (а вероятнее всего больше), чем шесть машинных инструкций
на языке Ассемблера, но легче написать одну строку на Си, чем
шесть на Ассемблере (необходимо помнить, что инструкции Ассембле-
ра отражают элементарные "способности" компьютера, и программы,
написанные на любых языках, должны, очевидно, перед выполнением
транслироваться в машинный язык).
Зачем же тогда вообще использовать Ассемблер, если на нем
программировать труднее, чем на других языках? Причина состоит в
том, что Ассемблер позволяет вам достигать любой части памяти и
непосредственно управлять любым устройством ввода-вывода, пос-
кольку программы на Ассемблере могут делать все то, на что спосо-
бен процессор. С другой стороны, поскольку Ассемблер является
"родным" языком компьютера, хорошо написанная на Ассемблере прог-
рамма позволит получить код с наименьшим временем выполнения. Ка-
чество выполняемого кода, получаемого в других языках, страдает
от того, что приходится выполнять трансляцию с этого языка на ма-
шинный язык, а код на языке ассемблера отображается в машинный
язык непосредственно, без малейшей потери эффективности. На языке
Ассемблера вы указываете компьютеру, что нужно делать, и он дела-
ет именно это - не больше и не меньше.
Конечно, если вы напишете на Ассемблере неэффективную прог-
рамму, она не будет быстро работать, поскольку процессор работает
в точности так, как это определено программой. Аналогично, в язы-
ке Ассемблера отсутствует широкая поддержка встроенного преобра-
зования типов данных или предохранение от таких возможных ошибок,
как случайная запись в переменную или выход за конец массива. Все
это означает, что на Ассемблере вы можете писать очень быстрые и
развитые программы, но такие программы потребуют от вас больше
мастерства и внимания, чем программы, написанные на других язы-
ках.
Теперь, когда вы поняли соотношение между процессором и его
языком Ассемблера, давайте рассмотрим конкретно язык Ассемблера
процессора 8088.
Процессоры 8088 и 8086
-----------------------------------------------------------------
Процессор 8088 - это процессор, который используется в
компьютерах IMP PC и XT, и благодаря которому создано одно из на-
иболее удачных семейств компьютеров. Однако процессор 8088 предс-
тавляет собой только один процессор из серии процессоров, извест-
ных, как серия iAPx86. Другие процессоры данной серии включают в
себя процессоры 8086, используемые в компьютерах IBM модели 25 и
30, процессор 80286, используемый в компьютере IBM AT и IBM PS/2
(модели 50 и 60) и процессор 80386, применяемый в компьютерах IBM
PS/2 модели 80. Каждый из этих процессором так или иначе отлича-
ется от процессора 8088. В Главе 11 "Процессор 80386 и другие
процессоры" различные процессоры серии iAPx86 обсуждаются более
подробно. Процессоры серии iAPx86 объединяет одно общее свойство:
все они могут выполнять код, написанный для процессоров 8086 и
8088.
Процессор 8086 представляет собой основание всей ветви про-
цессоров серии iAPx86. Процессор 8086 - это тот же процессор 8088
с расширенной шиной внешних данных. В то время как процессор 8086
может осуществлять передачу данных в память и из память по 16 би-
тов за операцию, процессор 8088 может передавать данные только по
8 бит. Оба процессора имеют одинаковый набор инструкций. Вообще
говоря, язык Ассемблера, используемый для программирования на IBM
PC и последующих компьютерах, известен, как язык Ассемблера про-
цессора 8086, а не язык Ассемблера процессора 8088. Поэтому при
изучении остальной части данной главы нужно иметь в виду, что
язык Ассемблера процессора 8086 включает в себя также Ассемблер
процессора 8088.
Возможности процессора 8086
-----------------------------------------------------------------
По современным стандартам процессор 8086 обладает скромными
возможностями. Кроме того, процессор 8086 был разработан десять
лет назад, и 10 лет технологической эволюции внесли много нового
в область проектирования микросхем. Тем не менее, процессор 8086
продолжает играть важную роль. Одной из причин этого является все
возрастающее количество персональных компьютеров IВM PC и совмес-
тимых с ними компьютеров. Никто не может игнорировать более чем
десятимиллионный парк компьютеров. Другая причина, однако, заклю-
чается в том, что даже сегодня процессор 8086 отвечает потребнос-
тям развитого программного обеспечения.
Процессор 8086 может адресоваться к большому объему памяти
(более одного миллиона символов или других байтовых (8-битовых)
значений), имеет мощный набор инструкций и при соответствующем
программировании может обеспечивать работу высокоэффективных
программ. Однако процессор 8086 представляет собой сверхбыстрый
процессор. Не каждый язык обеспечивает на процессоре 8086 должную
производительность и никакой другой язык не сравниться с Ассем-
блером при разработке превосходных программ процессора 8086.
(Процессор 8086 работает со скоростью 4.77 или 8 мегагерц, про-
цессор 80286 - со скоростью 6, 8, 10, 12 и даже 16 мегагерц, про-
цессор 80386 может работать со скоростью 16, 20 и 35 мегагерц.)
Программисту, работающему на Ассемблере, предоставляются та-
кие ресурсы процессора 8086, как память, интерфейс ввода-вывода,
регистры и, конечно, инструкции. Рассмотрим далее эти ресурсы.
Память
-----------------------------------------------------------------
Процессор 8086 может адресоваться к памяти объемом 1 мега-
байт (это два в двадцатой степени или 1048576 ячеек памяти, каж-
дая размером 8 бит). Первый байт памяти имеет адрес 0, а послед-
ний - адрес 0FFFFFh (см. Рис. 4.2).
Адрес 0FFFFFh - это шестнадцатиричная форма (по основанию
16) записи, о чем говорит суффикс h. В десятичном виде (по осно-
ванию 10) это эквивалентно значению 1048575. Использование записи
в шестнадцатиричном виде - существенная черта программирования на
Ассемблере. Шестнадцатиричной записи мы коснемся в Главе 5 "Ос-
новные элементы программы на Ассемблере".
Шестнадцатиричный ---------------------- Десятичный адрес
адрес 00000 | | 0
00001 | | 1
00002 | | 2
00003 | | 3
00004 | | 4
00005 | | 5
00006 | | 6
00007 | | 7
00008 | | 8
00009 | | 9
0000A | | 10
0000B | | 11
0000C | | 12
0000D | | 13
0000E | | 14
0000F | | 15
00010 | | 16
|--------------------|
. .
. .
. .
FFFEF | | 1048559
FFFF0 | | 1048560
FFFF1 | | 1048561
FFFF2 | | 1048562
FFFF3 | | 1048563
FFFF4 | | 1048564
FFFF5 | | 1048565
FFFF6 | | 1048566
FFFF7 | | 1048567
FFFF8 | | 1048567
FFFF9 | | 1048569
FFFFA | | 1048570
FFFFB | | 1048571
FFFFC | | 1048572
FFFFD | | 1048573
FFFFE | | 1048574
FFFFF | | 1048575
----------------------
Рис. 4.2 Пространство адресов памяти процессора 8086.
Один байт размером 8 бит может содержать один символ, или
одно целое значение в диапазоне от 0 до 255. Это не означает, что
процессор 8086 не может работать с большими значениями. Два байта
(которые называются словом) могут одно целое значение в диапазоне
от 0 до 65535. Процессор 8086 может работать как с байтами, так и
со словами.
Четыре вместе взятых байта (которые называются двойным сло-
вом) могут содержать целое значение в диапазоне от 0 до
4294967295 или одно значение с плавающей точкой (плавающей запя-
той) с одинарной точностью. Четыре вместе взятых байта (четверное
слово) могут содержать одно значение с плавающей точкой двойной
точности. Процессор 8086 не обрабатывает эти два последние типа
данных непосредственно, однако сопроцессор 8087 может непосредс-
твенно работать со значениями с плавающей точкой и целыми значе-
ниями с расширенной точностью. При наличии соответствующего прог-
раммного обеспечения процессор 8086 может выполнять виртуальную
обработку любого типа данных, хотя и медленнее.
В любой момент программа процессора 8086 может считать или
изменить содержимое любого из более чем 1000000 байт памяти. Нап-
ример, фрагмент программы:
.
.
.
mov ax,0
mov dx,ax
mov bx,0
mov al,[bx]
.
.
.
загружает содержимое байта по адресу 0 в регистр AL. Здесь не
стоит беспокоиться о деталях: на самом деле пространство адресов
памяти процессора 8086 обеспечивает память для рабочих значений,
несколько превышающих 1000000, к которым процессор 8086 может по-
лучить гибкий, быстрый и оперативный доступ.
Один мегабайт - это большая память, существенно большая, чем
64 килобайта (2 в степени 16 или 65536 байт), адресуемых процес-
сорами, предшествующими процессорам 8086. С другой стороны, пос-
ледняя модель процессора этой серии, процессор 80386, может обра-
щаться к памяти в 4000 раз большей, чем память процессора 8086.
Поэтому, как вы можете видеть, процессор 8086 все же весьма огра-
ничен в использовании памяти. Кроме того, в компьютере IBM PC из
одного мегабайта адресного пространства доступно для общего ис-
пользования только 640К (килобайт). Остальное адресное пространс-
тво предназначено для использования системным программным обеспе-
чением, а также занято памятью, используемой для работы с
дисплеем (видеопамять). К тому же, не следует забывать о том, что
инструкции, а также данные, хранятся в памяти, поэтому данные и
код программы должна помещаться в компьютере РС в память объемом
не более 640К.
В то время как процессор 8086 может адресоваться к памяти
объемом 1 мегабайт, практически не так просто одновременно полу-
чить доступ с более чем 64К (64 килобайта) памяти. Это связано со
специфическим средством, которое называется сегментацией. Сегмен-
тацию мы рассмотрим в последующих разделах ("Сегментные регист-
ры").
Ввод и вывод
-----------------------------------------------------------------
Процессор 8086 поддерживает устройства ввода-вывода двумя
способами: с помощью инструкций ввода-вывода и через адреса памя-
ти. Некоторые устройство ввода вывода управляются с помощью пор-
тов, которые представляют собой специальные адреса ввода-вывода в
отдельном от 1 мегабайта адресном пространстве в 64К (см. Рис.
4.3).
Адрес памяти Адрес ввода-вывода (порт)
---------------------- ----------------------
00000 | | 0000 | |
00001 | | 0001 | |
00002 | | 0002 | |
00003 | | 0003 | |
00004 | | 0004 | |
00005 | | 0005 | |
00006 | | 0006 | |
00007 | | 0007 | |
00008 | | 0008 | |
00009 | | 0009 | |
0000A | | 000A | |
|--------------------| |--------------------|
. . . .
. . . .
. . . .
|--------------------| |--------------------|
FFFF5 | | FFF5 | |
FFFF6 | | FFF6 | |
FFFF7 | | FFF7 | |
FFFF8 | | FFF8 | |
FFFF9 | | FFF9 | |
FFFFA | | FFFA | |
FFFFB | | FFFB | |
FFFFC | | FFFC | |
FFFFD | | FFFD | |
FFFFE | | FFFE | |
FFFFF | | FFFF | |
---------------------- ----------------------
Рис. 4.3 Память и адреса ввода-вывода процессора 8086.
Адресов ввода-вывода у процессора 8086 намного меньше, чем
адресов памяти. В то время как технически возможно реализовать
64К адресов ввода-вывода, практически имеются только 4К адресов
ввода-вывода. К тому же адреса ввода-вывода не используются для
хранения значений, а служат для управления и передачи данных в
каналы устройств ввода-вывода. Например, последовательные уст-
ройства, такие, как модемы, управляются целиком с помощью нес-
кольких адресов ввода-вывода.
Доступ к адресам ввода-вывода можно получить с помощью двух
специальных инструкций, IN и OUT, которые больше ни для чего не
используются. Например, инструкция:
out dx,al
посылает содержимое регистра AL в порт ввода-вывода, определяемый
регистром DX. К инструкциям IN и OUT мы вернемся в Главе 5 "Ос-
новные элементы программы на Ассемблере".
Некоторые устройства ввода-вывода представляют собой устрой-
ства с отображаемой памятью. Это означает, что они управляются
через обычные адреса памяти, а не адреса ввода-вывода. Особенно
это относится к дисплейным адаптерам, которые могут использовать
16К, 32К или даже 256К пространства адресов памяти процессора
8086 для своих битовых массивов (массивов, описывающих точки, ко-
торые адаптеры выводят на экран).
Данное устройство может управляться как с помощью адресов
ввода-вывода, так и с помощью адресов отображаемой памяти. Факти-
чески, при работе с дисплейными адаптерами для некоторых функций
используются инструкции ввода-вывода, а для других - адреса памя-
ти.
Регистры
-----------------------------------------------------------------
В процессоре 8086 имеется несколько быстрых элементов памяти
на интегральных схемах, которые называются регистрами. Регистры
можно рассматривать, как ячейки памяти, к которым процессор 8086
может обращаться быстрее, чем к обычной памяти, но это только
часть особенностей регистров. Каждый из регистров имеет уникаль-
ную природу и предоставляет определенные возможности, которые
другими регистрами или ячейками памяти не поддерживаются.
Регистры разбиваются на четыре категории: регистры флагов,
регистры общего назначения, указатель инструкций и сегментные ре-
гистры (см. Рис. 4.4).
Регистр флагов
15 0
--------------------------------------------------------
FLAGS | |
--------------------------------------------------------
Регистры общего назначения
--------------------------------------------------------
AX | AH | AL |
--------------------------------------------------------
--------------------------------------------------------
BX | BH | BL |
--------------------------------------------------------
--------------------------------------------------------
CX | CH | CL |
--------------------------------------------------------
--------------------------------------------------------
DX | DH | DL |
--------------------------------------------------------
--------------------------------------------------------
SI | |
--------------------------------------------------------
--------------------------------------------------------
DI | |
--------------------------------------------------------
--------------------------------------------------------
BP | |
--------------------------------------------------------
--------------------------------------------------------
SP | |
--------------------------------------------------------
Указатель инструкций
--------------------------------------------------------
IP | |
--------------------------------------------------------
Сегментные регистры
--------------------------------------------------------
CS | |
--------------------------------------------------------
--------------------------------------------------------
DS | |
--------------------------------------------------------
--------------------------------------------------------
ES | |
--------------------------------------------------------
--------------------------------------------------------
SS | |
--------------------------------------------------------
Рис. 4.4 Регистры процессора 8086.
Регистр флагов
-----------------------------------------------------------------
Этот 16-разрядный (16-битовый) регистр содержит всю необхо-
димую информацию о состоянии процессора 8086 и результатах пос-
ледних инструкций (см. Рис. 4.5).
15 0
------------------------------------------------------------
| | | | | O | D | T | S | Z | | A | P | P | | C |
------------------------------------------------------------
Битовые флаги:
O - флаг переполнения;
D - флаг направления;
I - флаг прерывания;
T - флаг перехвата;
S - флаг знака;
Z - флаг нуля;
A - флаг дополнительного переноса;
P - флаг четности;
C - флаг переноса.
Рис. 4.5 Регистр флагов процессора 8086.
Например, если вы хотите знать, получен ли при вычитании ну-
левой результат, непосредственно после этой инструкции вам следу-
ет проверить флаг нуля (бит Z в регистре флагов). Если он уста-
новлен (то есть имеет ненулевое значение), это будет говорить о
том, что результат нулевой. Другие флаги, такие, как флаги пере-
носа и переполнения аналогичным образом сообщают о результатах
арифметических и логических операций.
Другие флаги управляют режимом операций процессора 8086.
Флаг направления управляет направлением, в котором строковые
инструкции выполняют перемещение, а флаг прерывания управляет
тем, будет ли разрешено внешним аппаратным средствам, таким, нап-
ример, как клавиатура или модем, временно приостанавливать теку-
щий код для выполнения функций, требующих немедленного обслужива-
ния. Флаг перехвата используется только программным обеспечением,
которое служит для отладки другого программного обеспечения (от-
ладчики).
Регистр флагов не считывается и не модифицируется непос-
редственно. Вместо этого регистр флагов управляется в общем слу-
чае с помощью специальных инструкций (таких, как CLD, STI и CMC),
а также с помощью арифметических и логических инструкций, модифи-
цирующих отдельные флаги. И наоборот, содержимое отдельных разря-
дов регистра флагов влияет на выполнение инструкций (например,
JZ, RCR и MOVSB). Регистр флагов не используется на самом деле,
как ячейка памяти, вместо этого он служит для контроля за состоя-
нием и управления процессором 8086.
Иначе говоря, другие регистры и память содержат данные, а
регистр флагов содержит информацию о соотношении между данными,
результатах операций и состоянии процессора 8086 в целом.
Регистры общего назначения
-----------------------------------------------------------------
Восемь регистров общего назначения (или общих регистров)
процессора 8086 (каждый размером 16 бит) используются в операциях
большинства инструкций в качестве источника или приемника при пе-
ремещении данных и вычислениях, указателей на ячейки памяти и
счетчиков. Каждый регистр общего назначения может использоваться
для хранения 16-битового значения, в арифметических и логических
операциях, может выполняться обмен между регистром и памятью (за-
пись из регистра в память и наоборот). Например, в данном фраг-
мента программы:
.
.
.
mov ax,5
mov dx,9
add ax,dx
.
.
.
значение 5 загружается в регистр AX, значение 9 - в DX, и эти два
значения складываются вместе. При этом результат (14) сохраняется
в регистре AX. Вместо регистров AX и DX здесь можно использовать
регистр CX, SI или любой другой регистр общего назначения.
Кроме такого общего свойства регистров, как использования их
для хранения значений или в качестве источника и приемника при
работе в инструкциях с данными, каждый регистр общего назначения
имеет свою особенность. Поэтому рассмотрим далее каждый из них
отдельно.
Регистр AX
-----------------------------------------------------------------
Регистр AX называют также накопителем (аккумулятором). Этот
регистр всегда используется в операциях умножения или деления и
является также одним из тех регистров, который можно использовать
для наиболее эффективных операций (арифметических, логических или
операций перемещения данных).
Младшие 8 бит регистра AX называются также регистром AL, а
старшие 8 бит - регистром AH. Это может оказаться удобным при ра-
боте с данными размером в байт. Таким образом, регистр AX можно
использовать, как два отдельных регистра. В следующем фрагменте
программы регистр AH устанавливается в значение 0, это значение
копируется в AL и затем в регистр AL добавляется 1.
.
.
.
mov ah,0
mov al,ah
inc al
.
.
.
В результате в регистре AX будет записано значение 1. Ре-
гистры BX, CX и DX могут аналогичным образом использоваться либо
как один 16-разрядный регистр, либо как два 8-разрядных.
Регистр BX
-----------------------------------------------------------------
Регистр BX может использоваться для ссылки на ячейку памяти
(указатель). Более подробно мы рассмотрим это в Главе 5 "Основные
элементы программы на Ассемблере". Если говорить кратко, то
16-битовое значение, записанное в BX, может использоваться в ка-
честве части адреса ячейки памяти, к которой производится доступ.
Например, следующий код загружает в AL содержимое адреса памяти
9:
.
.
.
mov ax,0
mov ds,ax
mov bx,9
mov al,[bx]
.
.
.
Как можно заметить, перед обращением к ячейке памяти, на ко-
торую указывает BX, мы загрузили в DS значение 0 (через регистр
AX). Это результат сегментной организации памяти процессора 8086,
о которой мы уже ранее упоминали. (К этой теме мы вернемся в раз-
деле "Сегментные регистры".) По умолчанию, когда BX используется
в качестве указателя на ячейку памяти, он ссылается на нее отно-
сительно сегментного регистра DS.
Как и регистры AX, CX и DX, регистр BX может интерпретиро-
ваться, как два восьмибитовых (8-разрядных) регистра - BH и BL.
Регистр CX
-----------------------------------------------------------------
Специализация регистра CX - использование в качестве счетчи-
ка. Предположим, мы хотим 10 раз повторить выполнение блока инст-
рукций. Это можно сделать следующим образом:
.
.
.
mov cx,10
Begin: .
.
.
.
<блок инструкций, который нужно повторить>
.
.
.
sub cx,1
jnz Begin
.
.
.
Не беспокойтесь, если в программе вы увидите незнакомые эле-
менты. Важно то, что инструкции между меткой Begin и инструкцией
JNZ будут повторяться до тех пор, пока содержимое регистра CX не
станет равным 0. Заметим, что чтобы уменьшить содержимое CX и пе-
рейти на начало цикла Begin, если регистр CX еще не равен 0,
здесь используются две инструкции - SUB CX,1 и JNZ.
Уменьшение значения счетчика и цикл - это часто используемый
элемент программы, поэтому в процессоре 8086 используется специ-
альная инструкция для того, чтобы циклы выполнялись быстрее и
были более компактными. Эта инструкция называется LOOP. Инструк-
ция LOOP (инструкция цикла) вычитает 1 из значения регистра CX и
выполняет переход, если содержимое регистра CX не равно 0 (все
это в одной инструкции). Для приведенного выше примера можно за-
писать такой эквивалент:
.
.
.
mov cx,10
Begin: .
.
.
.
<блок инструкций, который нужно повторить>
.
.
.
loop Begin
.
.
.
К рассмотрению циклов мы вернемся в Главе 5 "Основные эле-
менты программы на языке Ассемблера". А пока запомните, что ре-
гистр CX особенно полезен для использования в циклах и в качестве
счетчика.
Как и регистры AX, BX и DX, регистр CX можно интерпретиро-
вать, как два 8-разрядных регистра - CH и CL.
Регистр DX
-----------------------------------------------------------------
Регистр DX - это единственный регистр, которые может исполь-
зоваться в качестве указателя адреса ввода-вывода в инструкциях
IN и OUT. Фактически, кроме использования регистра DX нет другого
способа адресоваться к портам ввода-вывода с 256 по 65535. Напри-
мер, в следующем фрагменте программы в порт 1000 записывается
значение 62:
.
.
.
mov al,62
mov dx,1000
out dx,al
.
.
.
Другие уникальные качества регистра DX относятся к операциям
деления и умножения. Когда вы делите 32- или 16-битовый делитель,
старшие 16 бит делимого должны быть помещены в регистр DX. После
выполнения деления остаток также сохраняется в DX. (Младшие 16
бит делимого должны быть помещены в AX. Частное от деления также
будет записано в AX.) Аналогично, когда вы перемножаете два
16-битовых сомножителя, старшие 16 бит произведения сохраняются в
DX (младшие 16 бит записываются в регистр AX).
Как и регистры AX, BX и DX, регистр DX можно интерпретиро-
вать, как два 8-разрядных регистра - DH и DL.
Регистр SI
-----------------------------------------------------------------
Как и регистр BX, регистр SI может использоваться, как ука-
затель на ячейку памяти. Например:
.
.
.
mov ax,0
mov ds,ax
mov si,20
mov al,[si]
.
.
.
Здесь 8-битовое значение, содержащееся по адресу 20, записы-
вается в регистр AL. Особенно полезно использовать регистр SI для
ссылки на память в строковых инструкциях процессора 8086. Напри-
мер:
.
.
.
mov ax,0
mov ds,ax
mov si,20
mov al,[si]
lodsb
.
.
.
Здесь не только содержимое по адресу памяти, на который ука-
зывает SI, сохраняется в регистре AX, но к SI также добавляется
1. Это может оказаться очень эффективным при организации доступа
к последовательным ячейкам памяти (например, к строке текста).
Кроме того, можно сделать так, что строковые инструкции будут ав-
томатически определенное число раз повторять свои действия, так
что отдельная инструкция может выполнить сотни, а иногда и тысячи
действий. Строковые инструкции мы более детально обсудим далее.
Регистр DI
-----------------------------------------------------------------
Регистр DI очень похож на регистр SI в том плане, что его
можно использовать в качестве указателя ячейки памяти. При ис-
пользовании его в строковых инструкциях он имеет также особые
свойства. Например:
.
.
.
mov ax,0
mov ds,ax
mov di,1024
add bl,[di]
lodsb
.
.
.
Здесь 8-битовое значение, расположенное по адресу 1024, за-
писывается в регистр BL. при использовании его в строковых инст-
рукциях регистр DI несколько отличается от регистра SI. В то вре-
мя как SI всегда используется в строковый инструкциях, как указа-
тель на исходную ячейку памяти (источник), DI всегда служит ука-
зателем на целевую ячейку памяти (приемник). Кроме того, в стро-
ковых инструкциях регистр SI обычно адресуется к памяти относи-
тельно сегментного регистра DS, тогда как DI всегда адресует-
ся к памяти относительно сегментного регистра ES. (Когда регистры
SI и DI используются в качестве указателей на ячейки памяти в
других инструкциях (не строковых), то они всегда адресуются к па-
мяти относительно регистра DS.) Например:
.
.
.
cld
mov dx,0
mov es,dx
mov di,2048
stosb
.
.
.
Строковая инструкция STOSB используется здесь и для сохра-
нения значения в регистре AL (по адресу памяти, на который указы-
вает регистр DI), и для добавления к содержимому регистра DI 1.
Однако мы несколько забежали здесь вперед: перед изучением стро-
ковых инструкций нам нужно сперва узнать о сегментах и сегментных
регистрах. Строковые инструкции мы более детально обсудим в далее
в данном руководстве.
Регистр BP
-----------------------------------------------------------------
Как и регистры BX, SI и DI, регистр BP также может использо-
ваться в качестве указателя на ячейку памяти, но здесь есть неко-
торые отличия. Регистры BX, SI и DI обычно ссылаются на память
относительно сегментного регистра DS (или, в случае использования
в строковых инструкциях регистра DI, относительно сегментного ре-
гистра ES), а регистр BP адресуется к памяти относительно регист-
ра SS (сегментный регистр стека).
Здесь мы снова забегаем несколько вперед, поскольку сегменты
мы еще не рассматривали, но принцип именно таков. Один из полез-
ных способов передачи параметров в подпрограмму состоит в занесе-
нии параметров в стек. Так делается в языках Паскаль и Си (см.
главу "Интерфейс Турбо Ассемблера с Турбо Си", где поясняется,
как и почему в языке Си для передачи параметров используется
стек).
Стек находится в сегменте, на который указывает регистр SS.
Например:
.
.
.
push bp
mov bp,sp
mov ax,[bp+4]
.
.
.
Здесь выполняется обращение к сегменту стека для загрузки в
AX первого параметра, передаваемого при вызове Турбо Си подпрог-
раммы на Ассемблере.
Если говорить кратко, то регистр BP создан для обеспечения
работы с параметрами, локальными переменными другой адресации к
памяти с использованием стека.
Регистр SP
-----------------------------------------------------------------
Регистр SP называется также указателем стека. Это "наименее
общий" из регистров общего назначения, поскольку он практически
всегда используется для специальной цели - обеспечения стека.
Стек - это область памяти, в которой можно сохранять значения и
из которой они могут затем извлекаться по дисциплине "послед-
ний-пришел-первый-ушел" (FIFO). То есть последнее сохраненное в
стеке значение будет первым значением, которое вы получите при
чтении из стека. Классической аналогией стека является стопка та-
релок. Поскольку тарелки можно класть столько сверху стопки (и
брать также), то первая положенная тарелка будет последней, кото-
рую вы сможете взять.
Регистр SP в каждый момент времени указывает на вершину сте-
ка. Как и в случае стопки тарелок, вершина стека - это то место,
в котором в стеке сохраняется следующее помещенное туда значение.
Действие, состоящее в занесении значений в стек, называют также
"заталкиванием" (pushing) в стек. В самом деле, инструкция PUSH
используется для занесения значений в стек. Аналогично, действие,
состоящее в извлечении (выборке) значений из стека, называют так-
же "выталкиванием" (popping) из стека (для этого используется
инструкция POP).
На Рис. 4.6 показывается, как изменяются регистры SP, AX и
BP по мере выполнения следующего кода (при этом подразумевается,
что начальное значение SP равно 1000):
.
.
.
mov ax,1
push ax
mov bx,2
push bx
pop ax
pop bx
.
.
.
. .
. .
. .
Вначале: | |
--------------- |----------------|
AX | ? | 996 | ? |
--------------- | |
--------------- |----------------|
BX | ? | 998 | ? |
--------------- | |
--------------- |----------------|
SP | 1000 |-----------------> 1000 | ? |
--------------- | |
|----------------|
. .
. .
. .
. .
После mov ax,1 / push ax: | |
--------------- |----------------|
AX | 1 | 996 | ? |
--------------- | |
--------------- |----------------|
BX | ? | ---------> 998 | 1 |
--------------- | | |
--------------- | |----------------|
SP | 998 |--------- 1000 | ? |
--------------- | |
|----------------|
. .
. .
. .
. .
После mov bx,2 / push bx: | |
--------------- |----------------|
AX | 1 | ---------> 996 | 2 |
--------------- | | |
--------------- | |----------------|
BX | 2 | | 998 | 1 |
--------------- | | |
--------------- | |----------------|
SP | 996 |--------- 1000 | ? |
--------------- | |
|----------------|
. .
. .
. .
После pop ax: | |
--------------- |----------------|
AX | 2 | 996 | ? |
--------------- | |
--------------- |----------------|
BX | 2 | ---------> 998 | 1 |
--------------- | | |
--------------- | |----------------|
SP | 998 |--------- 1000 | ? |
--------------- | |
|----------------|
. .
. .
. .
. .
После pop bx: | |
--------------- |----------------|
AX | 2 | 996 | ? |
--------------- | |
--------------- |----------------|
BX | 1 | 998 | 1 |
--------------- | |
--------------- |----------------|
SP | 1000 |-----------------> 1000 | ? |
--------------- | |
|----------------|
. .
Рис. 4.6 Регистры AX, BX, SP и стек.
Хотя процессор 8086 и позволяет записывать значения в SP или
складывать и вычитать хранящиеся в регистре SP значения (как это
можно делать с обычными регистрами общего назначения), вам не
следует к этому прибегать, если вы не делаете этого преднамерен-
но. Если вы изменяете SP, то изменяется расположение вершины сте-
ка, что быстро может привести к неприятностям.
Почему? Ведь занесение в стек и извлечение из него не явля-
ется единственным способом использования стека. Стек используется
всякий раз, когда вы вызываете или возвращаетесь из подпрограммы
(процедуры или функции). Кроме того, стек используют некоторые
системные ресурсы (такие, как клавиатура или системный таймер),
когда они прерывают процессор 8086, чтобы выполнить свои функции.
Все это означает, что стек может в любой момент потребоваться.
Если вы измените SP, даже на несколько инструкций, то правильное
значение стека может оказаться недоступным, когда он потребуется
системным ресурсам.
Короче говоря, оставьте SP в покое, если только вам не тре-
буется изменить его специально. Можно свободно выполнять операции
занесения в стек и извлечения из него, вызовы и возвраты управле-
ния, но не изменяйте значения регистра SP непосредственно. Любой
из других семи регистров общего назначения можно спокойно изме-
нять в любой момент.
Указатель инструкций
-----------------------------------------------------------------
Указатель инструкций (регистр IP) всегда содержит смещение в
памяти, по которому хранится следующая выполняемая инструкция.
Когда выполняется одна инструкция, указатель инструкций перемеща-
ется таким образом, чтобы указывать на адрес памяти, где хранится
следующая инструкция. Обычно следующей выполняемой инструкцией
является инструкция, хранимая по следующему адресу памяти, но не-
которые инструкции, такие, как вызовы или переходы, могут привес-
ти к тому, что в указатель инструкций будет загружено новое зна-
чение. Таким образом, будет выполнен переход на другой участок
программы.
Значение счетчика инструкций нельзя прочитать или записать
непосредственно. Загрузить в указатель инструкций новое значение
может только специальная инструкция перехода (аналогичная только
что описанным).
Указатель инструкций сам по себе не определяет адрес, по ко-
торому находится следующая выполняемая инструкция. Картину здесь
опять усложняет сегментная организация памяти процессора 8086.
Для извлечения инструкции предусмотрен регистр CS, где хранится
базовый адрес, при этом указатель инструкций задает смещение от-
носительно этого базового адреса.
Каждый раз, когда мы говорим об адресации памяти, нам прихо-
дится упоминать сегменты. И каждый раз мы откладываем на потом
полное объяснение данного вопроса. Теперь пришло время этим за-
няться.
Сегментные регистры
-----------------------------------------------------------------
Теперь мы подошли к наиболее необычному аспекту процессора
8086 - сегментации памяти. Основной предпосылкой сегментации яв-
ляется следующее: процессор 8086 может адресоваться к 1 мегабайту
памяти. Для адресации ко всем ячейкам адресного пространства в 1
мегабайт необходимы 20-разрядные сегментные регистры. Однако про-
цессор 8086 использует только 16-разрядные указатели на ячейки
памяти. Вспомним, например, что для ссылки на память используется
16-разрядный регистр BX. Как же тогда согласовать 16-разрядные
указатели процессора 8086 и 20-разрядные адреса?
Ответ состоит в том, что процессор 8086 использует двухсту-
пенчатую схему адресации. Да, используются 16-разрядные указате-
ли, но эта форма представляет собой только часть полной схемы ад-
ресации. Каждый 16-разрядный указатель памяти или смещение комби-
нируется с содержимым 16-разрядного сегментного регистра для фор-
мирования 20-разрядного адреса памяти.
Сегменты и смещения комбинируются следующим образом: значе-
ние сегмента сдвигается влево на 4 (то есть умножается на 16), а
затем складывается со смещением, как показано на Рис. 4.7.
--------------------------- ----------------
| 16-разрядный сегментный | | 16-разрядное |
| регистр | | смещение |
--------------------------- ----------------
| |
V |
-------------------- |
( умножение на 16 ) |
( /сдвиг влево на 4/ ) |
-------------------- |
| |
V |
---------------------- |
| Значение сегмента, | |
| умноженное на 16, | |
| равно 20-разрядно- | |
| му значению | |
---------------------- |
| |
| ----- |
---------->( + )<-------------
-----
|
V
---------------------------------------
| 20-разрядное значение адреса памяти |
---------------------------------------
Рис. 4.7 20-разрядные адреса памяти.
Рассмотрим, например, следующий фрагмент программы:
.
.
.
mov ax,1000h
mov ds,ax
mov si,201h
mov dl,[si]
.
.
.
Здесь для сегментного регистра DS устанавливается значение
1000h, SI устанавливается в значение 201h. Мы можем представить
их в виде 4сегмент:смещение" - 1000:201h. (Эффективные вычисления
для пары "сегмент:смещение" могут выполняться только по основа-
нию 16. Это еще одна причина необходимости познакомиться с шест-
надцатиричной арифметикой.) Адрес в DL, из которого загружается
адрес, представляет собой:
((DS * 16) + SI) или ((1000h * 16) + 201h)
1000h
Х 16
------
10000h
Этот пример иллюстрируется на Рис. 4.8.
--------------------------- ----------------
DS | 1000h | SI | 201h |
--------------------------- ----------------
| |
V |
-------------------- |
( умножение на 16 ) |
( /сдвиг влево на 4/ ) |
-------------------- |
| |
V |
---------------------- |
| 10000h | |
---------------------- |
| |
| ----- |
---------->( + )<-------------
-----
|
V
-----------------------
Адрес памяти | 10201h |
-----------------------
Рис. 4.8 Вычисление адреса памяти с помощью инструкции mov.
С другой стороны это можно рассматривать просто как сдвиг
значения сегмента на 4 бита, или одну шестнадцатиричную цифру,
что эквивалентно умножению на 16:
1000
+ 201
-----
10201
Теперь вы можете видеть, что программа получает доступ к
полному адресному пространству в 1 мегабайт с помощью использова-
ния только пары "сегмент:смещение". Фактически, для доступа к па-
мяти вы всегда должны использовать пару "сегмент:смещение". Все
инструкции и режимы адресации процессора 8086 по умолчанию рабо-
тают относительно того или иного сегментного регистра, хотя в не-
которых инструкциях можно явно указать, что нужно использовать
желаемый сегментный регистр.
Вам редко потребуется загружать значение непосредственно в
сегментный регистр. Вместо этого вы будете загружать в сегментные
регистры имена сегментов, которые в ходе ассемблирования, компо-
новки и выполнения превращаются в числа. Это необходимо, посколь-
ку нет способа сказать заранее, где в памяти будет находиться
данный сегмент: это зависит от версии DOS, числа и размера рези-
дентных в памяти программ, а также потребности в памяти остальной
части программы. Использование имен сегментов позволяет Турбо Ас-
семблеру и операционной системе DOS выполнять подобные вычисле-
ния.
Наиболее общим именем сегмента является @Date, которое в уп-
рощенных директивах определения сегментов используется для ссылки
на используемый по умолчанию сегмент данных. Например:
DOSSEG
.MODEL SMALL
.DATA
var1 DW 0
.
.
.
.CODE
mov ax,@data
mov ds,ax
.
.
.
END
Здесь регистр DS загружается таким образом, что он будет
указывать на используемый по умолчанию сегмент данных, в котором
находится Var1.
Здесь мы опять забежали вперед: упрощенные директивы опреде-
ления сегментов и загрузку сегментных регистров мы обсудим в сле-
дующей главе.
Использование сегментов процессора 8086 приводит к некоторым
интересным моментам. Один из них состоит в том, что только блок
памяти размером в 64К в любой момент может адресоваться через
сегментный регистр, так как 64К - это максимальный объем памяти,
к которой можно адресоваться с помощью 16-битового смещения. Это
может оказаться неприятным при работе с большим (более 64К) объ-
емом памяти, так как и значение сегментного регистра, и смещение,
придется часто изменять.
Адресация к большим блокам памяти в процессоре 8086 может
представлять еще большую трудность, поскольку, в отличие от ре-
гистров общего назначения (общих регистров), сегментные регистры
не могут использоваться в качестве источников или приемников в
арифметических и логических инструкциях. Фактически, единственная
операция, которую можно выполнять с сегментными регистрами, сос-
тоит в копировании значений между сегментными регистрами и други-
ми общими регистрами или памятью. Например, чтобы добавить значе-
ние 100 к регистру ES, потребуется следующее:
.
.
.
mov ax,es
add ax,100
mov es,ax
.
.
.
Из всего этого можно сделать заключение, что процессор 8086
лучше подходит для работы с памятью в блоках, не превышающих 64К.
Второй момент использования сегментов состоит в том, что
каждая ячейка памяти адресуется через многие возможные сочетания
"сегмент:смещение". Например, адрес памяти 100h адресуется с по-
мощью следующих значений "сегмент:смещение": 0:100h, 1:F0h, 2:E0h
и т.д., так как при вычислении всех этих пар "сегмент:смещение"
получается значение адреса 100h.
Аналогично регистрам общего назначения каждый сегментный ре-
гистр играет свою, конкретную роль. Регистр CS указывает на код
программы, DS указывает на данные, SS - на стек, сегмент (сегмен-
тный регистр) ES - это "трафаретный" (дополнительный) сегмент,
который может использоваться так, как это необходимо. Рассмотрим
сегментные регистры более подробно.
Регистр CS
-----------------------------------------------------------------
Регистр CS указывает на начало блока памяти объемом 64К, или
сегмент кода, в котором находится следующая выполняемая инструк-
ция. Следующая инструкция, которую нужно выполнить, находится по
смещению, определяемому в сегменте кода регистром IP, то есть на
нее указывает адрес (в форме "сегмент:смещение") CS:IP. Процессор
8086 никогда не может извлечь инструкцию из сегмента, отличного
от того, который определяется регистром CS.
Регистр CS можно изменять с помощью многих инструкций, вклю-
чая отдельные инструкции перехода, вызовы и возвраты управления.
Ни при каких обстоятельствах регистр CS нельзя загрузить непос-
редственно.
Никакие другие режимы адресации или указатели памяти, отлич-
ные от IP, не могут нормально работать относительно регистра CS.
Регистр DS
-----------------------------------------------------------------
Регистр DS указывает на начало сегмента данных, которые
представляет собой блок памяти объемом 64К, в котором находится
большинство размещенных в памяти операндов. Обычно для ссылки на
адреса памяти используются смещения, предполагающие использование
регистров BX, SI или DI. В основном сегмент данных представляет
собой то, о чем говорит его название: как правило это сегмент, в
котором находится текущий набор данных. Адресация памяти обсужда-
ется далее, в Главе 5 "Основные элементы программы на языке Ас-
семблера".
Регистр ES
-----------------------------------------------------------------
Регистр ES указывает на начало блока памяти объемом 64К, ко-
торый называется дополнительным сегментом. Как и подразумевает
его название, дополнительный сегмент не служит для какой-то конк-
ретной цели, но доступен тогда, когда в нем возникает необходи-
мость. Иногда дополнительный сегмент используется для выделения
дополнительного блока памяти объемом 64К для данных. Однако дос-
туп к памяти в дополнительном сегменте менее эффективен, чем дос-
туп к памяти в сегменте данных (см. Главу 10 "Развитое программи-
рование на Турбо Ассемблере").
Особенно полезен дополнительный сегмент, когда используются
строковые инструкции. Все строковые инструкции, которые выполняют
запись в память, используют в качестве адреса памяти, в которую
нужно выполнить запись, пару регистров ES:DI. Это означает, что
регистр ES особенно полезен при использовании его в качестве це-
левого сегмента при копировании блоков, сравнении строк, просмот-
ре памяти и очистке блоков памяти. Мы рассмотрим строковые инст-
рукции и использование в связи с ними регистра ES в Главе 5 "Бо-
лее подробно о программировании на Турбо Ассемблере".
Регистр SS
-----------------------------------------------------------------
Регистр SS указывает на начало сегмента стека, которые
представляет собой блок памяти объемом 64К, в котором находится
стек. Все инструкции, которые неявно используют регистр SP (вклю-
чая занесение в стек, извлечение из стека, вызовы и возвраты уп-
равления), работают с сегментом стека, так как только регистр SP
может использоваться для адресации памяти в сегменте стека.
Как мы обсуждали ранее, регистр BP также работает относи-
тельно сегмента стека. Это позволяет использовать регистр BP для
доступа к параметрам и переменным, которые хранятся в стеке. Од-
нако более подробно адресацию к памяти мы обсудим в следующей
главе.
Набор инструкций процессора 8086
-----------------------------------------------------------------
Для программиста ключевым ресурсом процессора 8086 является
набор инструкций. Как мы уже обсуждали ранее, набор инструкций
включает в себя все действия, которые программист может заставить
выполнить процессор 8086. Полный набор инструкций Турбо Ассембле-
ра приведен в Таблице 4.1.
Как можно заметить, в наборе инструкций процессора 8086 име-
ется множество инструкций. Эти инструкции выполняют множество
действий, от пустой операции, которая не выполняет никаких функ-
ций (NOP), до копирования 65535 байт (REP MOVSB). Подробному ос-
вещению набора инструкций мы уделим много времени в других гла-
вах.
Набор инструкций Турбо Ассемблера
Таблица 4.1
-----------------------------------------------------------------
Инструкция Процессор Инструкция Процессор
-----------------------------------------------------------------
ADD все SAL все
OR все SHL все
ADC все SAR все
SBB все SHR все
AND все RCL все
SUB все RCR все
XOR все AAA все
CMP все AAS все
XCHG все CBW все
TEST все CWDE 386
MOV все (1) CLC все
ESC все CLD все
JMP все CLI все
CALL все CMC все
INT все CMPSB все
INC все CMPSW все
DEC все CMPSD 386
PUSH все CWD все
POP все CDQ 386
AAD все (2) DAA все
AAM все (2) DAS все
IN все HLT все
OUT все INTO все
LEA все IRET все
LDS все IRETD 386
LES все LAHF все
LSS 386 LODSB все
LFS 386 LODSW все
LGS 386 LODSD 386
DIV все MOVSB все
MUL все MOVSW все
IDIV все MOVSD 386
IMUL все (3) NOP все
NEG все POPF все
NOT все POPFD 386
ROL все PUSHF все
ROR все PUSHFD 386
SAHF все REPE все
SCASB все REPZ все
SCASW все REPNE все
SCASD 386 REPNZ все
STC все SEGCS все (4)
STD все SEGSD все (5)
STI все SEGSS все (6)
STOSB все SEGES все (7)
STOSW все SEGFS 386 (8)
STOSD 386 SEGGS 386 (9)
WAIT все RET все
XLATB все RETN все (10)
STOS все RETF все (11)
SCAS все JA все (12)
LODS все JNBE все (12)
XLAT все JAE все (12)
MOVS все JNB все (12)
CMPS все JNK все (12)
PUSHA 186-386 JB все (12)
PUSHAD 386 JNAE все (12)
POPA 186-386 JC все (12)
POPAD 386 JBE все (12)
LEAVE 186-386 JNA все (12)
INSB 186-386 JE все (12)
INSD 386 JG все (12)
OUTSB 186-386 JNLE все (12)
OUTSW 186-386 JGE все (12)
OUTSD 386 JNL все (12)
INS 186-386 JL все (12)
OUTS 186-386 JNGE все (12)
BOUND 186-386 JLE все (12)
ENTER 186-386 JNG все (12)
LOCK все JNE все (12)
REP все JNZ все (12)
JNO все (12) SETA 386
JNS все (12) SETAE 386
JNP все (12) SETB 386
JPO все (12) SETBE 386
JO все (12) SETC 386
JP все (12) SETE 386
JPE все (12) SETG 386
JS все (12) SETGE 386
LOOP все (13) SETL 386
LOOPE все (14) SETLE 386
LOOPZ все (14) SETNA 386
LOOPNE все (14) SETNAE 386
LOOPNZ все (14) SETNB 386
LOOPW все (15) SETNB 386
LOOPWE все (16) SETNC 386
LOOPWZ все (16) SETNE 386
LOOPWNE все (16) SETNG 386
LOOPWNZ все (16) SETNGE 386
LOOPD 386 (17) SETNL 386
LOOPDE 386 (17) SETNLE 386
LOOPDZ 386 (17) SETNO 386
LOOPDNE 386 (17) SETNP 386
LOOPDNZ 386 (17) SETNS 386
JCXZ все (18) SETNZ 386
JECXZ 386 (19) SETO 386
BT 386 SETP 386
BTC 386 SETPE 386
BTR 386 SETS 386
BTS 386 SETZ 386
BSF 386
BSR 386 APPL 286p+386p
MOVSX 386 CLTS 286p+386p
MOVZX 386 LLDT 286p+386p
SHLD 386 LMSW 286p+386p
SHRD 386 LTR 286p+386p
SLDT 286p+386p FXAM 8087-387
SMSW 286p+386p FXTRACT 8087-387
STR 286p+386p FXAM 8087-387
VERR 286p+386p FXTRACT 8087-387
VERW 286p+386p FYL2X 8087-387
LGDT 286p+386p FYL2XP1 8087-387
LIDT 286p+386p FNCLEX 8087-387
SGDT 286p+386p FNDISI 8087-387
SIDT 286p+386p FNENI 8087-387
LAR 286p+386p FNINIT 8087-387
LSL 286p+386p FADDP 8087-387
FMULP 8087-387 FDIVP 8087-387
FADD 8087-387 FDIVRP 8087-387
FDIV 8087-387 FSUBP 8087-387
FDIVR 8087-387 FSUBRP 8087-387
FMUL 8087-387 FXCH 8087-387
FSUB 8087-387 FCOMPP 8087-387
FCOM 8087-387 FFREE 8087-387
FCOM 8087-387 FIADD 8087-387
FCOMP 8087-387 FICOM 8087-387
FST 8087-387 FICOMP 8087-387
F2XM1 8087-387 FIDIV 8087-387
FABS 8087-387 FIDIVR 8087-387
FCHS 8087-387 FIMUL 8087-387
FCLEX 8087-387 FIST 8087-387
FDECSTP 8087-387 FISUB 8087-387
FDISI 8087-387 FISUBR 8087-387
FENI 8087-387 FLDCW 8087-387
FINCSTP 8087-387 FSTCW 8087-387
FINIT 8087-387 FSTSW 8087-387
FLD1 8087-387 FNSTCW 8087-387
FLDL2E 8087-387 FNSTSW 8087-387
FLDL2T 8087-387 FLDENV 8087-387
FLDLG2 8087-387 FRSTOR 8087-387
FLDLN2 8087-387 FSAVE 8087-387
FLDPI 8087-387 FSTENV 8087-387
FLDZ 8087-387 FNSAVE 8087-387
FNOP 8087-387 FNSTENV 8087-387
FPATAN 8087-387 FLD 8087-387
FPREM 8087-387 FSTP 8087-387
FPTAN 8087-387 FILD 8087-387
FRNDINT 8087-387 FISTP 8087-387
FSCALE 8087-387 FBLD 8087-387
FSQRT 8087-387 FBSTP 8087-387
FIST 8087-387 FWAIT 8087-387
-----------------------------------------------------------------
(1) - реализована на процессоре 386 с использованием специальных
регистров;
(2) - за инструкцией может следовать любое 8-битовое непосредст-
венное значение х (по умолчанию 10);
(3) - расширения процессора 286;
(4) - генерирует переопределение CS, может следовать за инструк-
цией;
(5) - генерирует переопределение DS, может следовать за инструк-
цией;
(6) - генерирует переопределение SS, может следовать за инструк-
цией;
(7) - генерирует переопределение ES, может следовать за инструк-
цией;
(8) - генерирует переопределение FS, может следовать за инструк-
цией;
(9) - генерирует переопределение GS, может следовать за инструк-
цией;
(10) - явный возврат управления ближнего типа;
(11) - явный возврат управления дальнего типа;
(12) - при переходах воспринимает аргумент ближнего или дальнего
типа;
(13) - размер операнда в операторе цикла определяется размером
сегмента;
(14) - аналогично LOOP;
(15) - размер операнда в цикле всегда равен слову (CX);
(16) - аналогично LOOPW;
(17) - размер операнда в цикле всегда равен двойному слову (ECX);
(18) - размер операнда в JCXZ равен слову (CX);
(19) - размер операнда в JECXZ равен двойному слову (ECX).
Компьютеры IBM PC и XT
-----------------------------------------------------------------
Мы сосредоточились на рассмотрении языка Ассемблера процес-
сора 8086, но суть вопроса состоит в том, что процессор 8086
представляет собой часть вычислительной системы, а на программи-
рование на языке Ассемблера в большой степени влияют аппаратная
конфигурация и операционная система.
Подавляющее большинство когда-либо написанных для процессора
8086 программ (и, возможно, большинство программ, написанных за
время существования ЭВМ) написаны для компьютеров IBM PC и XT и
совместимых с ними машин, которые работают под управлением опера-
ционной системы MS-DOS (далее мы будем просто называть это семей-
ство компьютеров IBM PC). Вы также, вероятно, планируете разраба-
тывать программы на Ассемблере для операционной среды IBM PC.
Без знания аппаратной конфигурации и операционной системы,
под управлением которой будут работать ваши программы, нельзя вы-
полнить ввод или вывод данных или даже завершить работу програм-
мы. Мы не будет обсуждать все возможности компьютеров IBM PC и
системного программного обеспечения, но расскажем о некоторых ос-
новных характеристиках IBM PC, предполагая, что вы самостоятельно
изучите соответствующие книги и руководства.
Устройства ввода и вывода
-----------------------------------------------------------------
Во всех компьютерах IBM PC имеется клавиатура, дисплейный
адаптер и монитор, а также дисковод на гибком диске. Часто также
имеются такие устройство, как модемы, принтеры, "мышь" и жесткие
диски. Каждое из этих устройств управляется с помощью весьма
сложной последовательности обращений к портам ввода-вывода или
памяти (или к тому и другому). Например, выбор нового видеорежима
на цветном графическом адаптере (CGA) требует выполнения более 30
инструкций OUT, а последовательности инструкций, использующиеся
для управления клавиатурой, модемом или диском еще более сложные.
Означает ли это, что для написания на Ассемблере полезных
программ для IBM PC вам потребуется освоить бесконечные последо-
вательности инструкций управления? Вовсе нет, системное прог-
раммное обеспечение вашего IBM PC выполнит за вас большую часть
этой работы.
Системное программное обеспечение для семейства IBM PC
-----------------------------------------------------------------
Системное программное обеспечение - это такое программное
обеспечение, которое служит для управления и используется в ка-
честве промежуточного уровня между прикладным программным обеспе-
чением (например, Турбо Ассемблером или Quattro) и аппаратным
обеспечением вашего компьютера (см. Рис. 4.9).
--------------------------------------------------------------
| Прикладное программное обеспечение |
--------------------------------------------------------------
| | |
| V |
| -------------------------------------- |
| | DOS | |
| | (обращение а DOS через функции | |
| | INT 21 и другие прерывания) | |
| -------------------------------------- |
| | |
| V V
| ---------------------------------
| | BIOS |
| | (обращение к базовой системе |
| | ввода-вывода (BIOS) через |
| | функции BIOS с помощью неко- |
| | торых прерываний) |
| ---------------------------------
| |
V V
--------------------------------------------------------------
| Аппаратное обеспечение IBM PC |
| Дисплейный адаптер, клавиатура, принтер, диск, "мышь", |
| джойстик и т.д. Обращение к устройствам - через порты |
| ввода-вывода и ячейки памяти (зависит от конкретных |
| устройств) |
--------------------------------------------------------------
Рис. 4.9 Программное обеспечение DOS и BIOS с точки зрения
управления и интерфейсного уровня.
В частности, системное программное обеспечение обрабатывает
все сложности организации интерфейса с отдельными устройствами.
Например, для того, чтобы обработать на вашем компьютере IBM PC
одно нажатие клавиши, требуется программа в несколько сотен строк
на Ассемблере, однако ваша программа может получить символ (код)
клавиши с помощью только одной системной функции. Это стало воз-
можным с помощью двух основных компонентов системного программно-
го обеспечения IBM PC: операционной системы DOS и базовой системы
ввода-вывода BIOS.
Как можно видеть на Рис. 4.9, системы программного обеспе-
чения DOS и BIOS служат управляющим и промежуточным уровнем между
прикладным программным обеспечением и аппаратным обеспечением IBM
PC. Прикладное программное обеспечение всегда имеет возможность
управлять аппаратными средствами непосредственно, но лучше по
возможности пользоваться функциями DOS и BIOS.
Операционная система DOS
-----------------------------------------------------------------
Операционная система DOS (известная также, как PC-DOS или
MS-DOS) - это программа, которая управляет вашим компьютером с
момента считывания им диска после включения питания и пока вы его
не выключите. DOS занимает часть из 640К доступной оперативной
памяти, но это малая плата за те трудности, с которыми пришлось
бы столкнуться при ее отсутствии. DOS выводит на экране подсказ-
ку A> или C> (или другую подсказку, если она задана на вашем
компьютере). Именно DOS воспринимает и выполняет такие команды,
как DIR.
Но это только видимая часть DOS. В операционной системе пре-
дусмотрено также большое число функций, которые широко использу-
ются любой прикладной задачей. С помощью функций DOS прикладные
задачи выполняют чтение из файлов и запись в них данных, получают
символы клавиш, или устанавливают и получают текущее время. Нап-
ример, фрагмент программы на Ассемблере:
.
.
.
mov ah,2 ; функция DOS вывода символа
mov dl,'A' ; A - это символ, который
; нужно вывести на экран
int 21h
.
.
.
вызывает функцию DOS вывода на экран, чтобы вывести символ A в
текущей позиции курсора.
Функции DOS следует вызывать, где это возможно, для таких
операций, как ввод с клавиатуры или из файла, вывод на экран или
в файл и печать информации. Поскольку сама операционная система
DOS - это не что иное, как программа на Ассемблере, ваши програм-
мы на Ассемблере могут делать все то, что делает операционная
система, но это нельзя назвать хорошим методом программирования.
Не все РС-подобные компьютеры одинаковы, и DOS часто скрывает
различия между ними. Таким образом, если вы игнорируете функции
DOS и выходите непосредственно на аппаратуру, ваши программы мо-
гут не работать на других машинах.
Кроме того может оказаться, что программы, работающие в об-
ход DOS, не смогут сосуществовать с другими программами, напри-
мер, с такими резидентными в памяти программами, как SideKick и
SuperKey. К тому же зачем писать лишний код, когда DOS уже сдела-
ла для вас эту работу? Короче, если функция DOS может выполнить
то, что вам нужно, используйте ее.
Основным справочником по функциям DOS может служить "Спра-
вочное руководство по DOS". (Имеется также ряд полезных русскоя-
зычных и англоязычных справочных программ по DOS, в которых можно
найти всю информацию по функциям DOS и BIOS.)
В том случае, когда DOS не обеспечивает нужную вам функцию,
можно использовать функцию BIOS. Мы кратко расскажем о функциях
BIOS, но давайте сначала рассмотрим наиболее существенные функции
DOS, выполняющие ввод, вывод и завершение программы.
Получение символов с клавиатуры
-----------------------------------------------------------------
Ввод информации с клавиатуры - один из основных способов
взаимодействия с компьютером IBM PC. DOS обеспечивает ряд функ-
ций, с помощью которых программа на ассемблере может обрабатывать
нажатия клавиш. Мы обсудим только часть из них.
Возможно одним из наиболее простых способов получения симво-
лов клавиш является функция "Ввод с клавиатуры", то есть функция
DOS номер 1. Функции DOS вызываются путем помещения номера функ-
ции в регистр AH и выполнения затем инструкции INT 21h. (Действи-
тельная работа инструкции INT несколько более сложна, но сейчас
вам требуется только знать, что каждый раз при вызове функции DOS
вы должны выполнять инструкцию INT 21h.) Следующий набранный на
клавиатуре символ возвращается в регистре AL.
Например, когда выполняется код:
.
.
.
mov ah,1
int 21h
.
.
.
операционная система DOS помещает следующий набранный на клавиа-
туре символ в регистр AL. Заметим, что если клавиша не нажата,
DOS будет ждать, когда она будет нажата, поэтому для выполнения
данной функции может потребоваться неопределенное время.
Вывод символов на экран
-----------------------------------------------------------------
Если нажатия клавиш означают взаимодействие пользователя с
программным обеспечением, то экран является дополнением. IBM PC
оснащаются дисплеями различных типов, начиная от цветного тексто-
вого до графического с высоким разрешением, но в данный момент мы
рассмотрим только вывод символов.
Функция DOS с номером 2 обеспечивает наиболее непосредствен-
ный путь вывода символа на экран. Для этого нужно просто помес-
тить 2 в регистр AH и выводимый символ в регистр DL, а затем вы-
звать DOS с помощью INT 21h. Следующий код отображает каждый вве-
денный символ на экране:
.
.
.
mov ah,1
int 21h ; получить код следующей нажа-
; той клавиши
mov ah,2
mov dl,al ; переместить считанный
; символ из AL в DL
int 21h ; вывести его на экран
.
.
.
Имеется также ряд других функций для считывания и вывода
символов и строк символов. Некоторые из них вы найдете в примерах
программ данного руководства. Поскольку для описания всех функций
DOS потребуется целая книга, мы не можем здесь рассказать обо
всех функциях. Однако мы настоятельно рекомендуем вам изучить
хотя бы некоторые из соответствующих книг и руководств и узнать о
функциях DOS поподробнее, поскольку они являются в программирова-
нии на Ассемблере ключевым ресурсом.
Есть еще одно замечание, которое нужно сделать относительно
клавиатуры, экрана и файлового ввода и вывода на языке Ассембле-
ра. Те из вас, кто пользовался функциями scanf и printf в языке
Си или функциями Readln n Writeln в Паскале, возможно с удивлени-
ем узнают, что в DOS не предусмотрено форматного ввода и вывода.
DOS выполняет только посимвольный или построчный ввод-вывод. В Си
для печати целой переменной вам требуется сделать следующее:
printf("\\d\n",i);
Си автоматически преобразует целое значение, которое хранит-
ся в 16-битовой ячейке памяти, в строку символов кода ASCII и пе-
чатает символы. В Ассемблере ваша программа должна явно преобра-
зовывать переменные в строки символов, перед тем, как вывести их
на экран. Аналогично, DOS знает только, как считывать символы и
строки символов с клавиатуры, поэтому вам придется писать прог-
раммы, преобразующие вводимые пользователем строки и символы в
другие данные.
В конце следующей главы мы покажем вам пример программы, ил-
люстрирующий все то, что вам нужно сделать, чтобы напечатать зна-
чение переменной. Сейчас запомните только, что функции DOS могут
печатать символы или строку символов, и ничего более. Функция по
преобразованию данных в символ, с которым может работать DOS,
возлагается на вас.
Вывод символов на экран
-----------------------------------------------------------------
Теперь, когда вы немного знаете о чтении и записи в програм-
ме, давайте напишем простую программу, которая выполняет просто
эхоотображение строки набранных на клавиатуре символов на экране.
Все необходимые для этого функции DOS, кроме одной, вы уже знае-
те. Эта последняя функция необходима, поскольку у вас нет способа
завершить программу, когда она закончит выполнение.
Те, кто знаком с языками Паскаль или Си, могут подумать, что
программа на Ассемблере просто закончит работу, когда она дойдет
до конца основной программы. Но это не так. Чтобы завершить свою
программу на Ассемблере, вы должны выполнить явный вызов функции
DOS.
Для завершения программы имеется несколько функций DOS, но
наиболее предпочтительным методом является выполнение функции DOS
с номером 4Ch (или 76 для тех, кто предпочитает десятичный вид).
Зная это, можно теперь написать полную программу отображения сим-
волов:
DOSSEG
.MODEL SMALL
.STACK 100h
.DATA
.CODE
EhcoLoop:
mov ah,1 ; функция DOS ввода с
; клавиатуры
int 21h ; получить следующую клавишу
cmp al,13 ; это клавиша ENTER?
jz EchoDone ; да, выполняем эхоотображение
mov dl,al ; поместить символ в DL
mov ah,2 ; функция DOS вывода на экран
int 21h ; вывести на экран символ
jnz EchoLoop ; отобразить следующий символ
EchoDone:
mov ah,4ch ; функция DOS завершения
; программы
int 21h ; завершить программу
END
Введите программу точно в таком виде и запустите ее. Вы уви-
дите, что каждый вводимый вами символ выведется дважды: один раз,
когда он отображается DOS при вводе с клавиатуры, и второй раз,
когда он отображается вашей программой. Важным моментом здесь яв-
ляется то, что все действия: считывания символов клавиш, вывод
символов на экран и завершение программы выполняются с помощью
функций DOS.
Базовая система ввода-вывода
-----------------------------------------------------------------
Иногда функции DOS не отвечают вашим потребностям. Тогда
настал момент обратиться к базовой системе ввода-вывода IBM PC -
BIOS. В отличие от DOS и прикладных программ BIOS не загружается
с диска и не занимает место в 640К доступной памяти. Вместо этого
BIOS хранится в памяти, доступной только по чтению (ROM или ПЗУ),
в той части адресного пространства процессора 8086, которое заре-
зервировано для системных функций.
BIOS является программным обеспечением IBM PC самого нижнего
уровня. Даже DOS использует для управления аппаратурой функции
BIOS. Лучше использовать функции BIOS, чем управлять аппаратными
средствами непосредственно, поскольку, аналогично DOS, BIOS поз-
воляет "скрыть" различия между различными компьютерами и устрой-
ствами. С другой стороны, там, где это возможно, вам следует
пользоваться функциями DOS, а не функциями BIOS, поскольку прог-
раммы, использующие BIOS, могут приводить к конфликту с другими
программами и в общем случае менее переносимы при работе на раз-
ных ЭВМ.
Выбор режима экрана
-----------------------------------------------------------------
Наиболее решающей причиной использования BIOS является уп-
равление дисплеем, так как DOS практически не предусматривает
поддержки широких возможностей дисплеев IBM PC. Только с помощью
вызова функций BIOS вы можете установить режим экрана, управлять
цветами, получить информацию о дисплейном адаптере и т.д. Напри-
мер, следующий код вызывает BIOS и устанавливает экран графичес-
кого адаптера CGA в четырехцветный графический режим:
.
.
.
mov ah,0 ; функция BIOS установки
; режима
mov al,4 ; номер режима для 4-цветной
; графики с разрешением 320х200
int 10h ; выполнить видеопрерывание
; BIOS для установки режима
.
.
.
Как вы наверное помните, мы уже говорили о том, что для
установки видеорежима необходимо более 30 инструкций OUT. Теперь
можно понять, что функция BIOS "Установить режим" избавляет вас
от большого объема работы.
В BIOS предусмотрен также ряд функций, не связанных с управ-
лением дисплеем, включающих в себя обработку нажатий клавиш и уп-
равление диском. Однако в общем случае эти задачи лучше выполнять
с помощью функций DOS.
Основным справочником по функциям BIOS является "Справочное
руководство по интерфейсу с базовой системой ввода-вывода".
Иногда необходимо обратиться к аппаратным средствам
-----------------------------------------------------------------
Теперь, когда вы знакомы со всеми доводами в пользу необхо-
димости использования функций DOS (или, если это абсолютно необ-
ходимо, функций BIOS), пришло время рассказать о тех случаях,
когда можно посоветовать вам обратиться непосредственно к аппара-
туре. Например, программное обеспечение коммуникаций управляет
последовательным портом IBM PC непосредственно с помощью инструк-
ций IN и OUT, поскольку ни DOS, ни BIOS не предусматривают адек-
ватной поддержки для последовательных коммуникаций. Аналогично,
высокопроизводительная графика должна выполняться с помощью не-
посредственного обращения к памяти дисплея, так как DOS не под-
держивает графику, а BIOS делает это довольно медленно.
Основным правилом при работе с аппаратными средствами явля-
ется уверенность в том, что у вас нет другой альтернативы. Если
имеются функции DOS или BIOS, которые вы можете использовать, то
используйте их. Если же нет, обращайтесь к аппаратуре непосредст-
венно. Кроме того, целью программирования является создание по-
лезных программ, а не следование правилам. Другой стороны, чем
меньше правил вы нарушаете, чем с меньшим числом проблем вы сто-
лкнетесь.
Другие ресурсы
-----------------------------------------------------------------
Для программиста, работающего на Ассемблере, в IBM PC пре-
дусмотрены также другие программные и аппаратные ресурсы. Мы не
будем все их рассматривать, приведем только кратко некоторые из
них (подробности вы можете найти в справочных материалах):
- драйвер ANSI.SYS, предусматривающий улучшенное управление
дисплеем без необходимости обращения к функциям BIOS;
- системные таймеры поддерживают таймер астрономического
времени, а также генерацию звука с помощью встроенного динамика
РС и точный отсчет времени;
- дополнительный арифметический сопроцессор 8087 значительно
ускоряет выполнение операций с плавающей точкой.
Глава 5. Основные элементы программы на языке Ассемблера
-----------------------------------------------------------------
Теперь, когда вы поняли, почему язык Ассемблера является
уникальным, можно заняться деталями программирования на Ассембле-
ре.
В этой главе вы узнаете об основных компонентах программы на
Ассемблере. Сначала мы расскажем о минимальных требованиях к ра-
ботающей программе на Ассемблере. Затем мы обсудим различные эле-
менты строки, и как их можно сочетать друг с другом. Попутно мы
расскажем много полезного об инструкциях, директивах и тех спо-
собах, с помощью которых программа на Ассемблере может получить
доступ к памяти. Вы увидите, как в Турбо Ассемблере определяются
и используются сегменты, узнаете о выделении и использовании пе-
ременных в памяти. Наконец, мы рассмотрим некоторые наиболее об-
щеупотребительные инструкции.
Объем охваченных здесь тем велик, но когда вы покончите с
этой главой, вы узнаете достаточно, чтобы начать писать программы
на Ассемблере. Реализовать это знание можно с помощью программы
подсчета слов, приведенной в конце главы.
Так как в данной главе только затрагиваются многие аспекты
программирования на Ассемблере, то в Главу 6 "Более подробно о
программировании на Турбо Ассемблере" и Главу 9 "Развитое прог-
раммирование на Турбо Ассемблере" где эти темы получают дальней-
шее развитие.
Элементы и структура программы на языке Ассемблера
-----------------------------------------------------------------
Теперь, когда вы лучше понимаете, что представляет собой
программа на языке Ассемблера процессора 8086, вы готовы к тому,
чтобы начать писать на Ассемблере программы. Давайте рассмотрим,
каковы минимальные требования к работающей программе на Ассембле-
ре. Даже простая программа на Ассемблере включает в себя некото-
рое количество строк. Рассмотрим, например, следующую программу:
DOSSEG ; директива упорядочивания
; сегментов
.MODEL SMALL ; модели кода и данных
; ближнего типа
.STACK 200h ; стек объемом 512 байт
.DATA ; начало сегмента данных
DisplayString DB 13,10 ; пара символов "возврат
; каретки/перевод строки"
; для начала новой строки
ThreeChars DB 3 DUP (?) ; память для трех символов,
; введенных с клавиатуры
DB '$' ; хвостовой символ "$",
; указывающий DOS, где
; завершить вывод строки
; DisplayString при
; выполнении функции 9
.CODE ; начало сегмента кода
Begin:
mov ax,@Data
mov ds,ax ; DS указывает на сегмент
; данных
mov bx,OFFSET ThreeChars ; указывает на
; ячейку памяти, где
; содержится первый символ
mov ah,1 ; функция DOS ввода с
; клавиатуры
int 21h ; получить следующую
; нажатую клавишу
dec al ; вычесть из символа 1
mov [bx],al ; сохранить измененный
; символ
inc bx ; указать на ячейку памяти,
; где содержится следующий
; символ
inc 21h ; получить следующую
; нажатую клавишу
dec al ; вычесть из символа 1
mov [bx],al ; сохранить измененный
; символ
inc bx ; ссылка на ячейку памяти
; со следующим символом
int 21h ; получить следующую
; нажатую клавишу
dec al ; вычесть из символа 1
mov [bx],al ; сохранить измененный
; символ
mov dx,OFFSET DisplayString ; ссылка на
; строку измененных
; символов
mov ah,9 ; функция DOS вывода строки
int 21h ; вывести измененные символы
mov ah,4ch ; функция DOS завершения
int 21h ; программы
END Begin ; директива, отмечающая
; конец исходного кода и
; указывающая, где начинать
; выполнение при запуске
; программы
Данная программа содержит упрощенные директивы определения
сегментов DOSSEG, .MODEL, .STACK, .DATA и .CODE, а также директи-
ву END. В каждой программе на Ассемблере, чтобы обеспечить опре-
деление сегментов и управление ими, необходимы директивы опреде-
ления сегментов (упрощенные или стандартные), а завершать
программу на Ассемблере всегда должна директива END. Директивы
определения сегментов и директиву END, а также некоторые другие
директивы мы опишем в данной главе.
Директивы представляют собой только "рамки" программы на Ас-
семблере. В самой программе необходимы также строки исходного
кода, выполняющие какие-либо действия, например:
mov [bx],al
и
inc dx
Эти строки представляют собой мнемоники инструкций, соот-
ветствующие набору инструкций процессора 8086, список которых
приведен в предыдущей главе. Однако, перед тем, как вы сможете
использовать инструкции или директивы, вы должны изучить формат
строки кода Ассемблера, так что давайте рассмотрим его.
Если вы захотите узнать, что же делает первая программа
(приведенный в выше пример), введите ее, запустите, наберите IBM,
после чего программа ответит вам:
HAL
Программа считывает три символа, вычитает из каждого символа
значение 1 и выводит на экран результат.
Зарезервированные слова
-----------------------------------------------------------------
Зарезервированные или ключевые слова предназначены только
для использования Турбо Ассемблером. Их нельзя использовать для
определения присваиваний, меток или имен процедур. Ключевые слова
следует рассматривать как элементы построения программы на Ас-
семблере. Эти слова приведены в Таблице 5.1 (включая операции +,
*, -, директивы .386, ASSUME, MASM, QUIRKS) и предопределенные
идентификаторы ??time, ??version, @WordSize).
Зарезервированные слова Турбо Ассемблера Таблица 5.1
-----------------------------------------------------------------
: @datasize @filename NAME RADIX
; ??date ??filename NE .RADIX
= DB FWORD NEAR RECORD
? %DEPTH GE %NEWPAGE REPT
[] DF GLOBAL %NOCONDS .SALL
/ DISPLAY GROUP %NOCREF SEG
() DOSSEG GT %NOCTLS SEGMENT
+ DP HIGH NOEMUL .SEQ
- DQ IDEAL %NOINCL .SFCOND
* DT IF NOJUMPS SHL
. DUP IF1 %NOLIST SHORT
.186 DW IF2 NOLOCALS SHR
.286 DWORD IFB %NOMACS SIZE
.286C ELSE IFDEF NOMASM51 SIZESTR
.286P ELSEIF IFDIF NOMULTERRS SMALL
.287 EMUL IFDIF1 NOSMART SMART
.386 END IFE %NOSYMS STACK
.386C ENDIF IFIDN NOT .STACK
.387 ENDM IFIDNI NOTHING STRUC
.8086 ENDP IFNB %NOTRUNC SUBSTR
.8087 ENDS IFNDEF NOWARN SUBTTL
ALIGN EQ %INCL OFFSET %SUBTTL
.ALPHA EQU INCLUDE OR %SYMS
AND ERR INCLUDELIB ORG SYMTYPE
ARG .ERR INSTR %OUT %TABSIZE
ASSUME .ERR1 IRP P186 TBYTE
%BIN .ERR2 IRPC P286 %TEXT
BYTE .ERRB JUMPS P286N .TFCOND
CATSTR .ERRDEF LABEL P287 THIS
@CODE ERRDIF .LALL P386 ??time
CODESEG ERRDIFI LARGE P386N TITLE
@CODESIZE ERRE LENGTH P386P %TITLE
COMM ERRIDN .LFCOND P387 %TRUNC
COMMENT ERRIDNI %LINUM P8086 TYPE
%CONDS ERRIFNB %LIST P8087 .TYPE
.CONST ERRIFNDEF .LIST PAGE UDATASEG
@Cpu ERRNB LOCAL %PAGESIZE UFARDATA
%CREF ERRNDEF LOCALS PARA UNION
.CREF ERRNZ LOW %PNCT UNKNOWN
%CREFALL EVEN LT PNO87 USES
%CREFREF EVENDATA MACRO %POPLCTL ??version
%CREFUREF EXITM %MACS PROC WARN
%CTLS EXTRN MASK PTR WITH
@curseg FAR MASM PUBLIC WORD
@data FARDATA MASM51 PURGE @WordSize
.DATA @fardata MOD %PUSHLCTL .XALL
.DATA? .FARDATA MODEL PWORD .XLIST
DATAPTR @fardata? .MODEL QUIRKS XOR
DATASEG .FARDATA? MULTERRS QWORD
-----------------------------------------------------------------
Формат строки
-----------------------------------------------------------------
Строки исходного кода на языке Ассемблера имеют следующий
формат:
<метка> <инструкция/директива> <операнды> <;комментарий>
где <метка> представляет собой необязательное имя идентификатора,
<инструкция/директива> - это мнемоника инструкции или директивы,
<операнды> - содержат сочетание 0, 1 или 2 (иногда более) конс-
тант, ссылок на память или регистры, или текстовых строк, как это
требуется в каждой конкретной инструкции или директиве, <;коммен-
тарий> - необязательный комментарий.
В любом месте в строки качестве символа продолжения строки
можно поместить символ обратной косой черты (\). Однако данный
символ нельзя использовать для разбиения идентификаторов. Обрат-
ная косая черта означает: "считать следующую строку, и продолжить
обработку с данной точки". При этом вы можете комментировать каж-
дую строку. Например:
foo mystructure \ ; Начать заполнение структуры
<0, \ ; Сначала - нулевое значение
1, \ ; Затем - единица
2> \ ; Cтруктура завершается значением 2
В двух контекстах символы продолжения строки не распознают-
ся. В общем случае они не распознаются в любом контексте, где
символы обрабатываются как текст, а не как идентификаторы, числа
или строки, либо в режиме MASM, когда продолжение строки исполь-
зуется в первых двух идентификаторах оператора. Например:
ifdif <123\>,<456\>
Здесь два вложенных символа "\" не распознаются, как продол-
жение строки.
comment \
:
начинает блок комментария, а не определяет идентификатор ближнего
типа с именем COMMENT.
Символ продолжения строки не распознается также внутри мак-
роопределений. Однако он распознается при расширении макрокоман-
ды.
Давайте рассмотрим каждый из этих элементов более подробно.
Метки
-----------------------------------------------------------------
Метки - это ни что иное, как имена, использующиеся в прог-
рамме для ссылки на числа и строки символов или ячейки памяти.
Метки позволяют вам присваивать имена переменным в памяти, значе-
ниям и адресам, где находятся конкретные инструкции. Например, в
следующей программе, которая вычисляет факториал 5, используется
несколько меток:
DOSSEG
.MODEL SMALL
.STACK 200h
.DATA
FactorialValue DW ?
Factorial DW ?
.CODE
FiveFactorial PROC
mov ax,@Data
mov ds,ax
mov [FactorialValue],1
mov [Factorial],2
mov cx,4
FiveFactorialLoop:
mov ax,[FactorialValue]
mul [Factorial]
mov [FactorialValue],ax
inc [Factorial]
loop FiveFactorialLoop
ret
FiveFactorial ENDP
END
Метки FactorialValue и Factorial эквивалентны адресам двух
16-битовых переменных. Они используются для последующей ссылки в
программе на эти две переменные. Метка FiveFactorial - это имя
подпрограммы (процедуры или функции), содержащей код. Она позво-
ляет вызывать этот код в других частях программы. Наконец, метка
FiveFactorialLoop эквивалентна адресу инструкции:
mov ax,[FactorialValue]
благодаря которой оператор LOOP в конце программы может осуще-
ствлять обратный переход на эту инструкцию.
Метки могут состоять из следующих символов:
A - Z a - z _ @ $ ? 0 - 9
В режиме MASM допускается также точка (.) (см. Главу 11), но
только в качестве первого символа. Цифры 0 - 9 не могут использо-
ваться в качестве первых символов метки. Символы $ и ? имеют
специальное значение, поэтому их не следует использовать в именах
ваших идентификаторах.
Каждая метка должна определяться только один раз, то есть
метки должны быть уникальными. (Из этого правила есть исключения.
Например, специальные метки, определенные с помощью директивы =,
локальные метки в макрокомандах и подпрограммы режима Ideal). Как
операнды метки могут использоваться любое число раз.
Метка может занимать всю строку, то есть на этой строке кро-
ме метки может отсутствовать инструкция или директива. В этом
случае значением метки является адрес инструкции или директивы на
следующей строке программы. Например, во фрагменте программы:
.
.
jmp DoAddition
.
.
.
DoAddition:
add ax,dx
.
.
.
следующей инструкцией, выполняемой после инструкции JMP, которая
выполняет переход на метку DoAddition, является инструкция ADD
AX,DX. Предыдущий пример эквивалентен следующему:
.
.
jmp DoAddition
.
.
.
DoAddition: add ax,dx
.
.
.
(Список директив приведен в Главе 3 "Справочного руководс-
тва", а о регистрах процессора 8086 рассказывается в Главе 4.)
В размещении каждой метки на своей собственной строке имеют-
ся два преимущества. Во-первых, когда вы размещаете каждую метку
на отдельной строке, легче использовать длинные метки без наруше-
ния структуры (формата) программы на Ассемблере, что сделало бы
ее менее читабельной. Во-вторых, справа от метки легче добавить
новую инструкцию, если метка не находится на той же строке, что и
инструкция. Чтобы преобразовать последний пример к виду:
.
.
jmp DoAddition
.
.
.
DoAddition: mov dx,[MemVar]
add ax,dx
.
.
.
вам пришлось бы разделить строку с меткой DoAddition и добавить
новый текст. Если же DoAddition находится на отдельной строке, вы
можете просто добавить после этой метки новую строку.
Метка не может совпадать с любым из используемых в выраже-
ниях встроенных символов, включая имена регистров (AX, BX и т.д.)
и операторы, используемые в выражениях (PTR, BYTE, WORD и т.д.).
В качестве меток нельзя также использовать любую из директив
IFxxx или .ERRxxx. Несколько других зарезервированных в Турбо Ас-
семблере идентификаторов могут использоваться только в определен-
ных контекстах. Эти идентификаторы включают в себя NAME, INCLUDE
и COMMENT. Данные имена могут использоваться, как имена элементов
структур, но не в качестве общих идентификаторов (более подробно
о структурах рассказывается в Главе 9).
Лучший подход - это избежать необходимости использования в
качестве меток любого из встроенных имен идентификаторов. Напри-
мер, метки:
bx DW 0
PTR:
были бы неприемлемы, так как BX - это регистр, а PTR - операция в
выражении. Однако метка:
Old_BX DW 0
вполне допустима.
Приведем еще примеры допустимых меток:
MainLoop
calc_long_sum
Error0
Iterate
Draw$Dot
Delay_100_milliseconds
Метки в коде программы (и содержащиеся на отдельной строке,
и после которых на той же строке следуют директивы или инструк-
ции) должны заканчиваться двоеточием (:). Двоеточие просто завер-
шает метку и не является ее частью. Например, в следующем фраг-
менте программы:
.
.
.
LoopTop:
mov al,[si]
inc si
and al,al
jz Done
jmp LoopTop
Done: ret
.
.
.
метки LoopTop и Done определены с двоеточиями, но в ссылках на
эти метки двоеточия не указываются.
Другие метки в общем случае не должны завершаться двоеточи-
ем. В примере программы в начале данного раздела содержатся нес-
колько меток без двоеточий.
Имена меткам следует назначать так, чтобы они имели смысл.
Сравните:
.
.
cmp al,'a'
jb NotALowerCaseLetter ; не строчная буква
cmp al,'z'
ja NotALowerCaseLetter
sub al,20h ; преобразовать в верхний
; регистр (прописную
; букву)
NotALowerCaseLetter:
.
.
и следующий фрагмент программы:
.
.
cmp al,'a'
jb P1
cmp al,'z'
ja P1
sub al,20h ; преобразовать в верхний
; регистр
P1:
.
.
Вариант с описательной меткой (особенно для владеющих ан-
глийским языком, для русскоязычных программистов, возможно, лучше
использовать варианты типа Ne_strochnaja_bukva) гораздо более по-
нятен, в то время как второй с первого взгляда, мягко говоря, за-
гадочен. Метки могут содержать символы подчеркивания. В зависи-
мости от вашего предпочтения можно использовать метки типа not_a_
lower_case_ letter или Not _A_Lower_Case_Letter.
Мнемоники инструкций и директивы
-----------------------------------------------------------------
Основным полем в строке программы на Ассемблере является
поле <инструкция/директива>. Это поле может содержать мнемонику
инструкции или директиву (две совершенно различные вещи).
Ранее в данной главе вы уже встречали мнемоники инструкций.
Они представляют собой удобные для чтений имена инструкций, не-
посредственно выполняемых процессором 8086. MOV, ADD, MUL и JMP -
все это мнемоники инструкций, соответствующие инструкциям процес-
сора 8086 перемещения данных, сложения, умножения и перехода со-
ответственно.
Турбо Ассемблер ассемблирует каждую мнемонику инструкции не-
посредственно в соответствующую инструкцию на машинном языке.
Каждый раз, когда вы включаете в программу на языке Ассемблера
одну мнемонику инструкции, результатом является одна инструкция в
выполняемом коде.
В отличие от мнемоник инструкций, директивы совсем не гене-
рируют выполняемого кода. Вместо этого они управляют различными
аспектами работы Турбо Ассемблера - от типа ассемблируемого кода
(процессоры 8086, 80286, 80386 и т.д.) до используемых сегментов
и формата создаваемых файлов листингов. Хотя это не единственное
отличие, вы можете рассматривать мнемоники инструкций, как гене-
рирующие реальную программу на машинном языке процессора 8086, а
директивы - как ответственные за обеспечение средств высокого
уровня Турбо Ассемблера, которые облегчают программирование на
Ассемблере.
В данном руководстве мы уделяем большое внимание различным
аспектам мнемоник и директив Турбо Ассемблера, которые обсуждают-
ся также в Главе 3 "Справочного руководства". Имеется несколько
директив, которые необходимы в любой программе на Ассемблере (в
особенности это касается директив определения сегментов, которые
мы обсудим несколько позднее). Другая директива, которая всегда
необходима в программе - это директива END.
Директива END
-----------------------------------------------------------------
Каждая программа должна содержать директиву END, отмечающую
конец исходного кода программы. Все строки, которые следуют за
директивой END, Турбо Ассемблером игнорируются. Если вы опустите
директиву END, то генерируется ошибка. Вы можете посчитать, что
концом программы является конец файла, но это не так: всегда тре-
буется указывать директиву END.
END является типичной директивой в том смысле, что она не
порождает никакого кода. Например:
DOSSEG
.MODEL SMALL
.STACK 200h
.DATA
ProgramStart:
mov ah,4ch
int 21h
END ProgramStart
Это, возможно, простейшая программа на Ассемблере. Она ниче-
го не делает, просто немедленно возвращает управление DOS. Обра-
тите внимание на использование директивы END для завершения кода,
из которого состоит данная программа.
Без сомнения вы заметили, что на одной строке с директивой
END содержится метка ProgramStart. Кроме завершения программы,
директива END может выполнять еще и вторую функцию, указывая, где
должно начинаться выполнение при запуске программы. По той или
иной причине вы можете не захотеть начать выполнение программы в
файле .EXE с первой инструкции. Директива END предусматривает та-
кие случаи. Предположим, например, вы запускаете программу, полу-
ченную в результате ассемблирования и компоновки следующего кода:
DOSSEG
.MODEL SMALL
.STACK 200h
.DATA
Delay:
mov cx,0
DelayLoop:
loop DelayLoop
ret
ProgramStart:
call Delay ; пауза на время,
; необходимое для
; выполнения 64 циклов
mov ah,4ch
int 21h
END ProgramStart
Выполнение здесь начинается не на первой инструкции исходно-
го кода (MOV CX,0) по метке Delay. Вместо этого выполнение начи-
нается с инструкции CALL Delay по метке ProgramStart, как опреде-
лено в директиве END.
Если программа состоит только из одного модуля (то есть од-
ного исходного файла), то в директиве END всегда нужно определять
адрес запуска программы. В программе, состоящей из нескольких мо-
дулей, определять адрес запуска программы следует только в дирек-
тиве END модуля, содержащего инструкцию, с которой должно начать-
ся выполнение программы. В директивах END других модулей должно
указываться только ключевое слово END и нечего более. В самом
деле: каждая программа должна иметь точку начала выполнения, но
было бы бессмысленно иметь несколько таких точек. Убедитесь в
том, что вашей программе имеется один (и только один) адрес нача-
ла выполнения. (Кстати, если в вашей программе имеется два таких
адреса, компоновщик TLINK использует только первый адрес, который
он обнаруживает, и игнорирует другой.)
Операнды
-----------------------------------------------------------------
Мнемоники инструкций и директивы сообщают Турбо Ассемблеру,
что нужно делать. С другой стороны, операнды указывают Турбо Ас-
семблеру, какие регистры, параметры, ячейки памяти и т.д. нужно
связать с каждым вхождением инструкции или директивы. Инструкция
MOV (перемещение данных) сама по себе ничего не означает. Чтобы
указать Турбо Ассемблеру, откуда нужно извлечь перемещаемое зна-
чение и где его сохранить, необходимы операнды.
Для различных инструкций требуются 0, 1, 2 или более операн-
дов. В действительности различными директивами может восприни-
маться любое число операндов, которое может уместиться на одной
строке. Правильное число операндов зависит от конкретной инструк-
ции или директивы. (В общем случае допускается три операнда.)
Возможные операнды включают в себя регистры, константы, метки,
переменные в памяти и текстовые строки.
Какие функции выполняет инструкция с одним операндом, доста-
точно очевидно: она выполняет операции с этим операндом. Напри-
мер:
push ax
заносит регистр AX в стек. Инструкции без операндов еще более
очевидны. Однако, что происходит в случае инструкции с двумя опе-
рандами, один из которых является источником, а остальные прием-
ником? Например, когда процессор 8086 выполняет инструкцию:
mov ax,bx
то из какого регистра он считывает значение, и в какой регистр
записывает?
Вы можете посчитать, что словесным эквивалентом данной
инструкции будет "переместить содержимое AX в BX", но это не так.
Вместо этого инструкция MOV помещает содержимое BX в AX. Можно
использовать следующее правило: замените в инструкции MOV запятую
между операндами знаком равенства, после чего она приобретет вид
оператора присваивания, аналогичный языку Си (или Паскаль). При
таком подходе инструкция MOV преобразуется в вид:
ax = bx;
Это позволит вам легче запомнить работу данной инструкции.
На самом деле в использовании правого операнда в качестве
источника есть некоторая доля путаницы, но по крайней мере в Ас-
семблере процессора 8086 это так, и скоро вы это используете.
Регистровые операнды
-----------------------------------------------------------------
Вероятно регистровые операнды являются наиболее часто ис-
пользуемыми в инструкциях операндами. Регистры могут использо-
ваться в качестве источника (исходный операнд) или приемника (це-
левой операнд) и при некоторых обстоятельствах могут даже содер-
жать адрес, на который нужно выполнить переход. С регистрами мож-
но делать много того, чего нельзя делать с константами, метками
или переменными в памяти. С другой стороны, имеются некоторые
инструкции, в которых можно использовать только регистровые опе-
ранды.
Приведем некоторые примеры регистровых операндов:
mov al,ax
push dl
xchg al,dl
ror dx,cl
in al,dx
inc sl
Регистровые операнды могут использоваться вместе с другими
операндами:
mov al,1
add [BaseCount],cx
cmp si,[bx]
Использование регистровых операндов не требует обширных по-
яснений. Чтобы использовать регистр в качестве операнда, вы зада-
ете имя этого регистра и использующую регистр инструкцию. Если
имеется два операнда и регистровым операндом является самый пра-
вый операнд, то он будет исходным регистром (источником), а если
самым левым операндом - то это целевой регистр (приемник). Если в
инструкции требуется два источника, то может присутствовать еще
один исходный регистр. Например, во фрагменте программы:
.
.
.
mov cx,1
mov dx,2
sub dx,cx
.
.
.
регистр CX устанавливается в значение 1, DX - в значение 2, а за-
тем из регистра DX вычитается CX и результат (1) снова записыва-
ется в регистр DX. В инструкции SUB CX является правым операндом,
поэтому это исходный регистр (источник). DX - самый левый опе-
ранд, поэтому он одновременно является вторым источником и прием-
ником. Кстати, действие данной инструкции SUB (вычитание) выража-
ется словами, как "вычесть CX из DX". Использование метода
преобразования в код языка Си для облегчения запоминания инструк-
ций позволяет преобразовать данную инструкцию SUB к виду:
dx -= cx;
а на Паскале это будет выглядеть, как:
dx := dx - cx;
Операнды-константы
-----------------------------------------------------------------
Регистры прекрасно подходят для хранения значений перемен-
ных, но часто в операндах требуется использовать постоянное зна-
чение. Например, предположим, вы хотите в цикле уменьшать значе-
ние регистра SI на 4, повторяя цикл, пока значение SI не станет
равным 0. Можно использовать следующие операции:
.
.
.
CountByFourLoop:
.
.
.
dec si
dec si
dec si
dec si
jnz CountByFourLoop
.
.
.
Однако намного проще использовать инструкции:
.
.
.
CountByFourLoop:
.
.
.
sub si,1
jnz CountByFourLoop
.
.
.
В качестве постоянных операндов (операндов-констант) можно
использовать также символы, поскольку символ представляет собой
определенное значение. Например, так как символ A имеет десятич-
ное значение 65, то следующие две инструкции эквивалентны:
.
.
.
sub al,'A'
sub al,65
.
.
.
Постоянные значения можно задавать в двоичном, восьмеричном
или шестнадцатиричном представлении, а также в десятичном виде.
Указанные формы представления чисел мы обсудим позднее в разделе
"Биты, байты и основания".
Операнды-константы никогда не могут при использовании двух
операндов располагаться слева, так как невозможно использовать
константу в качестве операнда-приемника (это противоречит опреде-
лению константы, как неизменяемой величины). Операнды-константы,
однако, могут прекрасно использоваться в том месте, где имеет
смысл использование значения в качестве исходного операнда. Про-
цессор 8086 накладывает на использование констант некоторые огра-
ничения. Например, вы не можете занести значение-константу в стек
(это ограничение касается только процессоров 8086/8088). Чтобы
занести в стек значение 5, вы должны выполнить две инструкции:
.
.
.
mov ax,5
push ax
.
.
.
Нужно изучить каждый отдельный случай, когда использование
констант не допускается. К счастью, таких инструкций немного, и
Турбо Ассемблер, конечно, даст вам знать, если вы пытаетесь ис-
пользовать константу некорректно.
Выражения
-----------------------------------------------------------------
Постоянные выражения можно использовать там же, где допуска-
ется использование постоянных значений (констант). Турбо Ассем-
блер поддерживает полное вычисление выражений, включая вложенные
скобки, арифметические, логические операции и операции отношения,
а также множество операций, предназначенных для таких целей, как
выделение для меток сегмента и смещения и определение размера пе-
ременных в памяти.
Например, во фрагменте программы:
.
.
.
MemVar DB 0
NextVar DB ?
.
.
.
mov ax,SEG MemVar
mov ds,ax
mov bx,OFFSET MemVar + (3*2) - 5)
mov BYTE PTR [bx],1
.
.
.
операция SEG используется для загрузки постоянного значения сег-
мента, в котором находится MemVar, и копирования этого значения
из регистра AX в DS. Далее в этой программе используется сложное
выражение, включающее в себя операции *, +, - и OFFSET, при вы-
числении которого получается значение OFFSET MemVar+1, которое
представляет собой ни что иное, как адрес NextVar. Наконец, для
выбора байтовой операции при сохранении константы 1 в ячейке, на
которую указывает регистр BX (что представляет собой NextVar),
используется операция BYTE PTR.
Относительно выражений стоит сделать важное замечание: при
вычислении всех выражений должно получаться значение-константа.
OFFSET MemVar - это значение-константа, представляющее собой сме-
щение переменной MemVar в ее сегменте. Кроме того, в то время как
сохраненное в переменной MemVar значение может изменяться, сама
переменная MemVar, конечно, никуда не перемещается.
Так как значения-константы точно известны, Турбо Ассемблер
может вычислять состоящие из постоянных значений выражения так
же, как он ассемблирует ваш исходный код. Для Турбо Ассемблера
выражение OFFSET MemVar + 2 совершенно аналогично выражению 5 +
2. Поскольку все элементы выражения неизменяемы и определены во
время ассемблирования, выражение можно свести к одному значе-
нию-константе.
В выражениях могут использоваться следующие операции:
<>, (), LENGTH, MASK, SIZE, WIDTH
. (селектор элемента структуры)
HIGH, LOW
: (переопределение сегмента)
OFFSET, PTR, SEG, THIS, TYPE
*, /, MOD, SHL, SHR
+, - (бинарные)
EQ, GE, GT, LE, LT, NE
NOT
AND
OR, XOR
LARGE, SHORT, SMALL, .TYPE
Названия и обозначения некоторых операций говорят сами за
себя. Эти операции выполняют в арифметических выражениях именно
те действия, для которых они предназначены. Операции мы поясним в
этой главе позднее, когда до них дойдет дело. Кроме того, ответы
на вопросы, касающиеся отдельных операций, можно найти в Главе 2
"Справочного руководства".
Операнды-метки
-----------------------------------------------------------------
Во многих инструкциях в качестве операндов можно использо-
вать метки. При указании их в соответствующих операциях метки мо-
гут использоваться для получения постоянных значений (констант).
Например:
.
.
.
MemWord DW 1
.
.
.
mov al,SIZE MemWord
.
.
.
Здесь значение 2 (размер в байтах переменной в памяти
MemWord) помещается в AL. В данном контексте метка может стано-
виться частью выражения, как уже показано в предыдущем разделе.
Метки могут также использоваться в качестве целевых операн-
дов в операциях CALL и JMP. Например, во фрагменте программы:
.
.
.
cmp ax,100
ja IsAbove100
.
.
.
IsAbove100:
.
.
.
инструкция JA используется для перехода по адресу, заданному опе-
рандом IsAbove100, если значение AX превышает 100. Здесь метка
используется в качестве константы, задавая адрес перехода.
Наконец, метки можно использовать в качестве операндов почти
также, как используются регистры, то есть как операнд-источник
или операнд-приемник в инструкциях работы с данными. Программа:
.
.
.
TampVar DW ?
.
.
.
mov [TempVar],ax
sub ax,[TempVar]
.
.
.
обнуляет содержимое регистра AX, так как первая инструкция запи-
сывает содержащееся в регистре AX значение в переменную памяти
TempVar, затем вторая инструкция вычитает из AX сохраненное в
TempVar значение.
Использование меток в качестве операндов представляет собой
отдельную тему при описании моделей памяти. Этого мы коснемся
позднее.
Режимы адресации к памяти
-----------------------------------------------------------------
Как при использовании операнда в памяти задать ту ячейку па-
мяти, с которой вы хотите работать? Очевидный ответ состоит в
том, чтобы присвоить нужной переменной в памяти имя (как мы это
делали в последнем разделе). С помощью, например, следующих опе-
раторов вы можете вычесть переменную памяти Debts (долги) из пе-
ременной памяти Assets (имущество):
.
.
.
Assets DW ?
Debts DW ?
.
.
.
mov ax,[Debts]
sub [Assets],ax
.
.
.
Однако адресация к памяти имеет и более глубокий смысл, ко-
торый не бросается в глаза. Предположим, у вас имеется символьная
строка с именем CharString, содержащая буквы ABCDEFGHIGKLM, кото-
рые начинаются в сегменте данных со смещения 100, как показано на
Рис. 4.1. Каким образом можно считать девятый символ (I), который
расположен по адресу 108? В языке Си вы можете просто использо-
вать оператор:
C = CharString[8];
(в Си элементы нумеруются с 0), а в Паскале:
C := CharString[9];
Как же это можно сделать в Ассемблере? Прямая ссылка на
строку CharString здесь, конечно, не подходит, так как первым
символом является символ A.
. .
. .
| |
|--------------|
TASM2 #1-5/Док = 137 =
99 | ? |
|--------------|
CharString --------------> 100 | 'A' |
|--------------|
101 | 'B' |
|--------------|
102 | 'C' |
|--------------|
103 | 'D' |
|--------------|
104 | 'E' |
|--------------|
105 | 'F' |
|--------------|
106 | 'G' |
|--------------|
107 | 'H' |
|--------------|
108 | 'I' |
|--------------|
109 | 'J' |
|--------------|
110 | 'K' |
|--------------|
111 | 'L' |
|--------------|
112 | 'M' |
|--------------|
113 | 0 |
|--------------|
114 | ? |
|--------------|
. .
. .
Рис. 5.1 Ячейки памяти со строкой символов CharString.
В действительности язык Ассемблера обеспечивает несколько
различных способов адресации к строкам символов, массивам и буфе-
рам данных. Наиболее простой способ состоит в том, чтобы считать
девятый по счету символ строки CharString:
.
.
.
.DATA
CharString DB 'ABCDEFGHIJKLM'
.
.
.
.CODE
.
.
.
mov ax,@Data
mov ds,ax
mov al,[CharString+8]
.
.
.
В данном случае это тоже самое, что:
mov al,[100+8]
так как CharString начинается со смещения 100. Все, что заключено
в квадратные скобки, интерпретируется Турбо Ассемблером, как ад-
рес, поэтому смещение CharString и 8 складывается и используется
в качестве адреса памяти. Инструкция принимает вид:
mov al,[108]
как показано на Рис. 5.2.
. .
. .
| |
|--------------|
99 | ? |
|--------------|
CharString --------------> 100 | 'A' |
|--------------|
101 | 'B' |
|--------------|
102 | 'C' |
|--------------|
103 | 'D' |
|--------------|
104 | 'E' |
|--------------|
105 | 'F' |
|--------------|
106 | 'G' |
|--------------|
107 | 'H' |-------
|--------------| |
CharString+8 -----------> 108 | 'I' | |
|--------------| V
109 | 'J' | --------
|--------------| | |
110 | 'K' | --------
|--------------| AL
111 | 'L' |
|--------------|
112 | 'M' |
|--------------|
113 | 0 |
|--------------|
114 | ? |
|--------------|
. .
. .
Рис. 5.1 Адресация строки символов строки CharString.
Такой тип адресации, когда ячейка памяти задается ее именем,
плюс некоторая константа, называется непосредственной (прямой)
адресацией. Хотя непосредственная адресация - это хороший метод,
она не отличается достаточной гибкостью, поскольку обращение вы-
полняется каждый раз по одному и тому же адресу памяти. Поэтому
давайте рассмотрим другой, более гибкий путь адресации памяти.
Рассмотрим следующий фрагмент программы, где в регистр AL
также загружается девятый символ CharString:
.
.
.
mov bx,OFFSET CharString+8
mov al,[bx]
.
.
.
В данном примере для ссылки на девятый символ используется
регистр BX. Первая инструкция загружает в регистр BX смещение
CharString (вспомните о том, что операция OFFSET возвращает сме-
щение метки в памяти), плюс 8. (Вычисление OFFSET и сложение для
этого выражения выполняется Турбо Ассемблером во время ассембли-
рования.) Вторая инструкция определяет, что AL нужно сложить с
содержимым по смещению в памяти, на которое указывает регистр BX
(см. Рис. 5.3).
mov al,[108]
как показано на Рис. 5.2.
. .
. .
| |
|--------------|
99 | ? |
|--------------|
CharString --------------> 100 | 'A' |
|--------------|
101 | 'B' |
|--------------|
102 | 'C' |
|--------------|
103 | 'D' |
|--------------|
104 | 'E' |
|--------------|
105 | 'F' |
|--------------|
106 | 'G' |
|--------------|
107 | 'H' |-------
----------- |--------------| |
BX | 108 | ------------> 108 | 'I' | |
----------- |--------------| V
109 | 'J' | --------
|--------------| | |
110 | 'K' | --------
|--------------| AL
111 | 'L' |
|--------------|
112 | 'M' |
|--------------|
113 | 0 |
|--------------|
114 | ? |
|--------------|
. .
. .
Рис. 5.3 Использование регистра BX для адресации к строке
CharString.
Квадратные скобки показывают, что в качестве операнда-источ-
ника должна быть использоваться ячейка, на которую указывает ре-
гистр BX а не сам регистр BX. Не забывайте указывать квадратные
скобки при использовании BX в качестве указателя памяти. Напри-
мер:
mov ax,[bx] ; загрузить AX из ячейки памяти,
; на которую указывает BX
и
mov ax,bx ; загрузить в AX содержимое
; регистра BX
это две совершенно различные инструкции.
Может возникнуть вопрос, зачем сначала загружать в регистр
BX смещение ячейки памяти и затем использовать BX, как указатель,
если тоже самое можно сделать с помощью одной инструкции с непос-
редственным операндом? Особое свойство регистров, используемых в
качестве указателей, состоит в том, что в отличие от инструкций,
использующих непосредственные операнды, инструкции, использующие
в качестве указателей регистры, могут ссылаться в разное время (в
процессе выполнения программы) на разные ячейки памяти.
Предположим, вы хотите определить последний символ завершаю-
щейся нулем строки CharString. Чтобы это сделать, вы должны, на-
чиная с первого символа строки CharString, найти завершающий
строку нулевой байт, затем вернуться назад на один символ и счи-
тать этот последний символ. Это невозможно сделать с помощью не-
посредственной адресации, так как строка может иметь произвольную
длину. Использование регистра BX значительно облегчает задачу:
.
.
.
mov bx,OFFSET CharString ; указывает на строку
FindLastCharLoop:
mov al,[bx] ; получить следующий
; символ строки
cmp al,0 ; это нулевой байт?
je FoundEndOfString ; да, вернуться на
; один символ
inc bx
jmp FilnLastCharLoop ; проверить следующий
; символ
FoundEndOfString:
dec bx ; снова указывает на
; последний символ
mov al,[bx] ; получить последний
. ; символ строки
.
.
Если вы собираетесь выполнять в памяти поиск символов или
слов, работать с массивами, или копировать блоки данных, вы пой-
мете, что использование регистров-указателей дает неоценимую по-
мощь.
BX - это не единственный регистр, который можно использовать
для ссылка на память. Допускается также использовать вместе с не-
обязательным значением-константой или меткой регистры BP, SI и
DI. Общий вид операндов в памяти выглядит следующим образом:
[базовый регистр + индексный регистр + смещение]
где базовый регистр - это BX или BP, индексный регистр - SI или
DI, а смещение - любая 16-битовая константа, включая метки и вы-
ражения. Каждый раз, когда выполняется инструкция, использующая
операнд в памяти, процессором 8086 эти три компоненты складывают-
ся. Каждая из трех частей операнда в памяти является необязатель-
ной, хотя (это очевидно) вы должны использовать один из трех эле-
ментов, иначе вы не получите адреса в памяти. Вот как элементы
операнда в памяти выглядят в другом формате:
BX SI
или + или + Смещение
BP DI
(база) (индекс)
Существует 16 способов задания адреса в памяти:
[смещение] [bp+смещение]
[bx] [bx+смещение]
[si] [si+смещение]
[di] [di+смещение]
[bx+si] [bx+si+смещение]
[bx+di] [bx+di+смещение]
[bp+si] [bp+si+смещение]
[bp+di] [bp+di+смещение]
где смещение - это то, что можно свести к 16-битовому постоянному
значению.
Может показаться, что 16 режимов адресации - это очень мно-
го, но если вы еще раз посмотрите на этот список, вы увидите, что
все режимы адресации получаются всего из нескольких элементов,
комбинируемых различными путями. Вот еще несколько способов, с
помощью которых можно, используя различные режимы адресации, заг-
рузить девятый символ строки CharString в регистр AL:
.
.
.
.DATA
CharString DB 'ABCDEFGHIJKLM',0
.
.
.
.CODE
mov ax,@Data
mov ds,ax
.
.
.
mov si,OFFSET CharString+8
mov al,[si]
.
.
.
mov bx,8
mov al,[Charstring+bx]
.
.
.
mov ..,OFFSET CharString
mov al,[bx+8]
.
.
.
mov si,8
mov al,[CharString+si]
.
.
.
mov bx,OFFSET CharString
mov di,8
mov al,[bx+di]
.
.
.
mov si,OFFSET CharString
mov bx,8
mov al,[si+bx]
.
.
.
mov bx,OFFSET CharString
mov si,7
mov al,[bx+si+1]
.
.
.
mov bx,3
mov si,5
mov al,[bx+CharString+si]
.
.
.
Все эти инструкции ссылаются на одну и ту же ячейку памяти -
[CharString]+8.
В данном примере можно найти несколько интересных моментов.
Во-первых, вы должны понимать, что знак плюс (+), используемый
внутри квадратных скобок, имеет специальное значение. Во время
ассемблирования Турбо Ассемблер складывает все постоянные значе-
ния (константы) внутри квадратных скобок, поэтому инструкция:
mov [10+bx+si+100],cl
принимает вид:
mov [bx+si+111],cl
После этого при реальном выполнении инструкции (во время
прогона программы) операнды в памяти складываются вместе процес-
сором 8086. Если регистр BX содержит значение 25, а SI содержит
52, то при выполнении инструкции MOV CL записывается по адресу
памяти 25 + 52 + 111 = 188. Ключевой момент состоит в том, что
базовый регистр, индексный регистр и смещение складываются вместе
процессором 8086 при выполнении инструкции. Таким образом, Турбо
Ассемблер складывает константы во время ассемблирования, а про-
цессор 8086 складывает содержимое базового регистра, индексного
регистра и смещения во время действительного выполнения инструк-
ции.
Как вы можете заметить, ни в одном из примеров мы не исполь-
зовали регистр BP. Это связано с тем, что поведение регистра BP
несколько отличается от регистра BX. Вспомните, что в то время
как регистр BX используется, как смещение внутри сегмента данных,
регистр BP используется, как смещение в сегменте стека. Это озна-
чает, что регистр BP не может обычно использоваться для адресации
к строке CharString, которая находится в сегменте данных.
Пояснение использования регистра BP для адресации к сегменту
стека приводится в Главе 4. В данный момент достаточно знать, что
регистр BP можно использовать так же, как мы использовали в при-
мерах регистр BX, только адресуемые данные должны в этом случае
находиться в стеке.
(На самом деле регистр BP можно использовать и для адресации
к сегменту данных, а BX, SI и DI - для адресации к сегменту сте-
ка, дополнительному сегменту или сегменту кода. Для этого исполь-
зуются префиксы переопределения сегментов (segment override pre-
fixes). О некоторых из них мы расскажем в Главе 10. Однако в
большинстве случаев они вам не понадобятся, поэтому пока мы прос-
то забудем об их существовании.)
Наконец, квадратные скобки, в которые заключаются непос-
редственные адреса, являются необязательными. То есть инструкции:
mov al,[MemVar]
и
mov al,MemVar
выполняют одни и те же действия. Тем не менее мы настоятельно ре-
комендуем вам заключать все ссылки на память в квадратные скобки.
Это поможет избежать путаницы и сделает вашу программу более яс-
ной и понятной. Несомненно, вы столкнетесь с программами, в кото-
рых квадратные скобки отсутствуют, так как некоторые все же счи-
тают, что в таком виде программа воспринимается лучше. Это, в об-
щем, дело вкуса, но если вы выберете стиль адресации по одной
ячейке памяти и будете содержательно его использовать, вам будет
легче писать программы.
Вы можете использовать также такую форму адресации к памяти:
mov al,CharString[bx]
или даже
mov al,CharString[bx][si]+1
Все эти формы представляют собой то же самое, что размещение
отдельных элементов адресации к памяти в одной паре квадратных
скобок и разделение их знаком плюс. Таким образом, последний опе-
ратор эквивалентен оператору:
mov al,[charString+bx+si+1]
Здесь снова нужно выбрать ту форму записи, которая вам боль-
ше нравится, и придерживаться ее.
Квадратные скобки, в которые заключаются регистры, указываю-
щие на ячейки памяти, являются обязательными. Без этих скобок,
BX, например, интерпретируется, как операнд, а не как ссылка на
операнд.
Комментарии
-----------------------------------------------------------------
Расскажем наконец, что представляет собой поле комментария.
Комментарии не выполняют никаких реальных действий в том смысле,
что они не влияют на код выполняемого Турбо Ассемблером файла. Но
это не означает, что они не являются существенными.
Вероятно, вы уже знаете, как программировать на языке высо-
кого уровня (Си, Паскале, Прологе или другом), поскольку очень
немногие начинают свое знакомство с программированием с Ассембле-
ра. По мере знакомства с этим языком вы будете вновь и вновь
встречаться с советами и рекомендациями по необходимости акку-
ратной записи комментариев. Это прекрасный совет, так как и слож-
ность, и время, прошедшее с момента ее написания, могут сделать
программу совершенно непонятной даже для ее автора.
По сравнению с языком Ассемблера программа, например, на
Паскале гораздо более наглядна. Программы Паскаля хорошо структу-
рированы, он имеет жестко типизованные переменные, арифметические
выражения, вызовы процедур и функций с формальными и фактическими
параметрами.
Ассемблер же не имеет встроенных управляющих структур, жест-
кой принудительной типизации данных, включающих переменные ариф-
метических выражений и присущего ему механизма передачи парамет-
ров. Короче говоря, код Ассемблера - это не тот хорошо структури-
рованный и легко обслуживаемый текст, к которым вы, возможно,
встречались. Поэтому, чтобы поднять Ассемблер до уровня естест-
венного кодирования, вы должны широко пользоваться комментариями,
а также подпрограммами и макрокомандами.
Имеется много способов включения в код Ассемблера коммента-
риев. Один из наиболее полезных подходов состоит в том, чтобы
слева от каждой инструкции помещать комментарий, в котором содер-
жится ее краткое пояснение. Например, инструкция:
mov [bx],al ; сохранить измененный символ
выглядит более понятной, чем инструкция
mov [bx],al
Необязательно комментировать каждую строку. Например, ком-
ментарии типа:
.
.
.
mov ah,1 ; функция DOS ввода с клавиатуры
int 21h ; вызвать DOS, чтобы получить
; следующую нажатую клавишу
.
.
.
не служат никакой полезной цели. Это, однако, не означает, что
комментировать такие строки не следует. Просто делайте коммента-
рии более короткими:
.
.
.
mov ah,1
int 21h ; получить следующую клавишу
.
.
.
Нужно помнить о том, что целью комментариев является не объ-
яснение каждого элемента вашей программы, а облегчение анализа ее
текста и понимания (вами или кем-либо другим).
Другим хорошим методом комментирования является использова-
ния для пояснения блоков кода строк-комментариев. Такие коммента-
рии могут описывать работу программы на более высоком уровне, чем
комментарии отдельных строк. Рассмотрим следующий пример:
.
.
.
;
; Сгенерировать для буфера передачи байт контрольной суммы
;
mov bx,OFFSET TransferBuffer
mov cx,TRANSFER_BUFFER_LENGTH
sub al,al ; очистить аккумулятор контрольной суммы
CheckSum:
add al,[bx] ; добавить в него текущее значение байта
inc bx ; указать на следующий байт
.
.
.
Обратите внимание, что мы не включаем комментарий в каждую
строку. Из комментариев данного блока программы видно, что в ре-
гистр BX загружается адрес буфера передачи, а в CX - длина буфе-
ра. В комментарии к этому блоку из семи строк кратко суммируется
его работа, поэтому комментарии каждой строки становятся менее
важными. Если кто-нибудь будет просматривать программу, то он
больше полезного извлечет из комментариев к блокам, чем из ком-
ментариев к строкам.
Другой метод комментирования еще более высокого уровня сос-
тоит во включении перед каждой подпрограммой описательного заго-
ловка-комментария ("шапки" программы). Такой заголовок может со-
держать описание подпрограммы, ее входные и выходные значения и
различные замечания по ее работе. Например:
;
; Функция, возвращающая контрольную сумму (размером в
; байт) буфера данных
;
; Входные данные:
; DS:BX - указатель на начало буфера
; CX - длина буфера
;
; Выходные данные:
; AL - контрольная сумма буфера
;
; Используемые регистры (содержимое не сохраняется):
; BX, CX
;
; Примечание: буфер не должен превышать 64К и не должен
; пересекать границу сегментов.
;
CheckSum PROC NEAR
sub al,al ; очистить аккумулятор
; контрольной суммы
add al,[bx] ; прибавить текущее
; значение байта
inc bx ; ссылка на следующее
; значение
loop CheckSum
ret
CheckSum ENDP
Вы наверное согласитесь с выводом, что если подпрограмма на-
писана и отлажена (то есть правильно работает), то редко прихо-
дится снова просматривать ее текст. Все, что вам потребуется уз-
нать, - это какие функции выполняет подпрограмма, то есть, что
происходит, когда вы ее вызываете, и как подпрограмма взаимодейс-
твует с вызывающей ее программой. Этим целям довольно хорошо от-
вечает приведенный в примере описательный заголовок (который
иногда называют также "шапкой" программы).
Существует и много других методов комментирования, и вы, без
сомнения, придумаете такой метод, который больше подходит для ва-
шего стиля программирования. Важно только писать комментарии с
самого начала, благодаря чему комментирование станет со временем
неотъемлемой частью вашего стиля программирования.
Директивы определения сегментов
-----------------------------------------------------------------
И в данной главе, и в предыдущей, мы уже уделили много вре-
мени для пояснения того, что собой представляют директивы опреде-
ления сегментов и как они влияют на составляемую вами программу.
Однако, имеется еще один момент, которого мы пока не касались.
Суть его в следующем: откуда Турбо Ассемблер в точности знает, в
каком сегменте или сегментах находятся код и данные?
Управление сегментами - это один из наиболее сложных аспек-
тов программирования на языке Ассемблера для процессоров 8086. В
Турбо Ассемблере предусмотрен не один, а целых два набора дирек-
тив управления сегментами. Первый набор, состоящий из упрощенных
директив определения сегментов, делает управление сегментами от-
носительно легким и идеально подходит для компоновки модулей Ас-
семблера с языками высокого уровня, но поддерживает только неко-
торые из сегментных средств, имеющихся в Турбо Ассемблере.
Второй набор, состоящий из стандартных (полных) директив опреде-
ления сегментов, более сложно использовать, но он предусматрива-
ет полное управление сегментами, необходимое в некоторых прик-
ладных программах Ассемблера. Далее мы рассмотрим как
упрощенные, так и стандартные директивы определения сегментов.
Здесь же мы просто дадим обзор того, как можно использовать ди-
рективы определения сегментов для составления ваших программ (их
полное описание содержится в Главе 9 "Развитое программирование
на Турбо Ассемблере").
Упрощенные директивы определения сегментов
-----------------------------------------------------------------
Основными упрощенными директивами определения сегментов яв-
ляются директивы .STACK, .CODE, .DATA, .MODEL и DOSSEG. Рассмот-
рим эти директивы, разбив их на две группы. Первой будет группа
директив .STACK, .CODE и .DATA.
Директивы .STACK, .CODE и .DATA
-----------------------------------------------------------------
Директивы определения сегментов .STACK, .CODE и .DATA
определяют, соответственно, сегмент стека, сегмент кода и сегмент
данных. Например, директива:
.STACK 200h
определяет стек размером в 200h (512) байт. Что касается стека,
то это все, что вы сможете сделать. Необходимо просто убедиться,
что в вашей программе имеется директива .STACK, и Турбо Ассемблер
выделит для вас стек. Для обычных программ вполне подходит стек
размером 200h, хотя в программах, интенсивно использующих стек
(например, в программах, содержащих рекурсивные вызовы) может
потребоваться стек большего размера. (Информация об исключениях
при использовании директивы .STACK приведена в разделе "Невыделе-
ние стека или выделение слишком маленького стека" в Главе 6.)
Директива .CODE отмечает начало сегмента кода. Вы можете
посчитать, что для Турбо Ассемблера достаточно очевидно, что все
ваши инструкции относятся к сегменту кода. На самом деле Турбо
Ассемблер позволяет вам (с помощью стандартных директив определе-
ния сегментов) использовать несколько сегментов кода, а директива
.CODE указывает Турбо Ассемблеру, в какой именно сегмент надо по-
местить ваши инструкции. Определение сегмента кода еще проще, чем
определение сегмента стека, так как аргументы для директивы .CODE
указывать не требуется. Например:
.
.
.
.CODE
sub ax,ax ; установить аккумулятор в значение 0
mov cx,100 ; число выполняемых циклов
.
.
.
Директива .DATA несколько более сложна. Как можно понять,
директива .DATA отмечает начало сегмента данных. В этом сегменте
следует размещать ваши переменные памяти. Например:
.
.
.
.DATA
TopBoundary DW 100
Counter DW ?
ErrorMessage BD 0dh,0dh,'***Ошибка***',0dh,0ah,'$'
.
.
.
Это довольно просто. Вся "сложность" директивы .DATA заклю-
чается в том, что до того, как вы будете обращаться к ячейкам па-
мяти в сегменте, определенном с помощью директивы .DATA, нужно
явно загружать сегментный регистр DS идентификатором @data. Так
как сегментный регистр можно загрузить из регистра общего назна-
чения или ячейки памяти, но в него нельзя загрузить константу,
регистр DS обычно загружается с помощью последовательности из
двух инструкций:
.
.
.
mov ax,@data
mov ds,ax
.
.
.
(Вместо регистра AX можно использовать любой общий регистр.) Дан-
ная последовательность инструкций устанавливает DS таким образом,
чтобы он указывал на сегмент данных, который начинается по дирек-
тиве .DATA.
Следующая программа выводит на экран текст, хранящийся в
строке DataString:
DOSSEG
.MODEL
.STACK small
.DATA
DataString DB 'Этот текст находится в сегменте данных'
.CODE
ProgramStart:
mov bx,@data
mov ds,bx ; устанавливает регистр DS на сегмент
; данных
mov dx,OFFSET DataString ; DX указывает на смещение
; DataString в сегменте .DATA
mov ah,9 ; номер функции DOS печати строки
int 21h ; вызвать DOS для печати строки
mov ah,4ch ; вызвать DOS для завершения программы
END ProgramStart
Без двух инструкций, которые устанавливают регистр DS в зна-
чение сегмента, определенного с помощью директивы .DATA, функция
печати строки не будет правильно работать. Строка DataString на-
ходится в сегменте данных и недоступна, пока регистр DS не будет
установлен в значение этого сегмента. Это можно рассматривать
следующим образом: когда вы вызываете операционную систему DOS
для печати строки, в паре регистров DS:DX вы передаете полный ад-
рес в формате "сегмент:смещение". Полный указатель вида "сегмент:
смещение" вы получите только после того, как в регистр DS будет
загружен сегмент данных, а в DX - смещение DataString.
У вас может возникнуть вопрос, почему нужно загружать ре-
гистр DS, а не CS или SS (или ES)?
Во первых, регистр CS никогда не загружается явно, так как
при запуске программы за вас это делает операционная система DOS.
Кроме того, если бы регистр CS уже не был установлен, когда приш-
ло время выполнить первую инструкцию программы, процессор 8086 не
знал бы, где найти эту инструкцию, и программа не стала бы рабо-
тать. Пока для вас это может быть недостаточно очевидно, но по-
верьте нам на слово: регистр CS загружается при запуске программы
автоматически, и у вас нет необходимости загружать его явно.
Аналогично, при запуске программы DOS загружает регистр SS,
значение которого при выполнении программы обычно остается неиз-
менным. Хотя изменять содержимое регистра SS можно, это редко
оказывается желательным, и определенно это не стоит делать, если
вы не знаете точно, что вы делаете. Таким образом, регистр SS
аналогично регистру CS устанавливается при начале выполнения
программы автоматически, и далее его трогать не нужно.
Регистр DS существенно от них отличается. В то время как ре-
гистр CS указывает на инструкции, а SS - на стек, регистр DS ука-
зывает на данные. Программы не работают непосредственно с инс-
трукциями или стеком, однако с данными они работают постоянно.
Кроме того, программы могут в любой момент пожелать получить дан-
ные в нескольких разных сегментах. Нужно помнить о том, что про-
цессор 8086 в любое время позволяет вам получить доступ к любой
ячейке памяти в пределах 1 Мбайта, но только в блоках по 64К (от-
носительно сегментного регистра).
Вы можете захотеть загрузить регистр DS одним сегментом, по-
лучить доступ к данным в этом сегменте, а затем загрузить DS дру-
гим сегментом, чтобы обратиться к другому блоку данных. В малень-
ких и средних программах (таких, как в приведенных нами примерах)
вам не потребуется использовать более одного сегмента данных, но
в больших программах несколько сегментов данных используется час-
то. Кроме того, вам потребуется загружать регистр DS различным
значениями, если вы хотите получить доступ к системным областям
памяти, например, к ячейкам памяти, используемым базовой системой
ввода-вывода (BIOS).
Из всего этого можно сделать следующий краткий вывод: Турбо
Ассемблер позволяет вам в любой момент установить регистр DS в
значение любого сегмента. За эту гибкость приходится расплачи-
ваться тем, что вы должны явно устанавливать регистр DS в значе-
ние нужного сегмента (обычно @data), что эквивалентно сегменту,
который начинается с директивы .DATA. После этого вы сможете по-
лучить доступ к ячейкам памяти этого сегмента.
Сегментный регистр ES загружается аналогично регистру DS.
Чаще всего вам не потребуется использовать регистр ES, но когда
появится необходимость получить доступ к ячейке памяти в сегмен-
те, на который указывает регистр ES, вы должны сначала загрузить
регистр ES значением этого сегмента. Например, следующая прог-
рамма загружает регистр ES значением сегмента .DATA, а затем
загружает через ES символ, который нужно напечатать из этого
сегмента:
DOSSEG
.MODEL small
.STACK 200h
.DATA
OutputChar DB 'B'
.CODE
ProgramStart:
mov dx,@data
mov es,dx ; установить ES в значение
; сегмента .DATA
mov bx,OFFSET OutputChar ; BX указывает на
; смещение OutputChar
mov al,es:[bx] ; получить выводимый символ
; из сегмента, на который
; указывает регистр ES
mov ah,2 ; функция DOS вывода символа
int 21h ; вызвать DOS для вывода
; символа на экран
END ProgramStart
Обратите внимание, что регистр ES (как и регистр DS ранее)
загружается последовательностью из двух инструкций:
.
.
.
mov dx,@Data
mov es,dx
.
.
.
Положим, в данном примере нет конкретной причины использо-
вать вместо DS регистр ES. Фактически, использование регистра ES
означает, что мы применили префикс переопределения сегмента ES:
(как это описывается в Главе 9). Однако во многих случаях чрезвы-
чайно удобно, когда регистр DS указывает на один сегмент, а ре-
гистр ES - на другой (особенно это касается использования строко-
вых инструкций).
Директива DOSSEG
-----------------------------------------------------------------
Директива DOSSEG приводит к тому, что сегменты в программе
Ассемблера будут сгруппированы в соответствии с соглашениями по
упорядочиванию сегментов фирмы Microsoft. В данным момент вам не
следует вникать в смысл того, что это означает. Запомните просто,
что почти все автономные программы на Ассемблере будут прекрасно
работать, если вы начнете их с директивы DOSSEG.
При компоновке модулей Ассемблера с модулями языков высокого
уровня задавать директиву DOSSEG не обязательно, так как в языке
высокого уровня автоматически выбирается упорядочивание сегментов
по стандарту фирмы Microsoft. Однако эта директива не повредит.
Из всего перечисленного можно сделать заключение, что ис-
пользование директивы DOSSEG в качестве первой строки вашей прог-
раммы является простейшим подходом (если у вас нет конкретной
причины поступать иначе). Благодаря этому вы сможете использовать
определенный порядок сегментов (более подробно о директиве DOSSEG
рассказывается в Главе 3 "Справочного руководства").
Директива .MODEL
-----------------------------------------------------------------
Директива .MODEL определяет модель памяти в модуле Ассембле-
ра, где используются упрощенные директивы определения сегментов.
Заметим, что в "ближнем" коде переходы осуществляются с помощью
загрузки одного регистра IP, а в "дальнем" коде - путем загрузки
регистров CS и IP. Аналогично, к "ближним" данным обращение вы-
полняется только по смещению, а к "дальним" - с помощью полного
адреса "сегмент:смещение". Вкратце, термин "дальний" (FAR) озна-
чает использование полного 32-разрядного адреса ("сегмент:смеще-
ние"), а "ближний" (NEAR) означает использование 16-разрядных
смещений.
Существуют следующие модели памяти:
Сверхмалая И код программы, и ее данные должны размещаться
внутри одного и того же сегмента размером 64К.
Код и данные имеют ближний тип.
Малая Код программы должен размещаться внутри одного
сегмента данных размером 64К, а данные программы
должны размещаться в отдельном сегмента данных
(размером 64К). И код, и данные должны быть ближ-
него типа.
Средняя Код программы может превышать 64К, но данные
программы должны помещаться в один сегмент разме-
ром 64К. Код имеет дальний тип, а данные - ближ-
ний.
Компактная Код программы должен помещаться в один сегмент
размером 64К, а данные могут превышать по размеру
64К. Код имеет ближний тип, а данные - дальнего
типа.
Большая И код, и данные программы могут превышать 64К, но
один массив данных не может превышать 64К. И код,
и данные имеют дальний тип.
Сверхбольшая И код, и данные программы могут превышать по раз-
меру 64К. Массивы данных также могут превышать
64К. Код и данные имеют дальний тип. Указатели на
элементы массива также дальнего типа.
Заметим, что с точки зрения Ассемблера большая и сверхболь-
шая модели идентичны. Сверхбольшая модель не поддерживает автома-
тически массивы данных, превышающие 64К.
Немногие программы на Ассемблере используют более 64К кода
или данных, поэтому для большинства прикладных задач подходит ма-
лая модель. Вы должны использовать малую модель там, где это воз-
можно, так как дальний код (средняя, большая и сверхбольшая мо-
дель) замедляет выполнение программы, а с данными дальнего типа
(компактная, большая и сверхбольшая модель) на Ассемблере рабо-
тать значительно труднее.
Описанные здесь модели памяти соответствуют моделям, исполь-
зуемым в Турбо Си (и во многих других компиляторах для компьютера
IBM PC). Когда вы компонуете модуль на Ассемблере с языком высо-
кого уровня, убедитесь, что используется правильная директива
.MODEL. Эта директива обеспечивает соответствие имен сегментов
Ассемблера тем именам, которые используются в языках высокого
уровня, а также используемого по умолчанию типа меток PROC, ис-
пользующихся для наименования подпрограмм, процедур и функций,
типу (ближнему или дальнему), используемому в языках высокого
уровня.
Директиву .MODEL необходимо указывать, если вы используете
упрощенные директивы определения сегментов, так как в противном
случае Турбо Ассемблер не будет знать как устанавливать сегменты,
определенные с помощью директив .CODE и .DATA. Директива .MODEL
должна предшествовать директивам .CODE, .DATA и .STACK.
Приведем пример наброска программы, использующей упрощенные
директивы определения сегментов:
DOSSEG
.MODEL small
.STACK 200h
.DATA
MemVar DW 0
.
.
.
.CODE
ProgramStart:
mov ax,@data
mov ds,ax
mov ax,[MemVar]
.
.
.
mov ah,4ch
int 21h
END ProgramStart
Другие упрощенные директивы определения сегментов
-----------------------------------------------------------------
Имеется еще несколько общеупотребительных директив определе-
ния сегментов. Вам они потребуются только для больших или специ-
альных программ, поэтому мы только кратко упомянем их. За более
подробной информацией вы можете обратиться к Главе 9.
Директива .DATA? используется аналогично директиве .DATA, но
она определяет ту часть сегмента данных, которая содержит неини-
циализированные данные. Она обычно используется в модулях Ассемб-
лера, которые компонуются с языком высокого уровня.
Директива .FARDATA позволяет вам определить дальний сегмент
данных, то есть сегмент данных, отличный от стандартного сегмента
@data, разделяемого (совместно используемого) всеми модулями. Ди-
ректива .FARDATA позволяет в модуле Ассемблера определить свои
собственные сегменты размером до 64К. Если задана директива
.FARDATA, то именем определенного по этой директиве дальнего сег-
мента данных будет @fardata, так же как @data - имя сегмента, оп-
ределенного по директиве .DATA.
Директива .FARDATA? во многом аналогична директиве .FARDATA,
но она определяет неинициализированный сегмент дальнего типа. Так
же как и для директивы .FARDATA и имени @fardata, при указании
директивы .FARDATA? сегмент данных дальнего типа, определенный по
этой директиве, получает имя @fardata?.
Директива .CONST определяет ту часть сегмента данных, в ко-
торой содержатся константы. Опять-таки это имеет силу только при
компоновке кода Ассемблера с языком высокого уровня.
При использовании упрощенных директив определения сегментов
можно использовать некоторые предопределенные метки. Метка
@FileName представляет собой имя ассемблируемого файла, @curseg -
имя сегмента, в который Турбо Ассемблер в данный момент выполняет
ассемблирование, @CodeSize - это 0 для моделей памяти с ближними
сегментами кода (сверхмалой, малой и компактная), 1 - для ком-
пактной и большой модели памяти и 2 - для сверхбольшой модели.
Аналогично, @DataSize = 0 в модели памяти с сегментами данных
ближнего типа (сверхмалая, малая и средняя модель памяти), 1 в
компактной и большой моделях и 2 - для сверхбольшой модели.
Стандартные директивы определения сегментов
-----------------------------------------------------------------
Далее мы приведем такой же пример программы, как и в преды-
дущем разделе, но на этот раз используем стандартные директивы
определения сегментов SEGMENT, ENDS и ASSUME.
DGROUP GROUP _DATA, STACK
ASSUME CS:_TEXT, DS:_DATA, SS:STACK
STACK SEGMENT PARA STACK 'STACK'
DB 200h DUP (?)
STACK ENDS
_DATA SEGMENT WORD PUBLIC 'DATA'
MemVar DW 0
.
.
.
_DATA ENDS
_TEXT SEGMENT WORD PUBLIC 'CODE'
ProgramStart:
mov ax,_DATA
mov ds,ax
mov ax,[MemVar]
.
.
.
mov ah,4ch
int 21h
_TEXT ENDS
END ProgramStart
Теперь вы видите, почему упрощенные директивы определения
сегментов называются упрощенными. Однако, многое из того, что де-
лают упрощенные директивы определения сегментов предназначено для
того, чтобы облегчить компоновку модулей Ассемблера с языками вы-
сокого уровня, что является излишним в автономных программах на
Ассемблере. Приведем пример программы HELLO с использованием
стандартных директив определения сегментов:
Stack Segment PARA STACK 'STACK'
DB 200h DUP (?)
Stack ENDS
Data SEGMENT WORD 'DATA'
HelloMessage DB 'Привет!',13,10,'$'
Data ENDS
Code Segment WORD 'CODE'
ASSUME CS:Code, DS:Data
ProgramStart:
mov ax,Data
mov ds,ax ; установить DS в значение
; сегмента данных
mov dx,OFFSET HelloMessage ; DS:DX указывает
; на сообщение 'Привет!'
mov ah,9 ; функция DOS вывода строки
int 21h ; вывести строку на экран
mov ah,4ch ; функция DOS завершения
; программы
int 21h ; завершить программу
Code ENDS
END ProgramStart
Последний пример не слишком усложнился, но тем не менее яс-
но, что стандартные директивы определения сегментов более сложны,
чем упрощенные директивы.
В Главе 9 стандартные (полные) директивы определения сегмен-
тов описываются более подробно. В данном разделе разделе мы пы-
таемся только дать вам представление о том, что делают стандарт-
ные директивы определения сегментов.
Директива SEGMENT
-----------------------------------------------------------------
Директива SEGMENT определяет начало сегмента. Метка, которая
указывается в данной директиве, определяет начало сегмента. Нап-
ример, директива:
Cseg SEGMENT
определяет начало сегмента с именем Cseg. Директива SEGMENT может
также (необязательно) определять атрибуты сегмента, включая вы-
равнивание в памяти на границу байта, слова, двойного слова, па-
раграфа (16 байт) или страницы (256 байт). Другие атрибуты вклю-
чают в себя способ, с помощью которого сегмент будет комбиниро-
ваться с другими сегментами с тем же именем и классом сегмента.
Директива ENDS
-----------------------------------------------------------------
Директива ENDS определяет конец сегмента. Например:
Cseg ENDS
завершает сегмент с именем Cseg, который начинался по директиве
SEGMENT. При использовании стандартных директив определения сег-
ментов вы должны явным образом завершать каждый сегмент.
Директива ASSUME
-----------------------------------------------------------------
Директива ASSUME указывает Турбо Ассемблеру, что в значение
какого сегмента установлен данный сегментный регистр. Директиву
ASSUME CS: требуется указывать в каждой программе, в которой ис-
пользуются стандартные сегментные директивы, так как Турбо Ас-
семблеру необходимо знать о сегменте кода для того, чтобы устано-
вить выполняемую программу. Кроме того, обычно используются
директивы ASSUME DS: и ASSUME ES:, благодаря которым Турбо Ас-
семблер знает, к каким ячейкам памяти вы можете адресоваться в
данный момент.
Директива ASSUME позволяет Турбо Ассемблеру проверить допус-
тимость каждого обращения к именованной ячейке памяти с учетом
значения текущего сегментного регистра. Рассмотрим следующий при-
мер:
.
.
.
Data1 SEGMENT WORD 'DATA'
Var1 DW 0
Data1 ENDS
.
.
.
Data2 SEGMENT WORD 'DATA'
Var2 DW 0
Data2 ENDS
Code SEGMENT WORD 'CODE'
ASSUME CS:Code
ProgramStart:
mov ax,Data1
mov ds,ax ; установить DS в Data1
ASSUME DS:Data1
mov ax,[Var2] ; попытаться загрузить Var2 в AX
; это приведет к ошибке, так как
; Var2 недоступна в сегменте
; Data1
.
.
.
mov ah,4ch ; номер функции DOS для
; завершения программы
int 21h ; завершить программу
Code ENDS
END ProgramStart
Турбо Ассемблер отмечает в данной программе ошибку, так как
в ней делается попытка получить доступ к переменной памяти Var2,
когда регистр DS установлен в значение сегмента Data1 (к Var2
нельзя адресоваться, пока DS не будет установлен в значение сег-
мента Data2).
Важно понимать, что Ассемблер на самом деле не знает, что
регистр DS установлен в значение Data1. С помощью директивы
ASSUME вы указали Турбо Ассемблеру, что нужно сделать такое допу-
щение. Директива ASSUME дает вам способ в любой момент сообщить
Ассемблеру о значении сегментного регистра, после чего Турбо Ас-
семблер будет сообщать вам, если вы пытаетесь сделать невозмож-
ное.
Однако Турбо Ассемблер не может перехватывать все подобные
ошибки. Когда в ссылке на память используется именованная пере-
менная памяти (такая, как Var1 и Var2 в предыдущем примере), Тур-
бо Ассемблер может проверить допустимость этой ссылки, так как
каждая именованная переменная памяти явным образом связана с сег-
ментом. Невозможно сообщить Турбо Ассемблеру, к какому сегменту
пытается обратиться инструкция:
mov al,[bx]
В этом случае Турбо Ассемблер должен предположить, что зна-
чение сегментного регистра DS соответствует тому сегменту, к ко-
торому вы хотите обратиться.
Если в данный момент сегментный регистр не указывает ни на
какой именованный сегмент, то чтобы сообщить об этом Ассемблеру,
можно использовать в директиве ASSUME ключевое слово NOTHING.
Например:
.
.
.
mov ax,0b800h
mov ds,ax
ASSUME ds:NOTHING
.
.
.
Здесь регистр DS устанавливается таким образом, чтобы указы-
вать на цветной графический экран, а затем Турбо Ассемблеру сооб-
щается, что регистр DS не указывает ни на какой именованный сег-
мент. Вот еще один способ ссылки на цветной графический экран:
.
.
.
ColorTextSeg SEGMENT AT 0B8000h
ColorTextMemory LABEL BYTE
ColorTextSeg ENDS
.
.
.
mov ax,ColorTextSeg
mov ds,ax
ASSUME ds:ColorTextSeg
.
.
.
Обратите внимание, что в директиве AT, которая следует за
директивой SEGMENT, задается явный начальный адрес сегмента.
Сделаем последнее замечание по директиве ASSUME: в некоторых
случаях она может привести к тому, что Турбо Ассемблер будет ис-
пользовать для доступа к памяти не тот сегментный регистр, кото-
рый вы ожидаете, а другой. Рассмотрим, например, следующий фраг-
мент программы:
.
.
.
Data1 SEGMENT WORD 'DATA'
Var1 DW 0
Data1 ENDS
Data2 SEGMENT WORD 'DATA'
Var2 DW 0
Data2 ENDS
Code SEGMENT WORD 'CODE'
ASSUME CS:Code
ProgramStart:
mov ax,Data1
mov ds,ax ; установить DS в Data1
ASSUME DS:Data1
mov ax,Data2
mov es,ax ; установить ES в Data2
ASSUME ES:Data2
mov ax,[Var2] ; загрузить Var2 в AX -
; Турбо Ассемблер укажет
; процессору 8086, что
; загрузку нужно выполнять
; относительно ES, так как
; к Var2 нельзя получить
; доступ относительно DS
.
.
.
mov ah,4ch ; функция DOS завершения
; работы программы
int 21h ; завершить программу
Code ENDS
END ProgramStart
Данный пример должен быть вам знаком: это модифицированная
версия фрагмента программы, использованного нами ранее для того,
чтобы показать, как директива ASSUME позволяет Турбо Ассемблеру
указать вам, когда вы пытаетесь использовать недопустимую ссылку
на память. Однако в данном примере сообщение об ошибке не выво-
дится. Но это не означает, что Турбо Ассемблер позволяет вам сде-
лать ошибку. Он модифицирует инструкцию:
mov ax,[Var2]
для доступа к Var2 относительно сегментного регистра ES, а не
сегментного регистра DS.
Это происходит по следующим причинам. Две директивы ASSUME
информируют Турбо Ассемблер о том, что регистр DS установлен в
значение сегмента Data1, а ES установлен в значение сегмента
Data2. Турбо Ассемблер совершенно правильно заключает, что к Var2
нельзя получить доступ относительно регистра DS, однако Var2 дос-
тупно относительно сегментного регистра ES. В итоге Турбо Ассем-
блер включает перед инструкцией MOV специальный код (префикс пе-
реопределения сегмента), чтобы указать процессору 8086, что вмес-
то сегментного регистра DS нужно использовать сегментный регистр
ES.
Какое все это имеет для вас значение? Это значит, что если
вы корректно используете директивы ASSUME, позволяя Турбо Ассем-
блеру узнать о текущих установленных для регистров DS и ES значе-
ниях, то он может автоматически вам помочь, проверяя возможность
доступа к именованным переменным в памяти и в некоторых случаях
даже может выполнить автоматическую корректировку сегмента.
Общее обсуждение префиксов переопределения сегментов и стан-
дартные директивы определения сегментов обсуждаются в Главе 10.
Стандартные или упрощенные директивы определения сегментов?
-----------------------------------------------------------------
Теперь, когда вы познакомились и с упрощенными, и со стан-
дартными директивами определения сегментов, возникает вопрос, ка-
кой набор директив определения сегментов следует использовать?
Ответ зависит от типа выполняемого программирования на Ассембле-
ре.
Если вы компонуете модули на Ассемблере с языками высокого
уровня, почти всегда желательно использовать упрощенные директивы
определения сегментов. Эти директивы выполняют всю работу по наи-
менованию сегментов и все функции, связанные с моделью памяти и
организации интерфейса с языками высокого уровня.
Если вы пишете большие автономные программы на Ассемблере,
используя много сегментов и смешанные модели памяти (код ближнего
и дальнего типа и/или данные ближнего и дальнего типа в одной
программе), то вам потребуется использовать стандартные директивы
определения сегментов, что позволит вам полностью управлять типом
сегмента, выравниванием, наименованием сегментов и способом их
комбинирования (сочетания).
Кратко можно сформулировать следующее правило: используйте
упрощенные директивы определения сегментов, пока вы не обнаружи-
те, что вам необходимо получить полное управление определениями
сегментов, которое может обеспечить только стандартное (полное)
определение сегментов.
Выделение данных
-----------------------------------------------------------------
Теперь, когда вы знаете, как создавать сегменты, давайте
рассмотрим, как можно заполнить эти сегменты осмысленными данны-
ми. Сегмент стека проблемы не представляет: там находится стек, а
к стеку вы можете обратиться с помощью инструкций PUSH и POP и
адресоваться через регистр BP. Сегмент кода заполняется инструк-
циями, которые генерируются в соответствии с мнемоникой инструк-
ций вашей программы, поэтому проблемы здесь также нет.
Остается сегмент данных. В Турбо Ассемблере предусмотрено
множество способов определения переменных в сегменте данных, как
инициализируемых некоторым значением, так и неинициализированных.
Чтобы понять, какие данные позволяет вам определять Турбо Ассемб-
лер, мы должны сначала немного рассказать вам основных типах дан-
ных Ассемблера.
Биты, байты и основания
-----------------------------------------------------------------
Основной единицей памяти компьютера является бит. В бите мо-
жет храниться значение 0 или 1. Бит сам по себе не особенно поле-
зен. Процессор 8086 не работает непосредственно с битами, он ра-
ботает с байтами, которые состоят из 8 бит.
Так как бит на самом деле представляет собой цифру с основа-
нием 3, байт содержит 8-разрядное число с основанием 2. Наиболь-
шие возможные числа с основанием 2 - это следующие числа:
2 в степени 0: 1
2 в степени 1: 2
2 в степени 2: 4
2 в степени 3: 8
2 в степени 4: 16
2 в степени 5: 32
2 в степени 6: 64
2 в степени 7: + 128
---
255
Это означает, что в байте может храниться значение в диапа-
зоне от 0 до 255.
В каждом из 8-разрядных регистров процессора 8086 (AL, AH,
BL, CL, CH, DL и DH) храниться ровно 1 байт. В каждой из 1000000
адресуемых ячеек памяти процессора 8086 также может храниться
ровно 1 байт.
Набор символов компьютера PC (который включает в себя строч-
ные и прописные символы, цифры от 0 до 9, специальные графические
символы, научные и специальные символы, а также различные знаки
пунктуации и прочие символы) состоит из ровно 256 символов.
Это число уже знакомо нам, не правда ли? Это не удивительно,
ведь набор символов компьютера РС построен таким образом, что в 1
байте хранится 1 символ.
Байт - это наименьшая единица адресации процессора 8086. В
нем может храниться 1 символ, 1 беззнаковое значение от 0 до 255
или одно значение со знаком в диапазоне от -128 до 127. Ясно, что
байт не подходит для многих задач программирования на Ассемблере,
например, хранения целых значений и значений с плавающей точкой,
а также указателей на память.
Следующая по величине единица памяти процессора 8086 - это
16-разрядное слово. Слово вдвое превосходит по размеру байт (со-
держит 16 бит). Фактически, слово храниться в памяти в виде двух
последовательных байтовых ячеек. Адресное пространство процессора
8086 можно рассматривать, как 500000 слов. Каждый из 16-разрядных
регистров (регистр AX, BX, CX, DX, SI, DI, BP, SP, CS, DS, ES,
SS, IP и регистр флагов) хранит одно слово.
Наибольшие возможные 16-разрядные числа с основанием 2 - это
следующие числа:
2 в степени 0: 1
2 в степени 1: 2
2 в степени 2: 4
2 в степени 3: 8
2 в степени 4: 16
2 в степени 5: 32
2 в степени 6: 64
2 в степени 7: 128
2 в степени 8: 256
2 в степени 9: 512
2 в степени 10: 1024
2 в степени 11: 2048
2 в степени 12: 4096
2 в степени 13: 8182
2 в степени 14: 16384
2 в степени 15: + 32768
-----
65535
Это также представляет собой максимальную величину целого
без знака, что не случайно, так как целые имеют длину 16 бит. Це-
лые со знаком (которые могут принимать значения в диапазоне от
-32768 до +32676) также хранятся в словах.
Так как слова имеют размер 16 бит, они могут адресоваться по
любому смещению в данном сегменте, поэтому значения размером в
слово достаточно велики, чтобы их можно было использовать в ка-
честве указателей памяти. Если вы вспомните, регистры BX, BP, SI
и DI размером в слово используются, как указатели на память.
Значения, хранимые в виде 32-битовых (4-байтовых) элементов,
называются двойными словами. Хотя процессор 8086 не может непос-
редственно работать с 32-битовыми целыми значениями, выполнять
32-разрядные арифметические операции (с помощью двух последова-
тельных 16-разрядных операций) можно с помощью таких инструкций,
как ADC и SBB. Благодаря двойным словам беззнаковые целые могут
принимать значения в диапазоне от 0 до 4294967295, а целые со
знаком - в диапазоне от -2147483648 до +2147483647.
Процессор 8086 может загружать указатель вида "сегмент:сме-
щение" из двойного слова в сегментный регистр и в регистр общего
назначения (с помощью инструкций LDS или LES), но это далеко от
непосредственных операций с двойными словами. В виде двойных слов
хранятся также числа одинарной точности с плавающей точкой (числа
с одинарной точностью занимают 4 байта и могут принимать значения
от 10 в -38 степени до 10 в 38 степени).
Каждое значения с плавающей точкой двойной точности требует
для хранения 8 байт. Такие 64-битовые значения называются четвер-
ными словами. В процессоре 8086 встроенная поддержка четверных
слов отсутствует. Однако арифметический сопроцессор 8087 исполь-
зует четверные слова в качестве базового типа данных. (Числа с
двойной точностью могут принимать значения в диапазоне от 10 в
-308 степени до 10 в 308 степени и иметь точность до 16 цифр.)
Для временных (промежуточных) значений с плавающей точкой
Турбо Ассемблер поддерживает еще один тип данных - элемент данных
длиной 10 байт. Этот 10-байтовый элемент данных (так называемый
временно-вещественный формат) может также использоваться для хра-
нения упакованных двоично-десятичных (или двоично кодированных
десятичных) значений (BCD), в которых в каждом байте хранится две
десятичных цифры.
Стоит заметить, что процессор 8086 хранит значения размером
в слово или двойное слова в памяти, при этом младший байт следует
первым. То есть, если значения размером в слово хранится по адре-
су 0, то в биты 7-0 значения записаны по адресу 0, а биты 15-8 -
по адресу 1 (см. Рис. 5.4).
-----------------------
WordVar --------------------> 0 | 9Fh |
|---------------------|
1 | 19h |
|---------------------|
2 | ? |
|---------------------|
3 | ? |
|---------------------|
4 | ? |
|---------------------|
DwordVar ------------------> 5 | 78h |
|---------------------|
6 | 56h |
|---------------------|
7 | 34h |
|---------------------|
8 | 12h |
|---------------------|
9 | ? |
|---------------------|
| |
. .
. .
Рис. 5.4 Переменная WordVar (размером в слово) содержит зна-
чение 199h, переменная DwordVar (размером в двойное слово) содер-
жит значение 123456h.
Аналогично, если значение размеров в двойное слово хранится
по адресу 5, то биты 7-0 хранятся по адресу 5, биты 15-8 хранятся
по адресу 6, биты 23-16 - по адресу 7, а биты 31-24 по адресу 8.
Это может показаться несколько странным, но именно так работают
все процессоры серии iAPx86.
Представление числовых значений
-----------------------------------------------------------------
Теперь, когда вы знаете о типах данных языка ассемблера,
возникает вопрос, как можно представлять значения? Легче всего
пользоваться десятичными значениями (то есть с основанием 10),
поскольку мы привыкли к ним в повседневной жизни. Определенно,
проще всего ввести:
mov cx,100 ; установить счетчик в значение 100
В самом деле, Турбо Ассемблер считает значения десятичными,
если вы не указали противное. С сожалению, десятичные значения во
многих случаях не совсем подходят для программирования на языке
Ассемблера, так как компьютеры представляют собой двоичные уст-
ройства (используют основание 2).
Тогда логично использовать в программах на Ассемблере двоич-
ное представление. Вы можете указать Турбо Ассемблеру, что число
выражено в двоичном виде, если просто поместите в конце числа
букву b (конечно, при этом число должно состоять только из 0 и 1,
поскольку это единственные две цифры, допустимые в двоичном
представлении). Например, десятичное значение 5 выражается в дво-
ичном виде, как 101b.
Проблема при десятичном представлении состоит в том, что
числа с основанием 2 слишком длинные и ими трудно пользоваться.
Это вызвано тем, что в каждой двоичное цифре может храниться
только два значения - 0 или 1. Это показано в следующей таблице:
-----------------------------------------------------------------
Десятичное Двоичное Восьмеричное Шестнадцатиричное
-----------------------------------------------------------------
0 0 0 0
1 1 1 1
2 10 2 2
3 11 3 3
4 100 4 4
5 101 5 5
6 110 6 6
7 111 7 7
8 1000 10 8
9 1001 11 9
10 1010 12 A
11 1011 13 B
12 1100 14 C
13 1101 15 D
14 1110 16 E
15 1111 17 F
16 10000 20 10
17 10001 21 11
18 10010 22 12
19 10011 23 13
20 10100 24 14
21 10101 25 15
22 10110 26 16
23 10111 27 17
24 11000 30 18
25 11001 31 19
26 11010 32 1A
. . . .
. . . .
256 1000000000 400 100
. . . .
. . . .
4096 1000000000000 10000 1000
. . . .
. . . .
65536 1000000000000000 200000 10000
-----------------------------------------------------------------
Например, перепишем последнюю инструкцию с операндами в дво-
ичном представлении:
mov cx,1100100b ; установить счетчик в
; значение 100
Двоичные значения размером в слово и двойное слово еще труд-
нее читать и использовать.
Если вы еще не знакомы с этими представлениями, мы настоя-
тельно рекомендуем вам раздобыть хорошую книгу на эту тему, так
как двоичная, восьмеричная и шестнадцатиричная запись - это один
из основных элементов в программировании на Ассемблере.
Два из этих представлений, восьмеричное и шестнадцатиричное,
не только хорошо соответствуют двоичной природе компьютера, но и
довольно компактны.
В восьмеричном представлении, или представлении с основанием
8, используются цифры от 0 до 7 (3 бита на цифру). На Рис. 5.5
показано, каким образом биты двоичного значения 001100100b можно
объединить в группы по три бита, чтобы образовать восьмеричное
значение 144o.
Двоичное 001 100 100
| | | | | |
------- ------- -------
| | |
--------- | ---------
| | |
v v v
Восьмиричное 1 4 4
Рис. 5.5 Преобразование двоичного значения 001100100 (деся-
тичное 100) в восьмеричное значение 144.
В итоге можно видеть, что восьмеричное число втрое короче
его двоичного эквивалента. В восьмеричном виде последний пример
принимает вид:
mov cx,144o ; установить счетчик в значение 100
Заметим, что суффикс o указывает на восьмеричную запись.
Можно также использовать суффикс q, который не так просто спутать
с нулем. Однако программисты, работающие на IBM PC, почти всегда
используют шестнадцатиричное (с основанием 16) представление, а
не восьмеричное.
Каждая шестнадцатиричная цифра может принимать одно из 16
значений. Перечислим их в шестнадцатиричном представлении:
0 1 2 3 4 5 6 7 8 9 A B C D E F 10 ...
После цифры 9 следуют шесть дополнительных шестнадцатиричных
цифр A - F. (Можно также использовать строчные буквы a - f.) Хотя
использование букв в качестве цифр может показаться странным, у
вас не выбора, так как вам нужно 16 цифр, а обычных цифр только
10. На Рис. 5.6 показано, как можно разбить на группы биты двоич-
ного числа 01100100b (100 в десятичном виде), чтобы образовать
шестнадцатиричное значение 64h.
Двоичное 0110 0100
| | | |
-------- --------
| |
--------- ---------
| |
v v
Шестнадцатиричное 6 4
Рис. 5.6 Преобразование двоичного значения 001100100 (деся-
тичное 100) в шестнадцатиричное значение 64.
Как показано на Рис. 4.6, в шестнадцатиричном представлении
значения имеют вид "4 бита на цифру". В итоге шестнадцатиричные
значения имеет длину, равную только 1/4 длины их двоичных эквива-
лентов. Фактически, смещение другого значения размером в слово
можно выразить с помощью четырех шестнадцатиричных цифр. В шест-
надцатиричном виде последний пример принимает вид:
mov cx,64h ; установить счетчик в значение 100
Шестнадцатиричные числа обозначаются суффиксом h. Кроме то-
го, шестнадцатиричные числа должны начинаться с одной из цифр
0-9, так как шестнадцатиричное число типа BAD4h может ошибочно
интерпретироваться, как метка. Приведем пример, к котором исполь-
зуется как число 0BAD4h, так и метка BAD4h:
.
.
.
.DATA
BAD4h DW 0 ; метка BAD4h
.
.
.
.CODE
mov ax,0BAD4h ; загрузить в AX
; шестнадцатиричную
; константу (первый 0
; показывает, что это
; константа
.
.
.
mov ax,BAD4h ; загрузить AX из
; переменной в памяти
; BAD4h (отсутствие
; 0 в качестве первого
; символа показывает,
; что это метка
.
.
.
В общем случае постоянным числовым значением может быть
только операнд, начинающийся с цифр 0-9.
Числа с плавающей точкой могут обозначаться двумя способами.
Во-первых, вы можете задавать значение с плавающей точкой в виде
"мантисса/экспонента", например:
1.1
-12.45
1.0E12
252.123E-6
Турбо Ассемблер преобразует форму "мантисса/экспонента" в
двоичный вид, при котором соблюдается формат с плавающей точкой.
Если хотите, вы можете задавать значения с плавающей точкой не-
посредственно в формате IEEE или двоичном формате фирмы
Microsoft, задав число в шестнадцатиричном виде и поместив к кон-
це его суффикс r.
Вещественные числа могут использоваться только в директивах
DD, DQ и DT (которые мы обсудим позднее). Если вы выберите ис-
пользование суффикса r, то вы должны точно определить максималь-
ное число шестнадцатиричных цифр для инициализируемого типа дан-
ных (плюс предшествующий ноль, если это необходимо). Например:
DD 40000000r ; 2.0 (длина в точности равна 8)
DD 0C014CCCCCCCCCCCr ; -5.2 (длина 16, плюс предшест-
; вующий ноль)
DT 4037D529AE9E86000000r ; 1.2E17 (ровно 20 бит)
В общем случае значительно проще использовать форму "мантис-
са/экспонента".
Чтобы показать, что число является десятичным, в качестве
суффикса можно использовать букву d. Зачем же нужен этот суффикс,
если по умолчанию Турбо Ассемблер и так предполагает, что все
числа являются десятичными? Как вы можете догадаться, ответ сос-
тоит в том, что числа кроме десятичного могут иметь и другие
представления. Для этого служит директива .RADIX, о которой мы
потом кратко расскажем.
Наконец, могут использоваться символьные константы, при этом
символы заключаются в одиночные или двойные кавычки. Значением
символа является его значение кода ASCII. Например, все следующие
строки загружаются в регистр AL символ A:
mov al,65
mov al,41h
mov al,'A'
mov al,"A"
Где можно использовать значения в различном описанном выше
представлении? Двоичные, восьмиричные, десятичные и шестнадцати-
ричные значения могут использоваться там же, где можно использо-
вать константы, например:
mov ax,1001b
add cx,5bh
sub [Count],177o
and al,1
mov al,'A'
Значения с плавающей точкой могут использоваться только в
директивах DD, DQ и DT. Двоично-десятичные значения (BCD) можно
использовать только в в директиве DT.
Выбор основания по умолчанию
-----------------------------------------------------------------
Чаще всего вы, вероятно, захотите использовать по умолчанию
десятичные значения, просто потому, что это наиболее знакомое
представление. Однако, иногда удобно использовать числа без суф-
фиксов, в которых по умолчанию используется другое основание. В
этом случае необходима директива .RADIX.
Директива .RADIX выбирает основание, которое будет по умол-
чанию использоваться для спецификации чисел. Например, директива:
.RADIX 16
в качестве используемого по умолчанию выбирает основание 16
(шестнадцатиричное). Действие директивы .RADIX показано в следую-
щем фрагменте программы:
.
.
.
.RADIX 16 ; выбрать в качестве используемо-
; го по умолчанию основание 16
mov ax,100 ; =100h или 256 в десятичном
; виде
.RADIX 10 ; выбрать в качестве используемо-
; го по умолчанию основание 10
sub ax,100 ; -100 в десятичном виде,
; результат равен 256-100=156
; в десятичном виде
.RADIX 2 ; выбрать по умолчанию основание
; 2 (двоичное)
add ax,100 ; +100b (4 в десятичном виде)
; результат = 156+4=160 (дес.)
.
.
.
С помощью директивы .RADIX можно выбрать основание 2, 8, 10
или 16. Операнд директивы .RADIX всегда указывается в десятичном
виде, независимо от того, какое основание выбрано по умолчанию.
Другими словами одна директива .RADIX не влияет на операнд следу-
ющей директивы .RADIX.
При использовании директивы .RADIX может возникнуть потенци-
альная проблема. Независимо от выбранного по умолчанию основания
системы счисления подразумевается, что значения, задаваемые в ди-
рективах DD, DQ или DT - это десятичные значения (если не исполь-
зуется суффикс). Это значит, что в директиве:
.
.
.
.RADIX 16
DD 1E7
.
.
.
1E будет равно 1 * 10 в седьмой степени, а не 1Eh. Фактически, на
практике всегда лучше указывать во всех шестнадцатиричных значе-
ниях суффикс h (даже после директивы .RADIX 16). Почему? Вспомни-
те о том, что b и d допускается использовать в качестве суффик-
сов, что определяет соответственно двоичное и шестнадцатиричное
представление. К сожалению, b и d могут также использоваться в
качестве шестнадцатиричных цифр. Если действует директива .RADIX
16, как Турбо Ассемблер будет воспринимать числа 129D и 101B?
В этом случае Турбо Ассемблер всегда обращает внимание на
допустимые суффиксы, поэтому 129D - это 129 в десятичном виде, а
101B - это 101 в двоичном виде (или 5 в десятичном). Это означа-
ет, что даже при действии директивы .RADIX 16 все шестнадцатирич-
ные числа, заканчивающиеся на b и d, должны иметь суффикс h. Учи-
тывая это, проще всего указывать этот суффикс во всех
шестнадцатиричных числах. Отсюда ясно, что пользы от директивы
.RADIX 16 мало.
Инициализированные данные
-----------------------------------------------------------------
Теперь мы готовы к тому, чтобы рассмотреть способы, с по-
мощью которых в Турбо Ассемблере можно определять переменные. Да-
вайте сначала рассмотрим определение инициализированных данных.
Директивы определения данных DS, DW, DD, DF, DP, DQ и DT
позволяют вам определить переменные в памяти различного размера:
DW 1 байт
DW 2 байта = 1 слово
DD 4 байта = 1 двойное слово
DF, DP 6 байт = 1 указатель дальнего типа (386)
DQ 8 байт = одно четверное слово
DT 10 байт
Например:
.
.
.
.DATA
ByteVar DB 'Z' ; 1 байт
WordVar DW 101b ; 2 байта (1 слово)
DwordVar DD 2BFh ; 4 байта (1 двойное слово)
QWordVar DQ 307o ; 8 байт (1 четверное слово)
TWordVar DT 100 ; 10 байт
.
.
.
mov ah,2 ; функция DOS вывода на
; дисплей
mov dl,[ByteVar] ; символ, который нужно
; вывести на экран
int 21h
.
.
.
add ax,[WordVar]
.
.
.
add WORD PTR [DwordVar],ax
adc WORD PTR [DwordVar+2],dx
.
.
.
Здесь определяются и используются пять переменных памяти и
показывается, как некоторые из таких переменных можно использо-
вать.
Инициализация массивов
-----------------------------------------------------------------
В одной директиве определения данных может указываться нес-
колько значений. Например, директива:
SampleArray DW 0, 1, 2, 3, 4
создает массив из пяти элементов с именем SampleArray, элементы
которого имеют размер в слово (см. Рис. 5.7). В директивах опре-
деления данных можно использовать любое число значений, умещающе-
еся на строке.
. .
. .
| |
|-----------|
| ? |
SampleArray -------------> |-----------|
| 0 |
|-----------|
| 1 |
|-----------|
| 2 |
|-----------|
| 3 |
|-----------|
| 4 |
|-----------|
| ? |
|-----------|
| |
. .
. .
Рис. 5.7 Пример массива из пяти элементов.
Как быть в том случае, если вы хотите определить массив, ко-
торый слишком велик и не может уместиться на одной строке? Для
этого просто нужно добавить несколько строк. Метку в директиве
определения данных указывать необязательно. Например, по директи-
вам:
.
.
.
SquareArray DD 0, 1, 4, 9, 16
DD 25, 36, 49, 64, 81
DD 100, 121, 144, 169, 196
.
.
.
создается массив элементов размером в двойное слово с именем
SquareArray, состоящий из квадратов первых 15 целых чисел.
Турбо Ассемблер позволяет вам определить блок памяти, иници-
ализированный указанным значением, с помощью операции DUP. Напри-
мер:
BlankArray DW 100h DUP (0)
Здесь создается массив BlankArray, состоящий из 255 (десят.)
слов, инициализированных значением 0. Аналогично, директива:
ArrayOfA DB 92 DUP ('A')
создает массив из 92 байт, каждый из которых инициализирован сим-
волом A.
Инициализация строк символов
-----------------------------------------------------------------
Рассмотрим теперь создание строк символов. Символы представ-
ляют собой допустимые операнды директив определения данных, поэ-
тому строку символов можно определить следующим образом:
String DB 'A', 'B', 'C', 'D'
В Турбо Ассемблере в этом случае предусмотрена также удобная
сокращенная форма:
String DB 'ABCD'
Если вы хотите использовать вид строки, принятый в языке Си
(которая заканчивается нулевым байтом), вы должны явно указать в
конце строки нулевой байт. Аналогично, если вы хотите завершить
строку символами возврата каретки и перевода строки, их также
нужно включить в строку. В следующем примере определяется тексто-
вая строка, за которой следует символ возврата каретки, символ
перевода строки и нулевой байт:
HelloString DB 'Привет!',0dh,0ah,0
Чтобы переместиться к левому краю следующей строки, вы
должны вывести пару символов "возврат каретки/перевод строки".
Например, программа:
DOSSEG
.MODEL SMALL
.STACK 100h
.DATA
String1 DB 'Line1','$'
String2 DB 'Line2','$'
String3 DB 'Line3','$'
.CODE
ProgramStart:
mov ax,@data
mov ds,ax
mov ah,9 ; функция DOS печати строки
mov dx,OFFSET String1 ; печатаемая строка
int 21h ; вызвать DOS для печати строки
mov dx,OFFSET String2 ; печатаемая строка
int 21h ; вызвать DOS для печати строки
mov dx,OFFSET String3 ; печатаемая строка
int 21h ; вызвать DOS для печати строки
mov ah,4ch ; функция DOS завершения
; программы
int 21h
END ProgramStart
печатает следующее:
Line1Line2Line3
Однако если в конце каждой строки вы добавите пару символов
"возврат каретки/перевод строки":
String1 DB 'Line1','$',0dh,0ah,'$'
String2 DB 'Line2','$',0dh,0ah,'$'
String3 DB 'Line3','$',0dh,0ah,'$'
то вывод будет выглядеть следующим образом:
Line1
Line2
Line3
Инициализация выражений и меток
-----------------------------------------------------------------
Начальное значение инициализированной переменной должно
представлять собой константу, но это не обязательно должно быть
число. Можно также использовать выражения:
TestVar DW ((924/2)+1)
а также метки:
.
.
.
.DATA
Buffer DW 16 DUP (0)
BufferPointer DW Buffer
.
.
.
Когда в качестве операнда директивы определения данных ис-
пользуется метка, то используется значение самой метки, а не зна-
чение, записанное по этой метке. В последнем примере начальное
значение BufferPointer представляет собой смещение Buffer в сег-
менте .DATA, а не значение 0, которое хранится в переменной
Buffer (как если бы для инициализации BufferPointer использова-
лось OFFSET Buffer). Другими словами, с учетом такой инициализа-
ции BufferPointer, и инструкция:
mov ax,OFFSET Buffer
и инструкция:
mov ax,[BufferPointer]
загрузят в регистр AX одно и то же значение (смещение переменной
Buffer).
В выражениях определения данных допускается использовать
метки. Например, в следующем фрагменте программы переменная
WordArrayLength инициализируется значением длины (в байтах)
WordArray:
.
.
.
.DATA
WordArray DW 50 DUP (0)
WordArrayEnd LABEL WORD
WordArrayLength DW (WordArrayEnd - WordArray)
.
.
.
Если вы хотите вычислить длину переменной WordArray в сло-
вах, а не в байтах, это можно сделать, просто разделив длину в
байтах на 2:
WordArrayLength DW (WordArrayEnd - WordArray) / 2
Неинициализированные данные
-----------------------------------------------------------------
Иногда нет смысла присваивать переменной памяти начальное
значение. Предположим, например, что ваша программа считывает
следующие 10 набранных на клавиатуре символов и записывает их в
массив с именем KeyBuffer:
.
.
.
mov cx,10 ; число символов, которые
; требуется считать
mov bx,OFFSET KeyBuffer ; символы будут
; сохранены в KeyBuffer
GetKeyLoop:
mov ah,1 ; функция DOS ввода с
; клавиатуры
int 21h ; получить следующий
; нажатый символ
mov [bx],al ; сохранить символ
inc cx ; ссылка на ячейку
; памяти для следующего
; символа
loop GetKeyLoop
.
.
.
Инициализировать KeyBuffer можно следующим образом:
KeyBuffer DB 10 DUP (0)
но смысла в этом немного, так как начальные значения массива
KeyBuffer будут немедленно перезаписаны в цикле GetKeyLoop. Все,
что здесь в действительности нужно - это определить переменную в
памяти, как неинициализированную. Турбо Ассемблер предусматривает
такую возможность с помощью символа "знак вопроса" (?).
Вопросительный знак указывает Турбо Ассемблеру, что вы ре-
зервируете ячейку памяти, но не инициализируете ее. Например, в
последнем примере KeyBuffer можно определить так:
KeyBuffer DB 10 DUP (?)
В данной строке резервируется 10 байт памяти, начиная с мет-
ки Keybuffer, но этим байтам не присваивается никакого конкретно-
го значения.
Конечно, каждый раз, когда вы используете неинициализирован-
ную переменную в памяти, вы должны быть уверены в том, что вы
инициализируете ее в своей программе перед тем, как использовать
эту переменную. Например, было бы ошибочным использовать содержи-
мое KeyBuffer в последнем примере до того, как этот массив будет
заполнен, так как находящиеся в KeyBuffer начальные значения не-
определены.
Именованные ячейки памяти
-----------------------------------------------------------------
До сих пор мы видели только, как можно присваивать имена
ячейкам памяти с помощью метки, предшествующей директиве опреде-
ления данных (например, DB). Другой удобный способ присваивать
имя ячейке памяти предоставляет директива LABEL.
Директива LABEL позволяет вам определить как имя метки, так
и ее тип, не определяя при этом данные. Вот, например, еще один
способ, с помощью которого можно определить в предыдущем примере
массив KeyBuffer:
.
.
.
KeyBuffer LABEL BYTE
DB 10 DUP (?)
.
.
.
Типы меток, которые можно определить с помощью директивы
LABEL, включают в себя:
BYTE PWORD FAR
WORD QWORD PROC
DWORD TBYTE UNKNOWN
FWORD NEAR
Типы BYTE (байт), WORD (слово), DWORD (двойное слово),
PWORD, QWORD и TBYTE говорят сами за себя, определяя, соответст-
венно, 1-, 2-, 4-, 6-, 8- и 10-байтовые элементы данных. Приведем
пример инициализации переменной памяти, как пары байт, и обраще-
ния к ней, как к слову:
.
.
.
.DATA
WordVar LABEL WORD
DB 1,2
.
.
.
.CODE
.
.
.
mov ax[WordVar]
.
.
.
Когда эта программа выполняется, в регистр AL загружается 1
(первый байт WordVar), а в регистр AH - значение 2.
Ключевые слова NEAR и FAR используются в программе для выбо-
ра типа вызова или перехода, необходимого для достижения опреде-
ленной метки. Например, в программе:
.
.
.
.CODE
.
.
.
FarLabel LABEL FAR
NearLabel LABEL NEAR
mov ax,1
.
.
.
jmp FarLabel
.
.
.
jmp NearLabel
.
.
.
первая инструкция JMP представляет собой переход дальнего типа
(загружаются оба регистра CS и IP), так как это переход на метку
типа FAR, а второй переход имеет ближний тип (загружается только
регистр IP), так как это переход на метку типа NEAR. Заметим, что
обе метки FarLabel и NearLabel описывают один и тот же адрес (ад-
рес инструкции MOV), но позволяют вам переходить на него различ-
ными способами.
Когда вы используете упрощенные директивы определения сег-
ментов, то директива PROC - это удобный способ определить метку
нужного типа (ближнюю или дальнюю) для текущей модели кода. Когда
используется сверхмалая, малая или компактная модель памяти, то
LABEL PROC - это то же самое, что LABEL FAR. Это означает, что
если вы измените модель памяти, вы также можете автоматически из-
менить определенные метки.
Например, в программе:
DOSSEG
.MODEL small
.
.
.
.CODE
.
.
.
EntryPoint LABEL PROC
.
.
.
метка EntryPoint имеет ближний тип, но если вы измените модель
памяти на большую, то это будет метка дальнего типа. Обычно для
определения тех точек входа, которые вы хотели бы изменить при
изменении модели памяти вместо директивы LABEL используют дирек-
тиву PROC (см. далее раздел "Подпрограммы"), однако иногда требу-
ется иметь несколько точек входа в подпрограмму, и вам потребует-
ся директива LABEL.
Наконец, мы подошли к директиве LABEL UNKNOWN. Ключевое сло-
во UNKNOWN (неизвестно) - это просто способ указать, что вы не
уверены насчет типа данных используемой метки. Если вы знакомы с
языком Си, то UNKNOWN аналогично типу языка Си void. В качестве
примера UNKNOWN предположим, что у вас имеется переменная памяти
TempVar, к которой иногда нужно обращаться, как к байту, а иногда
- как к слову. С помощью LABEL UNKNOWN это делается в следующей
программе:
.
.
.
.DATA
TempVar LABEL UNKNOWN
DB ?,?
.
.
.
.CODE
.
.
.
mov [TampVar],ax
.
.
.
add di,[TempVar]
.
.
.
Перемещение данных
-----------------------------------------------------------------
Итак, вы уже получили довольно обширное представление о при-
роде языка Ассемблера, основных его концепциях и структуре прог-
рамм на Ассемблере. Теперь, когда вы усвоили основы, можно сосре-
доточить внимание на инструкциях языка Ассемблера, образующих ту
часть программы на Ассемблере, которая и указывает процессору
8086 на необходимость выполнения конкретных действий. Давайте
начнем с самой основной операции Ассемблера - перемещения данных.
Данные в процессоре 8086 перемещаются с помощью инструкции
MOV. В действительности MOV (от слова переместить) - это не сов-
сем удачное название данной инструкции. Более удачным было бы
название COPY (копировать), так как инструкция MOV на самом деле
записывает копию операнда-источника в операнд-приемник. Например,
инструкции:
.
.
.
mov ax,0
mov bx,9
mov ax,bx
.
.
.
сначала записывают в регистр AX константу 0, затем в регистр BX
записывается константа 9, и, наконец, содержимое BX копируется в
AX, как показано на следующей схеме:
После mov ax,0: -------------------------------
AX | 0 |
-------------------------------
-------------------------------
BX | ? |
-------------------------------
После mov bx,9: -------------------------------
AX | 0 |
-------------------------------
-------------------------------
BX | 9 |
-------------------------------
После mov ax,bx0: -------------------------------
AX | 9 |
-------------------------------
-------------------------------
BX | 9 |
-------------------------------
Заметим, что значение 9 не перемещается из BX в AX, оно
просто копируется из регистра BX в регистр AX.
В инструкции MOV можно использовать почти любую пару операн-
дов, что имеет смысл за исключением того случая, когда в качестве
операнда используется сегментный регистр (этот случай мы обсудим
далее в разделе "Обращение к сегментным регистрам"). В качестве
операнда-источника (правого операнда) инструкции MOV можно ис-
пользовать следующее: константу, выражение, при вычислении кото-
рого получается константа, общий регистр или ячейку памяти, дос-
тупную с помощью одного из режимов адресации, описанного в разде-
ле "Режимы адресации памяти". В качестве операнда-приемника (ле-
вого операнда) инструкции MOV может использоваться общий регистр
или ячейка памяти.
Выбор размера данных
-----------------------------------------------------------------
В языке Ассемблера с помощью инструкции MOV можно копировать
байты или значения размером в слово. Давайте рассмотрим, каким
образом Турбо Ассемблер определяет, с каким размером данных нужно
работать.
Во многих случаях операнды явным образом указывают Турбо Ас-
семблеру, каким должен быть размер данных. Если в инструкции ис-
пользуется регистр, то размер данных должен соответствовать раз-
меру этого регистра. Например, размер данных в следующих инструк-
циях ясен:
.
.
.
mov al,1 ; байт
mov dx,si ; слово
mov bx,[dl] ; слово
mov [bp+si+2],al ; байт
.
.
.
Аналогично, именованные ячейки памяти имеют заданные разме-
ры, поэтому размер данных в следующих инструкциях для Турбо Ас-
семблера ясен:
.
.
.
.DATA
TestChar DB ?
TempPointer DW TestChar
.
.
.
.CODE
.
.
.
mov [TestChar],'A'
mov [TempPointer],0
.
.
.
Однако, иногда приходится иметь дело с инструкциями MOV, в
которых размер данных не определен. Например, Турбо Ассемблер не
может сделать вывод о том, следует ли в следующей инструкции за-
писать значение размером в слово или в байт:
mov [bx],1
Фактически, Турбо Ассемблер не будет знать, как такую инст-
рукцию нужно ассемблировать. Было бы удобно иметь возможность
временного доступа к переменой размером в слово, как к байту, и
наоборот.
Турбо Ассемблер дает вам способ гибкого определения размера
данных в виде операций WORD PTR и BYTE PTR. Операция WORD PTR
указывает Турбо Ассемблеру, что данный операнд в памяти нужно ин-
терпретировать, как операнд размером в слово, а операция BYTE PTR
указывает Турбо Ассемблеру, что данный операнд в памяти нужно ин-
терпретировать, как операнд размером в байт, независимо от его
предопределенного размера. Например, можно сделать так, что в
последнем примере значение 1 размером в слово будет записываться
в слово, на которое указывает регистр BX:
mov WORD PTR [bx],1
или же можно сделать так, что в данном примере значение 1 разме-
ром в байт будет записываться в байт, на который указывает ре-
гистр BX:
mov BYTE PTR [bx],1
Заметим, что операции WORD PTR и BYTE PTR, если их применять
к регистрам, не имеют смысла, так как регистры всегда имеют фик-
сированный размер. В таком случае операции BYTE PTR и WORD PTR
игнорируются. Аналогично, эти операции игнорируются при примене-
нии к константам, поскольку они всегда имеют тот же размер, что и
операнд-приемник.
Операции WORD PTR и BYTE PTR имеют другое назначение: их
можно использовать для временного выбора размера данных для име-
нованной переменной в памяти. Почему это может оказаться
полезным? Рассмотрим следующий пример:
.
.
.
.DATA
Source1 DD 12345h
Source2 DD 54321h
Sum DD ?
.
.
.
.CODE
.
.
.
mov ax,WORD PTR [Source1] ; получить младшее
; слово Source1
mov dx,WORD PTR [Source1+2] ; получить старшее
; слово Source1
add ax,WORD PTR [Source2] ; прибавить к Source2
; младшее слово
adc dx,WORD PTR [Source2+2] ; прибавить к Source2
; старшее слово
mov WORD PTR [Sum],ax ; сохранить младшее
; слово суммы
mov WORD PTR [Sum+2],dx ; сохранить старшее
; слово суммы
.
.
.
Все переменные, которые используются в данном примере,
представляют собой длинные целые или двойные слова. Однако, про-
цессор 8086 не может выполнять сложение двойных слов непосредс-
твенно, поэтому такое сложение приходится разбивать на ряд опера-
ций со словами. Операция WORD PTR позволяет обращаться к частям
переменных Source1, Source2 и Sum, как к словам, хотя сами эти
переменные представляют собой двойные слова.
Операции FAR PTR и NEAR PTR хотя и не влияют непосредственно
на размер данных, они аналогичны операциям WORD PTR и BYTE PTR.
Операция FAR PTR приводит к тому, что целевая метка инструкции
перехода или вызова будет интерпретироваться, как дальняя метка,
и при этом будут загружаться оба регистра CS и IP. С другой сто-
роны, операция NEAR PTR вынуждает интерпретировать соответствую-
щую метку, как метку ближнего типа, переход на которую осуще-
ствляется путем загрузки одного регистра IP.
Данные со знаком и без знака
-----------------------------------------------------------------
И числа со знаком, и беззнаковые числа состоят из последова-
тельности двоичных цифр. Ответственность за различие этих двух
видов чисел возлагается на программиста, который пишет программу
на Ассемблере (то есть на вас), а не на процессор 8086. Например,
значение 0FFFFh может представлять собой либо 65535, либо -1, в
зависимости от того, как ваша программа его интерпретирует.
Откуда вы знаете, что 0FFFFh - это -1? Прибавьте к нему 1:
.
.
.
mov ax,0ffffh
add ax,1
.
.
.
и вы обнаружите, что результат будет равен 0. Как раз такой ре-
зультат должен получиться при сложении -1 и 1.
Одна и та же инструкция ADD будет работать одинаково хорошо,
независимо от того, представляют ли собой операнды значения со
знаком или беззнаковые значения. Предположим, например, что вы
вычли из 0FFFFh значение -1 следующим образом:
.
.
.
mov ax,offffh
sub ax,1
.
.
.
Результат при этом был бы равен 0FFFEh, что представляет со-
бой 65534 (беззнаковое число) или -2 (число со знаком).
Если это кажется непонятным, прочитайте одну из книг, реко-
мендуемых в конце данного руководства (или одну из книг по Ас-
семблеру, изданных в СССР, например книгу Бредли). Это позволит
вам больше узнать об арифметике с дополнением до двух - средстве,
с помощью которого процессор 8086 обрабатывает числа со знаком. К
сожалению, мы не располагаем здесь местом, чтобы подробно расска-
зать об арифметике значений со знаком, хотя для программиста это
представляет собой одну из важных тем, которую нужно хорошо пони-
мать. Пока же запомните, что инструкции ADD, SUB, ADC и SBB рабо-
тают одинаково хорошо как с беззнаковыми значениями, так и со
значениями со знаком, поэтому для таких операций не требуется
специальных инструкций сложения или сочетания. Знак имеет значе-
ние в операциях умножения или деления (как вы увидите далее), а
также при преобразовании размеров данных.
TASM2 #1-5/Док = 204 =
Преобразование размеров данных
-----------------------------------------------------------------
Иногда бывает необходимо преобразовать слова в байты или на-
оборот. При этом, как и в других действиях, значения могут быть
со знаком или без знака.
Давайте сначала рассмотрим преобразование слова в байт. Это
довольно просто: нужно только избавиться от старшего байта слова.
Например:
.
.
.
mov ax,5
mov bl,al
.
.
.
Здесь значение 5 размером в слово в регистре AX преобразует-
ся в байтовое значение 5 в регистре BL. Конечно, вы должны быть
уверены, что преобразуемое вами значение поместится в байте. По-
пытка преобразовать в байт значение 100h с помощью инструкций:
.
.
.
mov dx,100h
mov al,dl
.
.
.
была бы безуспешной, так как в регистр AL был бы записан только
младший (нулевой) байт.
Преобразование беззнакового байтового значения в слово зак-
лючается просто в обнулении старшего байта слова. Например, инс-
трукции:
.
.
.
mov cl,12
mov al,cl
mov ah,0
.
.
.
преобразуют беззнаковое значение 12 в регистре CL в значение 12
размером в слово в регистре AX.
Преобразование в слово байтового значения со знаком несколь-
ко более сложно, поэтому в процессоре 8086 для выполнения этой
задачи предусмотрена специальная инструкция CBW. Инструкция CBW
преобразует байтовое значение со знаком в регистре AL в значение
со знаком размером в слово в регистре AX. В следующем фрагменте
программы байтовое значение со знаком -1 в регистре DH преобразу-
ется в значение со знаком размером в слово в регистре DX (-1):
.
.
.
mov dh,-1
mov al,dh
cbw
mov dx,ax
.
.
.
В наборе инструкций процессора 8086 для преобразования слова
со знаком в регистре AX в двойное слово со знаком в регистрах DX:
AX (старшее слово содержится в регистре AX) предусмотрена специ-
альная инструкция CWD. Следующие инструкции преобразуют значение
со знаком +10000 (размером в слово), содержащееся в регистре AX,
в значение со знаком +10000 (размером в двойное слово), содержа-
щееся в паре регистров DX:AX:
.
.
.
mov ax,10000
cwd
.
.
.
Беззнаковые значения размером в слово можно преобразовать в
беззнаковые значения размером в двойное слово путем обнуления
старшего слова значения.
Доступ к сегментным регистрам
-----------------------------------------------------------------
Хотя для перемещения значений в сегментные регистры и из них
можно использовать инструкцию MOV, это особый случай, более огра-
ниченный, чем другие случаи использования инструкции MOV. Если
одним из операндов инструкции MOV является сегментный регистр, то
другим операндом должен быть регистр общего назначения или ячейка
памяти. Загрузить константу в сегментный регистр непосредственно
невозможно, и невозможно непосредственно скопировать один сегмен-
тный регистр в другой сегментный регистр.
Так как имена сегментов являются константами, необходимо
загружать сегментные регистры таким же образом, как общие регист-
ры или переменную в памяти. Вот, например, два способа установки
регистра ES в значение сегмента .DATA:
.
.
.
.DATA
DataSeg DW @Data
.
.
.
.CODE
.
.
.
mov ax,@Data
mov es,ax
.
.
.
mov ex,[DataSeg]
.
.
.
Вместо этого хотелось бы сделать следующее:
mov es,@Data ; недопустимо!
но это работать не будет.
Чтобы скопировать содержимое одного сегментного регистра в
другой сегментный регистр, вам придется передать значение через
регистр общего назначения или память. Инструкции:
.
.
.
mov ax,cs
mov ds,ax
.
.
.
и
.
.
.
push cs
pop ds
.
.
.
копируют содержимое регистра CS в DS. Первый метод работает быст-
рее, но при втором методе требуется меньший объем кода.
Не удивляйтесь, при работе с инструкцией MOV вы сталкивае-
тесь с ограничениями, когда дело касается сегментных регистров,
ведь в большинстве инструкций сегментные регистры вовсе не допус-
кается использовать в качестве операндов. Сегментные регистры
можно заносить в стек и извлекать из стека, но этим дело и огра-
ничивается. В операциях сложения, вычитания, логических операциях
или сравнениях их использовать нельзя.
Перемещение данных в стек и из стека
-----------------------------------------------------------------
Со стеком (областью памяти в сегменте стека, работающей по
дисциплине FIFO - "первым-пришел-первым-ушел") вы уже встреча-
лись. На вершину стека всегда указывает регистр SP. Для обращения
к данным в стеке, с использованием режимов адресации памяти, при
которых указателем базы является регистр BP, можно использовать
инструкцию MOV. Например, инструкция:
mov ax,[bp+4]
загружает регистр AX содержимым слова в сегменте стека со смеще-
нием BP+4 (доступ к стеку через регистр BP описывается в Главе
2).
Однако чаще к стеку обращаются с помощью инструкций PUSH и
POP. Инструкция PUSH сохраняет операнд в вершине стека, а инст-
рукция POP извлекает значение из вершины стека и сохраняет его в
операнде. Например, инструкции:
.
.
.
mov ax,1
push ax
pop bx
.
.
.
заносят значение (равное 1) в регистре AX в вершину стека, затем
извлекают 1 из вершины стека и сохраняют ее в BX.
Обмен данными
-----------------------------------------------------------------
Выполнять обмен содержимого двух операндов позволяет инст-
рукция XCHG. Это предоставляет удобный способ выполнять операцию,
которая в противном случае потребовала бы трех инструкций. Напри-
мер, инструкция:
xchg ax,dx
выполняет обмен содержимого AX и DX, что эквивалентно выполнению
инструкций:
.
.
.
push ax
mov ax,dx
pop dx
.
.
.
Ввод-вывод
-----------------------------------------------------------------
До сих пор мы обсуждали перемещение данных между константа-
ми, регистрами и адресным пространством процессора 8086. Как вы
можете вспомнить, в процессоре 8086 имеется также второе, незави-
симое адресное пространство, которое называется пространством ад-
ресов ввода-вывода. В общем случае в качестве каналов управления
и обмена данными таких устройств, как дисководы, дисплейные адап-
теры, принтеры и клавиатура, могут использоваться 65536 адресов
ввода-вывода или портов.
Большинство инструкций процессора 8086, включая инструкцию
MOV, имеют доступ только к операндам в пространстве адресов памя-
ти. Обращаться к портам ввода-вывода могут только две инструкции
- IN и OUT.
Инструкция IN копирует содержимое из указанного порта вво-
да-вывода в регистр AL или AX. Адрес порта ввода-вывода, указыва-
емый в качестве источника, можно выбрать одним из двух способов.
Если адрес порта меньше 256 (100h), вы можете указать его в инст-
рукции, например:
in al,41h
Эта инструкция копирует байт из порта ввода-вывода 41h в ре-
гистр AL.
При втором способе вы можете использовать для ссылки на порт
ввода-вывода, из которого нужно выполнить чтение, регистр DX:
.
.
.
mov dx,41h
in al,dx
.
.
.
Для чего регистр DX используется в качестве указателя порта
ввода-вывода? Во-первых, если адрес порта ввода-вывода превышает
255, вы должны использовать DX. Во-вторых, использование регистра
DX позволяет при адресации к портам ввода-вывода получить большую
гибкость. Например, указатель на порт ввода-вывода можно передать
подпрограмме, загрузив его в регистр DX.
Пусть вас не введет в заблуждение синтаксис инструкции IN -
регистры AL и AX являются единственно возможными операндами-при-
емниками. Аналогично, единственными допустимыми операндами-источ-
никами являются регистр DX и значение-константа, меньшая 255.
Поэтому, как бы вам этого ни хотелось, использовать инструкции
типа:
in bh,si
недопустимо.
Инструкция OUT в точности эквивалентна инструкции IN, только
операндом-источником является регистр AL или AX, а порт ввода-вы-
вода, на который указывает регистр DX или постоянное значение,
меньшее 256, является операндом-приемником. Например, следующие
инструкции устанавливают порт ввода-вывода 3B4h в значение 0Fh:
.
.
.
mov dx,3b4h
mov al,0fh
out dx,al
.
.
.
Операции
-----------------------------------------------------------------
Перемещение данных - это, конечно, важная функция, поскольку
компьютер тратит существенную часть своего времени на перемещение
данных из одного места в другое. Однако в равной степени важно
иметь возможность манипулировать данными, выполняя над ними ариф-
метические и логические операции. Поэтому далее мы рассмотрим
арифметические и логические операции, поддерживаемые процессором
8086.
Арифметические операции
-----------------------------------------------------------------
Даже если ваш компьютер РС и не тратит все время на работу с
числами и вычислительные операции, вы знаете, что он может это
сделать, если вам это потребуется. Кроме того, на компьютере РС
может работать множество электронных таблиц, программ баз данных
и инженерных пакетов. Если принять все это во внимание, то стано-
вится достаточно очевидным, что компьютер IBM PС должен обладать
мощными вычислительными способностями.
И это в самом деле так. Однако, хотя работающее на процессо-
ре 8086 программное обеспечение может прекрасно выполнять матема-
тические действия, сам процессор 8086 обеспечивает на удивление
простые арифметические возможности. В процессоре 8086 отсутствуют
инструкции для выполнения арифметических операций с плавающей
точкой (арифметических действий с такими числами, как 5.2 и
1.03Е17), не говоря уже о тригонометрических функциях. Эти опера-
ции выполняются арифметическим сопроцессором 8087. Это не означа-
ет, что программы, работающие на процессоре 8086, не могут выпол-
нять арифметические операции над числами с плавающей точкой. Ведь
электронные таблицы могут работать и на компьютерах РС без сопро-
цессора 8087. Однако программы процессора 8086 выполняют арифме-
тические операции над числами с плавающей точкой медленнее, с по-
мощью последовательности инструкций сдвигов, сложений и проверок,
а не с помощью одной быстро выполняющейся инструкции, как в соп-
роцессоре 8087.
Кроме того, в процессоре 8086 не предусмотрено арифметичес-
ких и логических инструкций, которые могут непосредственно рабо-
тать с операндами, размер которых превышает 16 бит.
В чем же тогда заключается встроенная поддержка арифметичес-
ких операций процессора 8086? Процессор 8086 может выполнять 8- и
16-битовое сложение, вычитание, умножение и деление чисел со зна-
ком и без знака и имеет специальные быстрые инструкции для увели-
чения и уменьшения операндов. В процессоре 8086 предусмотрена
также поддержка операций сложения и вычитания значений, превышаю-
щих 16 бит, хотя для операций с такими значениями требуется нес-
колько инструкций.
Сложение и вычитание
-----------------------------------------------------------------
Во многих примерах программ мы уже встречались с инструкция-
ми ADD (сложение) и SUB (вычитание). Их действие соответствует
названию. Инструкция ADD выполняет сложение операнда-источника
(правого операнда) с содержимым операнда-приемника и записывает
результат в операнд-приемник. Инструкция SUB делает тоже самое,
только она вычитает операнд-источник из операнда-приемника.
Например, инструкции:
.
.
.
.DATA
BaseVal DW 99
Adjust DW 10
.
.
.
.CODE
.
.
.
mov dx,[BaseVal]
add dx,11
sub dx,[Adjust]
.
.
.
сначала загружают значение, записанное в BaseVal, в регистр DX,
затем прибавляют к нему константу 11 (в результате в DX получает-
ся значение 110) и, наконец, вычитают из DX значение 10, записан-
ное в переменной Adjust. Полученное в результате значение 100
сохраняется в регистре DX.
32-разрядные операнды
-----------------------------------------------------------------
Операции ADD и SUB работают с 8- или 16-битовыми операндами.
Если вы, к примеру, хотите сложить или вычесть 32-разрядные опе-
ранда, вы должны разбить операцию на ряд операций со значениями
размером в слово и использовать инструкции ADC и SBB.
Когда вы складываете два операнда, процессор 8086 записывает
состояние во флаг переноса (бит С в регистре флагов), которое по-
казывает, был ли выполнен перенос из приемника. Вы знакомы с
принципом переноса в десятичной арифметике: если вы складываете
90 и 10, то получаете перенос в третью цифру (разряд).
90
+ 10
----
100
Рассмотрим теперь сложение двух шестнадцатиричных значений:
FFFF
+ 1
-----
10000
Младшее слово результата равно нулю, перенос равен 1, пос-
кольку результат (10000h) не вмещается в 16 бит.
Инструкция ADC аналогична инструкции ADD, но в ней учитыва-
ется флаг переноса (предварительно установленный предыдущим сло-
жением). Всякий раз когда вы складываете два значения, превышаю-
щие по размеру слово, то младшие (менее значащие) слова нужно
сложить с помощью инструкции ADD, а остальные слова этих значений
- с помощью одной или нескольких инструкций ADC, последними скла-
дывая самые значащие слова. Например, следующие инструкции скла-
дывают значение в регистрах CX:BX, размером в двойное слово, со
значением, записанным в регистрах DX:AX:
.
.
.
add ax,bx
adc dx,cx
.
.
.
а в следующей группе инструкций выполняется сложение четверного
слова в переменной DoubleLong1 с четверным словом в переменной
DoubleLong2:
.
.
.
mov ax,[DoubleLong1]
add [DoubleLong2],ax
mov ax,[DoubleLong1+2]
adc [DoubleLong2+2],ax
mov ax,[DoubleLong1+4]
adc [DoubleLong1+4],ax
mov ax,[DoubleLong1+6]
adc [DoubleLong2+6],ax
.
.
.
Инструкция SBB работает по тому же принципу, что и инструк-
ция ADC. Когда инструкция SBB выполняет вычитание, в ней учитыва-
ется заем, произошедший в предыдущем вычитании. Например, следую-
щие инструкции вычитают значение, записанное в регистрах CX:BX,
из значения размером в двойное слово, записанного в регистрах
DX:AX:
.
.
.
sub ax,bx
sbb dx,cx
.
.
.
При работе с инструкциями ADC и SBB вы должны убедиться, что
флаг переноса не изменился с момента выполнения последнего сложе-
ния или вычитания, иначе состояние заема/переноса, хранящееся во
флаге переноса, будет потеряно. Например, в следующем фрагменте
программы сложение CX:BX с DX:AX выполняется некорректно:
.
.
.
add ax,bx ; сложить младшие слова
sub si,si ; очистить SI (флаг переноса
; сбрасывается в 0)
adc dx,cx ; сложить старшие слова...
; это будет работать некорректно,
; так как с момента последней
; операции сложения содержимое
; флага переноса потеряно
.
.
.
Увеличение и уменьшение
-----------------------------------------------------------------
Иногда в программе не Ассемблере требуется выполнить сложе-
ние, которое состоит просто в прибавлении к операнду значения 1.
Такая операция называется увеличением (инкрементацией). Аналогич-
но, из содержимого регистров и переменных в памяти иногда нужно
вычесть значение 1. Такая операция называется уменьшением (декре-
ментацией). Для таких операций, как изменение содержимого счетчи-
ка или продвижение регистров-указателей по памяти все операции
сложения и вычитания можно выполнять с помощью увеличения и
уменьшения.
Для выполнения таких часто требующихся действий в наборе
инструкций процессора 8086 предусмотрены две инструкции - INC
(увеличить) и DEC (уменьшить). Инструкция INC прибавляет к ре-
гистру или переменной в памяти 1, а инструкция DEC вычитает из
регистра или переменной в памяти 1.
Например, следующая программа заполняет 10-байтовый массив
TempArray числами 0, 1, 2, 3, 4, 5, 6, 7, 8, 9:
.
.
.
.DATA
TempArray DB 10 DUP (?)
FillCount DW ?
.
.
.
.CODE
.
.
.
mov al,0 ; первое значение,
; записываемое в TempArray
mov bx,OFFSET TempArray ; BX указывает на
; TempArray
mov [FillCount],10 ; число элементов,
; которыми нужно
; заполнить массив
FillTempArrayLoop:
mov [bx],al ; установить текущий
; элемент TempArray
inc bx ; ссылка на следующий
; элемент массива
; TempArray
inc al ; следующее записываемое
; значение
dec [FillCount] ; уменьшить счетчик
; числа заполняемых
; элементов
jnz FillTempArray ; обработать следующий
; элементе, если мы еще
; не заполнили все
; элементы
.
.
.
Почему предпочтительнее использовать инструкцию:
inc bx
а не инструкцию:
add bx,1
ведь делают они одно и то же? Это так, но в то время, как инст-
рукция ADD занимает 3 байта, инструкция INC занимает только 1
байт и выполняется быстрее. Фактически, более экономно выполнить
две операции INC, чем прибавить к регистру размером в слово зна-
чение 2 (увеличение и уменьшение регистров и переменных в памяти
размером в байт занимает 2 байта, что так же короче, чем сложение
и вычитание).
Короче говоря, инструкции INC и DEC - это наиболее эффектив-
ные инструкции, с помощью которых можно увеличивать и уменьшать
значения переменных в памяти и регистров. Их следует использовать
там, где это возможно.
Умножение и деление
-----------------------------------------------------------------
Процессор 8086 может выполнять отдельные типы операций умно-
жения и деления. Эта одна из сильных сторон процессора 8086, пос-
кольку во многих микропроцессорах вообще отсутствует непосредст-
венная поддержка операций умножения и деления, а эти операции до-
вольно сложно выполнить программным путем.
Инструкция MUL перемножает 8- или 16-битовые беззнаковые
сомножители, создавая 16- или 32-битовое произведение. Давайте
сначала рассмотрим умножение 8-битовых сомножителей.
При 8-битовом (8-разрядном) умножении один из операндов дол-
жен храниться в регистре AL, а другой может представлять собой
любой 8-битовый общий регистр или переменную памяти соответствую-
щего размера. Инструкция MUL всегда сохраняет 16-битовое произве-
дение в регистре AX. Например, во фрагменте программы:
.
.
.
mov al,25
mov dh,40
mul dh
.
.
.
AL умножается на DH, а результат (1000) помещается в регистр AX.
Заметим, что в инструкции MUL требуется указывать только один
операнд, другой сомножитель всегда храниться в регистре AL (или в
регистре AX в случае перемножения 16-битовых сомножителей).
Инструкция перемножения 16-битовых сомножителей работает
аналогично. Один из сомножителей должен храниться в регистре AX,
а другой может находиться в 16-разрядном общем регистре или в пе-
ременной памяти. 32-битовое произведение инструкция MUL помещает
в этом случае в регистры DX:AX, при этом младшие (менее значащие)
16 битов произведения записываются в регистр AX, а старшие (более
значащие) 16 бит - в регистр DX. Например, инструкции:
.
.
.
mov ax,1000
mul ax
.
.
.
загружают в регистр AX значение 1000, а затем возводят его в
квадрат, помещая результат (значение 1000000) в регистры DX:AX.
В отличие от сложения и вычитания, в операции умножения не
учитывается, являются ли сомножители операндами со знаком или без
знака, поэтому имеется вторая инструкция умножения IMUL для умно-
жения 8- и 16-битовых сомножителей со знаком. Если не принимать
во внимание, что перемножаются значения со знаком, инструкция
IMUL работает аналогично инструкции MUL. Например, при выполнении
инструкций:
.
.
.
mov al,-2
mov ah,10
imul ah
.
.
.
в регистре AX будет записано значение -20.
Процессор 8086 позволяет вам с определенными ограничениями
разделить 32-битовое значение на 16-битовое, или 16-битовое зна-
чение на 8-битовое. Давайте сначала рассмотрим деление
16-битового значения на 8-битовое.
При беззнаковом делении 16-битового значения на 8-битовое
делимое должно быть записано в регистре AX. 8-битовый делитель
может храниться в любом 8-битовом общем регистре или переменной в
памяти соответствующего размера. Инструкция DIV всегда записывает
8-битовое частное в регистр AL, а 8-битовый остаток - в AH. Нап-
ример, в результате выполнения инструкций:
.
.
.
mov ax,51
mov dl,10
div dl
.
.
.
результат 5 (51/10) будет записан в регистр AL, а остаток 1 (ос-
таток от деления 51/10) - в регистр AH.
Заметим, что частное представляет собой 8-битовое значение.
Это означает, что результат деления 16-битового операнда на
8-битовый операнд должен превышать 255. Если частное слишком ве-
лико, то генерируется прерывание 0 (прерывания по делению на 0).
Инструкции:
.
.
.
mov ax,0fffh
mov bl,1
div bl
.
.
.
генерируют прерывание по делению на 0 (как можно ожидать, преры-
вание по делению на 0 генерируется также, если 0 используется в
качестве делителя).
При делении 32-битового операнда на 16-битовый операнд дели-
мое должно записываться в регистрах DX:AX. 16-битовый делитель
может находиться в любом из 16-битовых регистров общего назначе-
ния или в переменной в памяти соответствующего размера. Например,
в результате выполнения инструкций:
.
.
.
mov ax,2
mov dx,1 ; загрузить в DX:AX 10002h
mov bx,10h
div bx
.
.
.
частное 1000h (результат деления 10002h на 10h) будет записано в
регистре AX, а 2 (остаток от деления) - в регистре DX.
Как и при умножении, при делении имеет значение, используют-
ся операнды со знаком или без знака. Для деления беззнаковых опе-
рандов используется операция DIV, а для деления операндов со зна-
ком - IDIV. Например, операции:
.
.
.
.DATA
TestDivisor DW 100
.
.
.
.CODE
.
.
.
mov ax,-667
cwd ; установить DX:AX в значение -667
idiv [TestDivisor]
.
.
.
сохраняют значение -6 в регистре AX и значение -67 в регистре DX.
Изменение знака
-----------------------------------------------------------------
Наконец, мы подошли к рассмотрению инструкции NEG, которая
позволяет вам изменить (инвертировать) знак содержимого общего
регистра или переменной в памяти. Например, после выполнения инс-
трукций:
.
.
.
mov ax,1 ; установить регистр AX в значение 1
neg ax ; отрицание AX, он становится
; равным -1
mov bx,ax ; скопировать содержимое AX в BX
neg bx ; отрицание BX, он становится равным 1
.
.
.
в регистре AX будет содержаться значение -1, а в регистре BX -
значение 1.
Логические операции
-----------------------------------------------------------------
Турбо Ассемблер поддерживает полный набор инструкций для вы-
полнения логических операций, включая инструкции AND (И), OR
(ИЛИ), XOR (исключающее ИЛИ) и NOT (НЕ). Эти инструкции могут
оказаться очень полезными при работе с отдельными битами слова
или байта, а также для выполнения операций булевой алгебры.
Результаты выполнения логических операций показаны в Таблице
5.1. Логическая инструкция выполняет поразрядные операции над би-
тами исходных операндов. Например, инструкция:
adn ax,dx
выполняет логическую операцию AND с битом 0 регистра AX и битом 0
регистра DX, затем ту же операцию с битами 1, 2 и т.д. до бита
15.
Выполнение логических инструкций процессора 8086 ADN, OR и XOR
Таблица 5.1
-----------------------------------------------------------------
| Исходный бит A | Исходный бит B | A AND B | A OR B | A XOR B |
|---------------------------------------------------------------|
| 0 | 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 | 1 |
| 1 | 0 | 0 | 1 | 1 |
| 1 | 1 | 1 | 1 | 0 |
-----------------------------------------------------------------
Инструкция AND комбинирует два операнда в соответствии с
правилами, показанными в Таблице 5.1, устанавливая каждый бит ре-
зультата (операнда-приемника) в 1 только в том случае, если оба
соответствующих бита операнда-источника равны 1. Инструкция AND
позволяет вам выделить отдельный бит или принудительно установить
его в значение 0. Например, инструкции:
.
.
.
mov dx,3dah
in al,dx
and al,1
.
.
.
выделяет бит 0 байта состояния цветного графического адаптера
(CGA). Эти инструкции оставляют регистр AL установленным в значе-
ние 1, если видеопамять адаптера CGA можно изменять, не вызывая
помех на экране ("снег"), и устанавливают его в нулевое значение
в противном случае.
Инструкция OR также комбинирует два операнда в соответствии
с правилами, приведенными в Таблице 5.1, устанавливая каждый бит
операнда-приемника в значение 1, если любой из соответствующих
бит операнда-источника равен 1. Инструкция OR позволяет вам при-
нудительно установить отдельные биты (или бит) в значение 1. Нап-
ример, инструкции:
.
.
.
mov ax,40h
mov ds,ax
mov bx,10h
or WORD PTR [bx],0030h
.
.
.
устанавливают биты 5 и 4 слова флагов аппаратуры базовой системы
ввода-вывода BIOS в значение 1. При этом BIOS будет поддерживать
монохромный дисплейный адаптер.
Инструкция XOR также комбинирует два операнда в соответствии
с правилами, приведенными в Таблице 5.1, устанавливая каждый бит
операнда-приемника в значение 1, только в том случае, один соот-
ветствующих бит операнда-источника равен 0, и в значение 1 в про-
тивном случае. Инструкция XOR позволяет вам "переключать" значе-
ния отдельных бит в байте. Например, инструкции:
.
.
.
mov al,01010101b
mov al,11110000b
.
.
.
устанавливают регистр AL в значение 10100101b или A5h. Когда для
регистра AL выполняется операция XOR со значением 11110000b
(0F0h), биты со значением 1 в 0F0h переключают значения соот-
ветствующих бит в регистре AL, а биты со значением 0 оставляют
соответствующие биты AL неизмененными.
Кстати, инструкция XOR дает удобный способ обнуления содер-
жимого регистра. Например, следующая инструкция устанавливает со-
держимое регистра AX в значение 0:
xor ax,ax
Наконец, инструкция NOT просто изменяет значение каждого
бита операнда на противоположное (как если бы над исходным опе-
рандом была выполнена операция XOR со значением 0FFh). Например:
.
.
.
mov bl,10110001b
not bl ; переключить BL в 01001110b
xor bl,0ffh ; переключить BL обратно в
; значение 10110001b
.
.
.
Сдвиги и циклические сдвиги
-----------------------------------------------------------------
В процессорах 8086 имеется множество способов, с помощью ко-
торых можно сдвигать биты регистра или переменной в памяти влево
или вправо. Простейшим из них является логический сдвиг.
Инструкция SHL (сдвиг влево, синоним - SAL) перемещает каж-
дый бит операнда-приемника на один разряд влево, по направлению к
самому значащему биту. На Рис. 5.8 показано, как значение
100010110b (96h или 150 в десятичном представлении), записанное в
AL, сдвигается влево с помощью инструкции SHL AL,1. В результате
получается значение 00101100b (24Ch или 44 в десятичном виде),
которое записывается обратно в регистр AL. Флаг переноса устанав-
ливается в значение 1.
AL
----------------------------------------------------------
| ----- ----- ----- ----- ----- ----- ----- ----- |
---| | 1 |<-| 0 |<-| 0 |<-| 1 |<-| 0 |<-| 1 |<-| 1 |<-| 0 | |<--
| | ----- ----- ----- ----- ----- ----- ----- ----- | |
| ---------------------------------------------------------- |
| Бит 7 6 5 4 3 2 1 0 |
| ----- |
---->| | 0 -----------
-----
Флаг переноса
Рис. 5.8 Пример выполнения сдвига влево.
Самый значащий (старший) бит вовсе сдвигается из операнда и
попадает во флаг переноса, а в наименее значащий бит заносится 0.
Для чего используется сдвиг влево? Чаще всего это делается
для выполнения с помощью операции SHL умножения на степень числа
2, так как каждая инструкция SHL умножает операнд на 2. Например,
с помощью следующих инструкций DX умножается на 16:
.
.
.
shl dx,1 ; DX * 2
shl dx,1 ; DX * 4
shl dx,1 ; DX * 8
shl dx,1 ; DX * 16
.
.
.
Умножение с помощью сдвига выполняется гораздо быстрее, чем
с помощью операции MUL.
Как вы могли заметить, в предыдущем примере в инструкции SHL
используется второй операнд - значение 1. Этот операнд указывает,
что содержимое DX нужно сдвинуть на 1 бит. К сожалению, процессор
8086 не поддерживает использования в качестве константы сдвига
других, отличных от 1 постоянных значений - 2, 3 и т.д. Однако, в
качестве счетчика сдвигов допускается использование регистра CL.
Например, инструкции:
.
.
.
mov cl,4
shl dx,cl
.
.
.
умножают содержимое регистра DX на 16 (как и в предыдущем приме-
ре).
Если есть сдвиг влево, то логично было бы предположить, что
существует также сдвиг вправо, и это так. Фактически, имеется
даже две операции сдвига вправо. Инструкция SHR (сдвиг вправо)
очень похожа на инструкцию SHL. Она выполняет сдвиг разрядов опе-
ранда вправо на 1 или CL бит, затем сдвигает наименее значащий
бит во флаг переноса и помещает 0 в самый значащий бит. Инструк-
ция SHR дает быстрый способ выполнения беззнакового деления на
степень числа 2.
Инструкция SAR (арифметический сдвиг вправо) аналогична
инструкции SHR, только при ее выполнении наиболее значащий бит
операнда сдвигается вправо в следующий бит, а затем копируется
обратно. На Рис. 5.9 показано, как значение 10010110b (96h или
-106 в десятичном представлении со знаком), записанное в регистре
AL, сдвигается вправо с помощью инструкции SAR AL,1. В результате
получается значение 11001011b (0CBh или -53 в десятичном предс-
тавлении со знаком), которое записывается обратно в регистр AL.
Флаг переноса устанавливается в значение 0.
AL
------------------------------------------------------------
| ------ |
| | | |
| V | |
| ----- | ----- ----- ----- ----- ----- ----- ----- |
| | 1 |<---| 0 |<-| 0 |<-| 1 |<-| 0 |<-| 1 |<-| 1 |<-| 0 | |<--
| ----- ----- ----- ----- ----- ----- ----- ----- | |
---------------------------------------------------------- |
Бит 7 6 5 4 3 2 1 0 |
----- |
| |-----
-----
Флаг переноса
Рис. 5.9 Пример выполнения инструкции SAR (арифметический
сдвиг вправо).
Таким образом, при выполнении данной инструкции сохраняется
знак операнда, поэтому инструкцию SAR полезно использовать для
выполнения деления со знаком на степень числа 2. Например, в ре-
зультате выполнения инструкций:
.
.
.
mov bx,-4
sar bx,1
.
.
.
в регистре BX будет записано значение -2.
В наборе инструкций процессора 8086 имеется также четыре
инструкции циклического сдвига: ROR, ROL, RCR и RCL. Инструкция
ROR аналогична инструкции SHR, но при ее выполнении наименее
значащий бит сдвигается в наиболее значащий бит, а также во флаг
переноса. На Рис. 5.10 показано, как значение 10010110b (96h или
159 в десятичном представлении), записанное в регистре AL,
циклически сдвигается вправо с помощью инструкции ROR AL,1. В
результате получается значение 01001011b (04Bh или 75 в
десятичном представлении), которое записывается обратно в регистр
AL. Флаг переноса устанавливается в значение 0.
AL
----------------------------------------------------------
| ----- ----- ----- ----- ----- ----- ----- ----- |
-->| | 1 |->| 0 |->| 0 |->| 1 |->| 0 |->| 1 |->| 1 |->| 0 | |---
| | ----- ----- ----- ----- ----- ----- ----- ----- | |
| ---------------------------------------------------------- |
| Бит 7 6 5 4 3 2 1 0 |
| |
---------------------------------------------------------------|
----- |
Флаг переноса | |<----
-----
Рис. 5.10 Пример выполнения операции ROR (циклический сдвиг
вправо).
Операция ROL имеет действие, обратное действию операции
ROR. Операнд также сдвигается циклически, но сдвиг выполняется
влево. При этом наиболее значащий бит сдвигается в наименее зна-
чащий. Инструкции ROR и ROL полезно использовать для переупоря-
дочивания бит в байте или в слове. Например, в результате выпол-
нения инструкций:
.
.
.
mov si,49F1h
mov cl,4
ror si,cl
.
.
.
в регистр SI будет записано значение 149Fh: биты 3-0 переместятся
в биты 15-12, биты 7-4 - в биты 3-0 и т.д.
Инструкции RCR и RCL работают несколько по-другому. Инструк-
ция RCR аналогично инструкции сдвига вправо, при этом наиболее
значащий бит сдвигается из флага переноса. На Рис. 5.11 показано,
как значение 10010110b (96h или 159 в десятичном представлении),
записанное в регистре AL, циклически сдвигается вправо через флаг
переноса, начальное значение которого равно 1, с помощью инструк-
ции RCR AL,1. В результате получается значение 11001011b (0CBh
или 203 в десятичном представлении), которое записывается обратно
в регистр AL. Флаг переноса устанавливается в значение 0.
AL
----------------------------------------------------------
| ----- ----- ----- ----- ----- ----- ----- ----- |
-->| | 1 |->| 0 |->| 0 |->| 1 |->| 0 |->| 1 |->| 1 |->| 0 | |---
| | ----- ----- ----- ----- ----- ----- ----- ----- | |
| ---------------------------------------------------------- |
| Бит 7 6 5 4 3 2 1 0 |
| ----- |
------------------------------------------------------| 1 |<----
-----
Флаг переноса
Рис. 5.11 Пример выполнения инструкции RCR (циклический
сдвиг вправо и перенос).
Инструкция RCL аналогично, соответственно, левому сдвигу.
При выполнении этой инструкции наименее значащий бит сдвигается
из флага переноса. Инструкции RCR и RCL полезно использовать для
сдвига операнда, состоящего из нескольких слов. Например, следую-
щие инструкции выполняют умножение значения в DX:AX, размером в
двойное слово, на 4:
.
.
.
shl ax,1 ; бит 15 регистра AX сдвигается во
; флаг переноса
rcl dx,1 ; флаг переноса сдвигается в бит 0
; регистра DX
shl ax,1 ; бит 15 регистра AX сдвигается во
; флаг переноса
rcl dx,1 ; флаг переноса сдвигается в бит 0
; регистра DX
.
.
.
Инструкции циклического сдвига, аналогично инструкциям
сдвига, могут сдвигать операнд на 1 бит или на число бит, задан-
ных регистром CL.
Циклы и переходы
-----------------------------------------------------------------
Да этого момента мы рассматривали выполнении процессором
8086 инструкций в строгой последовательности. При этом каждая
следующая инструкция выполнялась сразу после инструкции по преды-
дущему адресу. Рассматривая программу:
.
.
.
mov ax,[BaseCount]
.
.
.
push ax
.
.
.
мы могли быть совершенно уверены, что инструкция ADD выполнится
непосредственно после инструкции MOV, а несколько позднее будет
выполнена инструкция PUSH.
Если бы этим ограничивалось все, на что способен процессор
8086, то такой компьютер был бы довольно примитивен. Фундамен-
тальным свойством любого полезного компьютера является наличие
инструкций, которые могут выполнять в программе переход (ветвле-
ние) на инструкцию, отличную от следующей инструкции в памяти. В
такой же степени важна возможность выполнения условного перехода,
в зависимости от состояния или результата операции.
Набор инструкций процессора 8086 содержит инструкции для
обоих видов переходов. Кроме того, предусмотрены специальные
инструкции переходов для обеспечения повторяющейся обработки бло-
ка кода.
Безусловные переходы
-----------------------------------------------------------------
Основной инструкцией перехода в наборе инструкций процессора
8086 является инструкция JMP. Эта инструкция указывает процессору
8086, что в качестве следующей за JMP инструкцией нужно выполнить
инструкцию по целевой метке. Например, после завершения выполне-
ния фрагмента программы:
.
.
.
mov ax,1
jmp AddTwoToAX
AddOneToAx:
inc ax
jmp AXIsSet
AddTwoToAX:
inc ax
AXIsSet:
.
.
.
регистр AX будет содержать значение 3, а инструкции ADD и JMP,
следующие за меткой AddOneToAX, никогда выполнены не будут. Здесь
инструкция:
jmp AddTwoToAX
указывает процессору 8086, что нужно установить указатель инст-
рукций IP в значение смещения метки AddTwoToAX, поэтому следующей
выполняемой инструкцией будет инструкция:
add ax,2
Иногда совместно с инструкцией JMP используется операция
SHORT. Для указания на целевую метку инструкция JMP обычно ис-
пользует 16-битовое смещение. Операция SHORT указывает Турбо Ас-
семблеру, что нужно использовать не 16-битовое, а 8-битовое сме-
щение (что позволяет сэкономить в инструкции JMP один байт). Нап-
ример, последний фрагмент программы можно переписать так, что он
станет на два байта короче:
.
.
.
mov ax,1
jmp SHORT AddTwoToAX
AddOneToAx:
inc ax
jmp SHORT AXIsSet
AddTwoToAX:
inc ax
AXIsSet:
.
.
.
Недостаток использования операции SHORT (короткий) состоит в
том, что короткие переходы могут осуществлять передачу управления
на метки, отстоящие от инструкции JMP не далее, чем на 128 бай-
тов, поэтому в некоторых случаях Турбо Ассемблер может сообщать
вам, что метка недостижима с помощью короткого перехода. К тому
же операцию SHORT имеет смысл использовать для ссылок вперед,
поскольку для переходов назад (на предшествующие метки) Турбо Ас-
семблер автоматически использует короткие переходы, если на метку
можно перейти с помощью короткого перехода, и длинные в противном
случае.
Инструкцию JMP можно использовать для перехода в другой сег-
мент кода, загружая в одной инструкции и регистр CS, и регистр
IP. Например, в программе:
.
.
.
CSeg1 SEGMENT
ASSUME CS:Cseg1
.
.
.
FarTarget LABEL FAR
.
.
.
CSeg1 ENDS
.
.
.
CSeg2 SEGMENT
ASSUME CS:CSeg2
.
.
.
jmp FarTarget ; переход дальнего типа
.
.
.
CSeg2 ENDS
.
.
.
выполняется переход дальнего типа.
Если вы хотите, чтобы метка принудительно интерпретирова-
лась, как метка дальнего типа, можно использовать операцию FAR
PTR. Например, во фрагменте программы:
.
.
.
jmp FAR PTR NearLabel
nop
NearLabel:
.
.
.
выполняется переход дальнего типа на метку NearLabel, хотя эта
метка находится в том же сегменте кода, что и инструкция JMP.
Наконец, вы можете выполнить переход по адресу, записанному
в регистре или в переменной памяти. Например:
.
.
.
mov ax,OFFSET TestLabel
jmp ax
.
.
.
TestLabel:
.
.
.
Здесь выполняется переход на метку TestLabel, так же, как и
в следующем фрагменте:
.
.
.
.DATA
JumpTarget DW TestLabel
.
.
.
.CODE
.
.
.
jmp [JumpTarget]
.
.
.
TestLabel:
.
.
.
Условные переходы
-----------------------------------------------------------------
Описанные в предыдущем разделе инструкции переходов - это
только часть того, что вам потребуется для написания полезных
программ. В действительности необходима возможность писать такие
программы, которые могут принимать решения. Именно это можно де-
лать с помощью операций условных переходов.
Инструкция условного перехода может осуществлять или нет
переход на целевую (указанную в ней) метку, в зависимости от сос-
тояния регистра флагов. Рассмотрим следующий пример:
.
.
.
mov ah,1 ; функция DOS ввода с клавиату-
; ры
int 21h ; получить следующую нажатую
; клавишу
cmp al,'A' ; была нажата буква "A"?
je AWasTyped ; да, обработать ее
mov [TampByte], al ; нет, сохранить символ
.
.
.
AWasTyped:
push ax ; сохранить символ в стеке
.
.
.
Сначала в данной программе с помощью функции операционной
системы DOS воспринимается нажатая клавиша. Затем для сравнения
введенного символа с символом A используется инструкция CMP. Эта
инструкция аналогична инструкции SUB, только ее выполнение ни на
что не влияет, поскольку назначение данной инструкции состоит в
том, чтобы можно было сравнить два операнда, установив флаги так
же, как это делается в инструкции SUB. Поэтому в предыдущем при-
мере флаг нуля устанавливается в значение 1 только в том случае,
если регистр AL содержит символ A.
Теперь мы подошли к основному моменту. Инструкция JE предс-
тавляет инструкцию условного перехода, которая. осуществляет пе-
редачу управления только в том случае, если флаг нуля равен 1. В
противном случае выполняется инструкция, непосредственно следую-
щая за инструкцией JE (в данном случае - инструкция MOV). Флаг
нуля в данном примере будет установлен только в случае нажатия
клавиши A, и только в этом случае процессор 8086 перейдет к вы-
полнению инструкции с меткой AWasTyped, то есть инструкции PUSH.
Набор инструкций процессора 8086 предусматривает большое
разнообразие инструкций условных переходов, что позволяет вам
осуществлять переход почти по любому флагу или их комбинации.
Можно осуществлять условный переход по состоянию нуля, переноса,
по знаку, четности или флагу переполнения и по комбинации фла-
гов, показывающих результаты операций чисел со знаками.
Перечень инструкций условных переходов приводится в Таблице
5.2.
Инструкции условных переходов Таблица 5.2
-----------------------------------------------------------------
Название Значение Проверяемые флаги
-----------------------------------------------------------------
JB/JNAE Перейти, если меньше / перейти, если CF = 1
не больше или равно
JAE/JNB Перейти, если больше или равно / пе- CF = 0
рейти, если не меньше
JBE/JNA Перейти, если меньше или равно / пе- CF = 1 или ZF = 1
рейти, если не больше
JA/JNBE Перейти, если больше / перейти, если CF = 0 и ZF = 0
не меньше или равно
JE/JZ Перейти, если равно ZF = 1
JNE/JNZ Перейти, если не равно ZF = 0
JL/JNGE Перейти, если меньше чем / перейти, SF = OF
если не больше чем или равно
JGE/JNL Перейти, если больше чем или равно / SF = OF
перейти, если не меньше чем
JLE/JNLE Перейти, если меньше чем или равно / ZF = 1 или SF = OF
перейти, если не больше, чем
JG/JNLE Перейти, если больше чем / перейти, ZF = 0 или SF = OF
если не меньше чем или равно
JP/JPE Перейти по четности PF = 1
JNP/JPO Перейти по нечетности PF = 0
JS Перейти по знаку SF = 1
JNS Перейти, если знак не установлен SF = 0
JC Перейти при наличии переноса CF = 1
JNC Перейти при отсутствии переноса CF = 0
JO Перейти по переполнению OF = 1
JNO Перейти при отсутствии переполнения OF = 0
-----------------------------------------------------------------
CF - флаг переноса, SF - флаг знака, OF - флаг переполне-
ния, ZF - флаг нуля, PF - флаг четности
Более подробная информация об инструкциях-синонимах и общие
сведения об инструкциях перехода содержатся в Главе 6. Там также
подробно рассказывается о способах, с помощью которых инструкции
процессора 8086 могут изменять регистр флагов.
Несмотря на свою гибкость, инструкции условного перехода
имеют также серьезные ограничения, поскольку переходы в них всег-
да короткие. Другими словами целевая метка, указанная в инструк-
ции условного перехода, должна отстоять от инструкции перехода не
более, чем на 128 байт. Например, Турбо Ассемблер не может ас-
семблировать:
.
.
.
JumpTarget:
.
.
.
DB 1000 DUP (?)
.
.
.
dec ax
jnz JumpTarget
.
.
.
так как метка JumpTarget отстоит от инструкции JNZ более чем на
1000 байт. В данном случае нужно сделать следующее:
.
.
.
JumpTarget:
.
.
.
DB 1000 DUP (?)
.
.
.
dec ax
jnz SkipJump
jmp JumpTarget
SkipJump:
.
.
.
где условный переход переход применяется для того, чтобы опреде-
лить, нужно ли выполнить длинный безусловные переход.
Циклы
-----------------------------------------------------------------
Одним из видов конструкций в программе, которые можно пост-
роить с помощью условных переходов, являются циклы. Цикл - это
просто-напросто блок кода, завершающийся условным переходом, бла-
годаря чему данных блок может выполняться повторно до достижения
условия завершения. Возможно, вам уже знакомы такие конструкции
циклов, как for и while в языке Си, while и repeat в Паскале и
FOR в Бейсике.
Для чего используются циклы? Они служат для работы с масси-
вами, проверки состояния портов ввода-вывода до получения опреде-
ленного состояния, очистки блоков памяти, чтения строк с клавиа-
туры и вывода их на экран и т.д. Циклы - это основное средство,
которое используется для выполнения повторяющихся действий. Поэ-
тому используются они довольно часто, настолько часто, что в на-
боре инструкций процессора 8086 предусмотрено фактически несколь-
ко инструкций циклов: LOOP, LOOPNE, LOOPE и JCXZ.
Давайте рассмотрим сначала инструкцию LOOP. Предположим, мы
хотим вывести 17 символов текстовой строки TestString. Это можно
сделать следующим образом:
.
.
.
.DATA
TestString DB 'Это проверка! ...'
.
.
.
.CODE
.
.
.
mov cx,17
mov bx,OFFSET TestString
PrintStringLoop:
mov dl,[bx] ; получить следующий
; символ
inc bx ; ссылка на следующий
; символ
mov ah,2 ; функция DOS вывода на
; экран
int 21h ; вызвать DOS для вывода
; символа
dec cx ; уменьшить счетчик длины
; строки
jnz PrintStringLoop ; обработать следующий
; символ, если он имеется
.
.
.
Есть, однако, лучший способ. Возможно, вы помните, что ранее
мы уже упоминали о том, что регистр CX весьма полезно бывает ис-
пользовать для организации циклов. Инструкция:
loop PrintStringLoop
делает то же, что и инструкции:
dec cx
jnz PrintStringLoop
однако выполняется она быстрее и занимает на один байт меньше.
Всякий раз, когда вам нужно организовать цикл, пока значение
счетчика не станет равным 0, запишите начальное значение счетчика
в регистр CX и используйте инструкцию LOOP.
Как же строятся циклы с более сложным условием завершения,
чем обратный отсчет значения счетчика? Для таких случаев предус-
мотрены инструкции LOOPE и LOOPNE.
Инструкция LOOPE работает также, как инструкция LOOP, только
цикл при ее выполнении будет завершаться (то есть перестанут вы-
полняться переходы), если регистр CX примет значение 0 или флаг
нуля будет установлен в значение 1 (нужно помнить о том, что флаг
нуля устанавливается в значение 1, если результат последней ариф-
метической операции был нулевым или два операнда в последней опе-
рации сравнения не совпадали). Аналогично, инструкция LOOPNE за-
вершает выполнение цикла, если регистр CX принял значение 0 или
флаг нуля сброшен (имеет нулевое значение).
Предположим, вы хотите повторять цикл, сохраняя коды нажатых
клавиш, пока не будет нажата клавиша ENTER или не будет накоплено
128 символов. Для выполнения такой работы можно написать такую
программу (где используется инструкция LOOPNE):
.
.
.
.DATA
KeyBuffer DB 128 DUP (?)
.
.
.
.CODE
.
.
.
mov cx,128
mov bx,OFFSET KeyBuffer
KeyLoop:
mov ah,1 ; функция DOS ввода с
; клавиатуры
int 21h ; считать следующую
; клавишу
mov [bx],al ; сохранить ее
inc bx ; установить указатель
; для следующей клавиши
cmp al,0dh ; это клавиша ENTER?
loopne KeyLoop ; если нет, то получить
; следующую клавишу, пока
; мы не достигнем максимально-
; го числа клавиш
.
.
.
Инструкция LOOPE известна также, как инструкция LOOPZ,
инструкция LOOPNE - как инструкция LOOPNZ, также как инструкции
JE эквивалентна инструкция JZ (это инструкции-синонимы).
Имеется еще одна инструкция цикла. Это инструкция JCXZ.
Инструкция JCXZ осуществляет переход только в том случае, если
значение регистра CX равно 0. Это дает удобный способ проверять
регистр CX перед началом цикла. Например, в следующем фрагменте
программы, при обращении к которому регистр BX указывает на блок
байт, которые требуется обнулить, инструкция JCXZ используется
для пропуска тела цикла в том случае, если регистр CX имеет зна-
чение 0:
.
.
.
jcxz SkipLoop ; если CX имеет значение 0, то
; ничего делать не надо
ClearLoop:
mov BYTE PTR [si],0 ; установить следующий байт в
; значение 0
inc si ; ссылка на следующий очищаемый
; байт
SkipLoop:
.
.
.
Почему желательно пропустить выполнение цикла, если значение
регистра CX равно 0? Потому что в противном случае значение CX
будет уменьшено до величины 0FFFFh и инструкция LOOP осуществит
переход на указанную метку. После этого цикл будет выполняться
65535 раз. Вы же хотели, чтобы значение регистра CX, равное 0,
указывало, что требуется обнулить 0 байт, а не 65536. Инструкция
JCXZ позволяет вам в этом случае быстро и эффективно выполнить
нужную проверку.
Относительно инструкций циклов можно сделать пару интересных
замечаний. Во-первых, нужно помнить о том, что инструкции циклов,
как и инструкции условных переходов, могут выполнять переход
только на метку, отстоящую от инструкции цикла не более чем на
128 байт в ту или другую сторону. Циклы, превышающие 128 байт,
требуют использования условных переходов с помощью безусловных
переходов (этот метод описан в предыдущем разделе "Условные пере-
ходы"). Во-вторых, важно понимать, что ни одна из инструкций цик-
лов не влияет на состояние флагов. Это означает, что инструкция:
loop LoopTop
не эквивалентна в точности инструкциям:
dec cx
jnz LoopTop
поскольку инструкция DEC изменяет флаги переполнения, знака, ну-
ля, дополнительного переноса и четности, а инструкция LOOP на
флаги не влияет. Кроме того, использование инструкции DEC не эк-
вивалентно варианту:
sub cx,1
jnz LoopTop
поскольку инструкция SUB влияет на флаг переноса, а инструкция
DEC - нет. Различия невелики, но при программировании на языке
Ассемблера важно понимать, какие именно флаги устанавливают те
или иные инструкции.
Подпрограммы
-----------------------------------------------------------------
До сих пор мы рассматривали только программы, представляющие
собой одну длинную последовательность исходного кода. Каждая
программа начиналась с раздела кода, выполняла поочередно каждую
инструкцию (иногда изменяя свой маршрут при выполнении циклов или
принятии решений), а затем завершалась в конце раздела кода. Все
это хорошо для небольших программ, но в больших программах требу-
ется использования таких конструкций, которые называются подпрог-
раммами.
Возможно вы уже знакомы с подпрограммами в языках высокого
уровня. В языке Си подпрограммы называются функциями, в Паскале и
Бейсике - процедурами и функциями. Подпрограммы, процедуры и фу-
нкции представляют собой, в общем, одно и то же - отдельную часть
кода, воспринимающую определенные входные данные, выполняющую оп-
ределенные действия и, возможно, возвращающую полученное в ре-
зультате значение.
Подпрограммы позволяют вам строить программы по модульному
принципу. При этом подпрограммы позволяют "скрывать" специфичес-
кие детали (то есть убирать их на нижний уровень) и сосредоточить
внимание на общем алгоритме программы. Подпрограммы позволяют
также сделать программы намного более компактными, поскольку от-
дельную подпрограмму можно вызывать во многих местах программы и
даже выполнять различные функции, передавая ей различные значе-
ния. В больших программах (независимо от того, написаны они на
Ассемблере, Паскале или Си) подпрограммы являются существенным
средством для создания упорядоченного и легко обслуживаемого ис-
ходного кода.
Выполнение подпрограмм
-----------------------------------------------------------------
Основные моменты выполнения подпрограммы иллюстрируются на
Рис. 5.12. В вызывающей подпрограмму программе выполняется инст-
рукция CALL, которая заносит адрес следующей инструкции в стек и
загружает в регистр IP адрес соответствующей подпрограммы, осуще-
ствляя таким образом переход на подпрограмму. После этого под-
программа выполняется, как любой другой код. В подпрограммах мо-
гут (часто это так и бывает) содержаться инструкции вызовов дру-
гих подпрограмм. Фактически, должным образом построенные подпрог-
раммы могут даже вызывать сами себя (это называется рекурсией).
. . . .
. . . .
. . . .
|-------------| |------------|
1000 | mov al,1 | (в IP загружается -->| shl al,1 | 1110
|-------------| 1110 и 1007 зано- | |------------|
1002 | mov bl,3 | сится в стек) | | add al,bl | 1112
|-------------| | |------------|
1004 | call DoCalc |-------------------- | and al,7 | 1114
|-------------| |------------|
1007 | mov ah,2 |<------------------- | add al,'0' | 1116
|-------------| Значение вершины | |------------|
1009 | int 21h | стека 1007 извле- ---| ret | 1118
|-------------| кается и заносится в |------------|
. . IP . .
. . . .
. . . .
Рис. 5.12 Выполнение подпрограммы.
Когда подпрограмма заканчивает работу, она вызывает инстру-
кцию RET, которая извлекает из стека адрес, занесенный туда со-
ответствующей инструкцией CALL, и заносит его в IP. Это приводит
к тому, что вызывающая программа возобновит выполнение с инструк-
ции, следующей за инструкции CALL.
Например, следующая программы выводит на экран три строки:
Привет
Пример строки
Еще одна строка
Для вывода строк вызывается подпрограмма PrintString:
DOSSEG
.MODEL SMALL
.STACK 200h
.DATA
Message1 DB 'Привет',0dh,0ah,0
Message2 DB 'Пример строки',0dh,0ah,0
Message3 DB 'Еще одна строка',0dh,0ah,0
.CODE
ProgramStart PROC NEAR
mov ax,@Data
mov ds,ax
mov bx,OFFSET Message1
call PrintString ; вывести строку "Привет"
mov bx,OFFSET Message2
call PrintString ; вывести строку "Пример строки"
mov bx,OFFSET Message3
call PrintString ; вывести строку "Еще одна
; строка"
mov ax,4ch ; функция DOS завершения
; программы
int 21h ; завершить программу
ProgramStart ENDP
;
; Подпрограмма вывода на экран строки, завершающейся
; нулевым символом
;
; Входные данные:
; DS:BX - указатель на выводимую строку.
;
; Нарушаемые регистры: AX, BX
;
PrintString PROC NEAR
PrintStringLoop:
mov al[bx] ; получить следующий символ
; строки
and dl,dl ; значение символа равно 0?
jz EndPrintString ; если это так, то вывод
; строки завершен
inc bx ; ссылка на следующий
; символ
mov ah,2 ; функция DOS вывода символа
int 21h ; вызвать DOS для вывода
; символа
jmp PrintStringLoop ; вывести следующий символ,
; если он имеется
EndPrintString:
ret ; возврат в вызывающую
; программу
PrintString ENDP
END ProgramStart
Здесь стоит отметить два момента. Во-первых, подпрограмма
PrintString не настроена жестко на печать определенной строки.
Она может печатать любую строку, на которую с помощью BX укажет
вызывающая программа. Во-вторых, для выделения подпрограммы ис-
пользованы две новых директивы - PROC и ENDP.
Директива PROC используется для того, чтобы отметить начало
процедуры. Метка, указанная в директиве PROC (в данном случае -
PrintString), представляет собой имя процедуры, как если бы ис-
пользовалось:
PrintString LABEL PROC
Однако, директива PROC делает большее. Она определяет, какую
инструкцию RET (возврат управления) - ближнюю или дальнюю - сле-
дует использовать в данной процедуре.
Давайте рассмотрим последний оператор несколько подробнее.
Вспомним, что когда выполняется переход на метку ближнего типа
(NEAR), в IP загружается новое значение, а при переходе на даль-
нюю метку (FAR) новые значения загружаются и в регистр IP, и в
CS. Если инструкция CALL ссылается на дальнюю метку, загружаются
и CS, и IP (как и при переходе).
Вот почему при дальнем вызове в стек заносятся и регистр CS,
и IP. Иначе откуда инструкция RET получит достаточную информацию
для возврата в вызывающую программу? Ведь если дальний вызов заг-
рузит CS и IP, а занесет в стек только IP, то при возврате можно
будет только загрузить IP из вершины стека. Тогда в результате
выполнения инструкции RET пара регистров CS:IP содержала бы зна-
чение CS вызываемой программы, а IP - вызывающей, что очевидно не
имеет смысла.
Что же произойдет, когда в стек будут занесены оба регистра
- CS и IP? Как Турбо Ассемблер узнает о типе возврата, генерируе-
мом в соответствующей подпрограмме? Один из путей состоит в явном
задании типа каждой инструкции возврата - RETN (возврат ближнего
типа) или RETF (возврат дальнего типа), однако лучший способ зак-
лючается в использовании директив PROC и ENDP.
Директива ENDP используется для того, чтобы пометить конец
подпрограммы, начатой с помощью директивы PROC. Директива ENDP
отмечает конец подпрограммы, которая начинается с директивы PROC
с той же меткой. Например, директивы:
.
.
.
TestSub PROC NEAR
.
.
.
TestSub ENDP
.
.
.
отмечают начало и конец подпрограммы TestSub.
Директивы ENDP и PROC не генерируют выполняемого кода, ведь
это директивы, а не инструкции. Все их действие заключаются в
управлении типом инструкции RET данной подпрограммы.
Если операндом директивы PROC является NEAR (ближний), то
все инструкции RET между директивой PROC и соответствующей ди-
рективой ENDP ассемблируются, как возвраты управления ближнего
типа. Если же, с другой стороны, операндом директивы PROC являет-
ся FAR (дальний), то все инструкции RET в данной процедуре ассем-
блируются, как возвраты управления дальнего типа.
Поэтому, чтобы, например, изменить тип всех инструкций RET в
TestSub, измените директиву PROC следующим образом:
TestSub PROC FAR
В общем случае лучше там, где это возможно, использовать
подпрограммы ближнего типа, так как дальние вызовы занимают боль-
ше памяти и выполняются медленнее, а возвраты дальнего типа также
выполняются медленнее, чем ближнего. Однако подпрограммы дальнего
типа становятся необходимыми, когда объем кода вашей программы
превышает 64К.
Если вы используете упрощенные директивы определения сегмен-
тов, то лучше использовать директиву PROC без операндов, напри-
мер:
TestSub PROC
Когда Турбо Ассемблер встречает такую директиву, он автома-
тически настраивает ее тип в соответствии с выбранной с помощью
директивы .MODEL моделью памяти (по умолчанию это малая модель
памяти). Программы со сверхмалой, малой и компактной моделями па-
мяти могут иметь вызовы ближнего типа, а вызовы в программах со
средней, большой и сверхбольшой моделью памяти имеют дальний тип.
Например, в программе:
.
.
.
.MODEL SMALL ; малая модель памяти
.
.
.
TestSub PROC
.
.
.
подпрограмма TestSub вызывается с помощью ближнего вызова, а в
программе:
.
.
.
.MODEL LARGE ; большая модель памяти
.
.
.
TestSub PROC
.
.
.
с помощью дальнего.
Передача параметров
-----------------------------------------------------------------
Из программ, вызывающих подпрограммы (которые называют вызы-
вающими программами или вызывающим кодом), подпрограммам часто
передается информация. Например, в примере программы предыдущего
раздела для передачи в подпрограмму PrintString использовался ре-
гистр BX. Это действие называется передачей параметров. При этом
параметры указывают подпрограмме, что нужно сделать.
Существует два общепринятых способа передачи параметров: в
регистрах и в стеке. Передача параметров через регистры часто ис-
пользуется в чистом коде Ассемблера, а передача через стек ис-
пользуется в большинстве языков высокого уровня, включая Паскаль
и Си, и в подпрограммах на Ассемблере, вызываемых из этих языков.
Передача параметров в регистрах очень проста. Для этого нуж-
но просто поместить значения-параметры в соответствующие регистры
и вызвать подпрограмму. Каждая подпрограмма может иметь свои
собственные потребности в параметрах, хотя вы вероятно поймете,
что чтобы избежать путаницы, лучше выработать некоторые соглаше-
ния и придерживаться их. Например, вы можете следовать правилу,
согласно которому первый параметр-указатель всегда передается в
регистре BX, второй - в SI и т.д. Если вы используете для переда-
чи параметров регистры, аккуратно комментируйте каждую подпрог-
рамму - какие параметры она получает и в каких регистрах они
должны находиться.
Передача параметров в стеке несколько более сложна и отлича-
ется значительно меньшей гибкостью, чем передача их через регист-
ры. Если вы решили использовать передачу параметров через стек,
вы вероятно будете использовать соглашения, принятые в предпочи-
таемом вами языке высокого уровня. Это позволит легко компоновать
подпрограммы на Ассемблере с программами, написанными на данном
языке. В соответствующих главах и приложениях данного руководства
приводится полное описание соглашений по передаче параметров в
Турбо Си, Турбо Паскале, Турбо Бейсике и Турбо Прологе, и приве-
дены примеры на Ассемблере.
Возвращаемые значения
-----------------------------------------------------------------
Подпрограммы часто возвращают значения в вызывающую програм-
му. В программах на Ассемблере, которые предполагается вызывать
из программы на языке высокого уровня, для возврата значений вы
должны следовать соглашениям данного языка. Например, функции,
вызываемые в языке Си, должны возвращать 8- или 16-битовые значе-
ния (значения символьного, целого типа и ближние указатели) в ре-
гистре AX, а 32-битовые значения (длинные целые и дальние указа-
тели) - в паре регистров DX:AX. В главах 6 - 9 данного руководст-
ва дается подробное описание соглашений по возвращаемым значениям
языка Турбо Си, Турбо Паскаля, Турбо Бейсика и Турбо Пролога.
В программах, где используется только язык Ассемблера, в от-
ношении возвращаемых значений допускается полная свобода: вы по-
жете помещать их в тот регистр, какой захотите. Фактически, в ре-
гистре флагов подпрограммы могут даже возвращать информацию о
состоянии (в виде установки флага переноса или флага нуля). Одна-
ко, лучше установить некоторые соглашения и их придерживаться.
Полезным соглашением может служить возврат 8-битовых значений в
регистре AL и 16-битовых значений в регистре AX.
Основная проблема при использовании в Ассемблере возвращае-
мых подпрограммами значений состоит в том, что при возврате
информации подпрограммы могут разрушить важную для вызывающей
программы информацию. В Ассемблере легче писать обращение к
подпрограмме, не помня о том, что подпрограмма возвращает
значение, скажем в SI (или что данная подпрограмма просто
изменяет SI), но при этом вы получите программу, в которой трудно
будет выявлять ошибки.
По этой причине лучше сводить к минимуму число значений,
возвращаемых в регистрах (лучше всего до одного) и возвращать до-
полнительные значения, сохраняя их в ячейках памяти, на которые
ссылаются передаваемые указатели (как это делается в Паскале и
Си).
Сохранение регистров
-----------------------------------------------------------------
Соответствующее сохранение регистров при обращении к под-
программе - это существенный момент в программировании на Ассем-
блере. В современных языках высокого уровня подпрограмма обычно
не может изменить значения переменных вызывающей программы, если
вызывающая программа этого явным образом не допускает. В Ассем-
блере это не так: переменные вызывающей программы хранятся часто
в тех же регистрах, что и регистры, используемые подпрограммой.
Например, если подпрограмма изменяет регистр, значение которого
вызывающая программа установила перед вызовом подпрограммы, но
который она использует после обращения к ней, вы получите ошибку.
Одно из решений этой проблемы состоит в том, чтобы при входе
в каждую подпрограмму заносить в стек все используемые ей регист-
ры и восстанавливать их из стека перед возвратом в вызывающую
программу. К сожалению, для этого требуется существенное время и
большой объем кода. Другая возможность заключается в том, чтобы
ввести правило, что вызывающая программа никогда не рассчитывает
на сохранение регистров подпрограммой и все функции по их сохра-
нению выполняет сама. Но это малопривлекательно, поскольку су-
щественным доводом в пользу применения Ассемблера является свобо-
да эффективного использования регистров.
Если говорить кратко, то в языке Ассемблера существует ко-
нфликт между скоростью и простотой программирования. Если вы со-
бираетесь использовать Ассемблер, вы сможете писать быстрые и
компактные программы, а это означает, что нужно разумно относить-
ся к сохранению регистров и обеспечить, чтобы при вызове каждой
подпрограммы из-за регистров не возникало конфликтов. Наилучшим
подходом является аккуратное комментирование каждой подпрограммы
и указание, какие регистры она использует, и обращение к данным
комментариями при каждом вызове подпрограммы.
Нужно уделять внимание как отслеживанию сохранения регист-
ров, так и максимально эффективному их использованию. В програм-
мировании на Ассемблере это одинаково важно. Языки высокого уров-
ня выполняют за вас эту работу, но они, как мы уже упоминали, не
позволяют получать такие быстрые и компактные программы, какие
можно получить с помощью Ассемблера.
Пример программы на языке Ассемблера
-----------------------------------------------------------------
Давайте теперь попробуем реализовать все то, что мы узнали в
последних двух главах, в виде полезного примера программы на Ас-
семблере. Эта программа WCOUNT.ASM подсчитывает число слов в фай-
ле и выводит его на экран.
;
; Программа для подсчета числа слов в файле. Слова разделены
; пробелами, символами табуляции, возврата каретки или перевода
; строки.
;
; Вызов: wc < имя_файла.расш
;
DOSSEG ; выбрать стандартный
; порядок сегментов
.MODEL SMALL ; код и данные
; помещаются в 64К
.STACK 200h ; стек размером 512
; байт
.DATA
Count DW 0 ; используется для
; подсчета слов
InWhitespace DB ? ; устанавливается в
; значение 1, когда
; последним прочитанным
; символов является
; разделитель
TempChar DB ? ; временная память,
; используемая
; в GetNextCharacter
Result DB 'Число слов: ', 5 DUP (?)
; строка, используемая
; для вывода результата
CountInsertEnd LABEL BYTE ; используется для
; определения конца
; области, в которой
; хранится строка со
; значением счетчика
DB 0dh,0ah,'$' ; функция DOS 9
; работает со строками,
; которые завершаются
; символом $
.CODE
ProgramStart:
mov ax,@Data
mov ds,ax ; DS указывает на
; сегмент данных
mov [InWhitespace],1 ; предположим, это
; разделитель, так как
; первый отличный от
; разделителя символ,
; который мы найдем,
; будет отмечать начало
; слова
CountLoop:
call GetNextCharacter ; получить следующий
; символ для проверки,
jz CountDone ; если он имеется
call IsCharacterWhitespace ; это разделитель?
jz IsWhitespace ; да
cmp [InWhitespace],0 ; символ не является
; разделителем - теперь
; мы в разделителе?
jz CountLoop ; мы не в разделителе
; и символ не является
; разделителем, поэтому
; с этим символом работа
; окончена
inc [Count] ; мы в разделителе и
; символ не является
; разделителем, значит
; мы нашли начало нового
; слова
mov [InWhitespace],0 ; отметить, что мы боль-
; ше не в разделителе
jmp CountLoop ; обработать следующий
; символ
IsWhitespace:
mov [InWhitespace],1 ; отметить, что мы в
; разделителе
jmp CountLoop ; обработать следующий
; символ
;
; Подсчет завершен - вывести результаты
;
CountDone:
mov ax,[Count] ; число, которое нужно
; преобразовать в строку
mov bx,OFFSET CountInsertEnd-1 ; ссылка на
; конец строки, в
; которую нужно
; поместить число
mov cx,5 ; число цифр, которые
; нужно преобразовать
call ConvertNumberToString ; преобразовать
; число в строку
mov bx,OFFSET Result ; ссылка на строку
; результата
call PrintString ; вывести результат
mov ah,4ch ; функция DOS
; завершения программы
int 21h ; завершить программу
;
; Подпрограмма получения следующего символа из стандартного
; ввода
;
; Входные данные: нет
;
; Выходные данные:
; AL = символ, если он был доступен
; флаг Z = 0 (NZ), если символ доступен,
; = 1 (Z) при достижении конца строки
;
; Нарушаемые регистры: AH, BX, CX, DX
;
GetNextCharacter PROC
mov ah,3fh ; функция DOS
; чтения из файла
mov bx,0 ; стандартный
; описатель ввода
mov cx,1 ; считать один символ
mov dx,OFFSET TempChar ; поместить символ
; в TempChar
int 21h ; получить следующий
; символ
jc NoCharacterRead ; если DOS сообщает
; об ошибке,
; интерпретировать ее,
; как конец файла
cmp [TempChar],1ah ; это Control-Z?
; (метка конца файла)
jne NotControlZ ; нет
NoCharacterRead:
sub ax,ax ; установить флаг Z,
and ax,ax ; что отражает, был
; ли считан символ (NZ)
; или мы достигли
; конца файла (Z).
; Обратите внимание,
; что функция DOS 3fh
; устанавливает регистр
; AX в значение числа
; считанных символов
mov al,[TempChar] ; возвратить считанный
; символ
ret ; выполнено
GetNextCharacter ENDP
;
; Подпрограмма, сообщающая, является ли прочитанный символ
; разделителем
;
; Входные данные:
; AL = проверяемому символу
;
; Выходные данные:
; флаг Z = 0 (NZ), если символ не является разделителем,
; = 1 (Z) если символ - разделитель
;
; Нарушаемые регистры: нет
;
IsCharacterWhitespace PROC
cmp al,09h ; это символ табуляции?
jz EndIsCharacterWhitespace ; если да, то
; это разделитель
cmp al,' ' ; это пробел?
jz EndIsCharacterWhitespace ; если да, то
; это разделитель
cmp al,0dh ; это возврат каретки?
jz EndIsCharacterWhitespace ; если да, то
; это разделитель
cmp al,0ah ; это перевод строки?
cmp al,' ' ; это пробел?
; если да, то это
; разделитель,
; возвратить Z, если
; нет, то это не
; разделитель, возвратить
; NZ (устанавливаться
; cmp)
EndIsCharacterWhiteSpace:
ret
IsCharacterWhiteSpace ENDP
;
; Подпрограмма, преобразующая двоичное число в текстовую
; строку
;
; Входные данные:
; AX = число, которое нужно преобразовать
; DS:BX = указатель на конец строки, в которой
; сохраняется текст
;
; Выходные данные: нет
;
; Нарушаемые регистры: AX, BX, CX, DX, SI
;
ConvertNumberToString PROC
mov si,10 ; используется в цикле
; ConvertLoop
sub dx,dx ; преобразовать AX в
; двойное слово в AD:DX
div si ; разделить число на 10
; остаток - в DX, это
; десятичное число из
; одной цифры; число/10
; находится в AX
add dl,'0' ; преобразовать остаток
; в текстовую строку
mov [bx],dl ; поместить эту цифру в
; строку
dec bx ; ссылка на следующую
; самую значащую цифру
loop ConvertLoop ; обработать следующую
; цифру, если она есть
ret
ConvertNumberToString ENDP
;
; Подпрограмма, выводящая строку на экран дисплея
;
; Входные данные:
; DS:BX = указатель на выводимую строку
;
; Выходные данные: нет
;
; Нарушаемые регистры: нет
;
PrintString PROC
push ax ; сохранение регистров
push dx ; в подпрограмме
mov ah,9 ; функция DOS вывода
; строки
mov dx,bx ; установить DS:DX на
; выводимую строку
int 21h ; вызвать DOS для
; вывода строки
pop dx ; восстановить измененные
pop ax ; регистры
ret ; возврат управления
PrintString ENDP
END ProgramStart
Выполняемый файл WCOUNT.EXE можно запускать в ответ на подс-
казку DOS, переназначив ввод из файла, в котором вы хотите подс-
читать слова. Например, для подсчета числа слов в файле
WCOUNT.ASM нужно в ответ на подсказку DOS ввести:
wcount <wcount.asm
Через несколько секунд на экран будет выведен результат:
Число слов: nnn
(где вместо nnn будет стоять конкретное число).
Относительно файла WCOUNT.ASM можно сделать несколько инте-
ресных замечаний. Во-первых, для выполнения различных функций при
чтении символа, проверки символа на разделитель, преобразования
числа слов в строку и вывода строки в файле WCOUNT.ASM использу-
ются подпрограммы. Это позволяет сделать основную программу файла
WCOUNT.ASM небольшой по размеру и простой для понимания.
Другое преимущество использования подпрограмм заключается в
простоте, с какой вы можете изменять работу подпрограмм. Если,
например, вам потребуется изменить определение разделителя и
включить в число разделителей знак равенства, вы можете изменить
подпрограмму IsWhitespace, оставив основную программу без измене-
ний.
Заметим, что подпрограммы GetNextCharacter и IsWhitespace
возвращают информацию о состоянии во влаге нуля (подпрограмма
GetNextCharacter возвращает также информацию в регистре AL). Флаг
нуля прекрасно подходит для возврата состояния типа "да/нет", а
регистр AL (или AX) хорошо использовать для возврата значений.
Наконец, обратим внимание на объем кода на Ассемблере, необ-
ходимый для выполнения операций по выводу текста. Чтобы вывести
на экран целое значение (число подсчитанных слов), нам пришлось
сначала преобразовать значение счетчика в текстовую строку (пов-
торно выполняя деление на 10 и добавляя к остатку символ "0").
Только после этого вызвали DOS для вывода текстовой строки. Это
сильно отличается от простого оператора на языке Си:
print("Число слов: dn",count);
С другой стороны, после того, как вы напишете подпрограммы
(такие, как ConvertNumberToString), вы можете повторно их исполь-
зовать. Если сформировать библиотеку полезных подпрограмм, то это
может в последующем существенно облегчить и упростить разработку
программ.
http://antibotan.com/ - Всеукраїнський студентський арх?в