ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.


В разделе можно заказать цветы с доставкой Кактус в Сургуте.
ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.


В разделе можно заказать цветы с доставкой Кактус в Сургуте.

 

Часть 1

=================================================================

                     Авторский коллектив "*.*"
                    под руководством Орлова С.Б.



                   ПРОГРАММА-СПРАВОЧНИК по системе
               программирования ТУРБО АССЕМБЛЕР 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 




Яндекс цитирования