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



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



 

Часть 2


    Глава 6. Более подробно о программировании на Турбо Ассемблере
-----------------------------------------------------------------

     Прочитав последние две главы, вы, конечно, очень много узна-
ли  о  языке Ассемблера, но остается узнать еще намного больше. В
данной главе мы коснемся некоторых довольно развитых,  но  весьма
полезных  аспектов  Турбо  Ассемблера и программирования на языке
Ассемблера.

     Мы обсудим в частности следующие темы:

     - Директивы Турбо Ассемблера EQU и =, которые  позволят  вам
присваивать именам значения и текстовые строки.

     - Мощные строковые инструкции Турбо Ассемблера.

     - Возможность ассемблирования  с  помощью  Турбо  Ассемблера
нескольких  исходных  файлов и последующего использования утилиты
TLINK для компоновки их в одну программу.

     - Возможность Турбо Ассемблера включать отдельные файлы  ис-
ходного кода в любую программу на Ассемблере.

     - Исчерпывающие файлы листингов исходного кода Турбо  Ассем-
блера.

     Имеется возможность писать программы на Ассемблере таким об-
разом,  что  они  будут ассемблироваться по-разному при различных
обстоятельствах. Мы рассмотрим, почему это может оказаться полез-
ным,  и директивы, делающие это возможным. Наконец, мы рассмотрим
некоторые наиболее общие ошибки, которые обычно  делают  програм-
мисты, работающие на Ассемблере.

     Вам следует обязательно просмотреть данную главу,  возможно,
даже  не  вникая  глубоко в ее содержание. Сегодня эта информация
может вам не потребоваться, но завтра, когда в ней появится необ-
ходимость, вы будете знать, где ее искать.


Использование директив присваивания
-----------------------------------------------------------------

     Давайте начнем с рассмотрения директив EQU и = для  присваи-
вания  меток  значениям  и  текстовым строкам. Это очень полезное
средство, позволяющее сделать программу на Ассемблере  более  по-
нятной и легко обслуживаемой.

                             Директива EQU
-----------------------------------------------------------------

     Причина использования меток для имен переменных, подпрограмм
и конкретных  инструкций очевидна.  Ведь в противном случае мы не
могли бы ссылаться по именам на эти элементы программ.  В  равной
степени важна,  но менее очевидна, необходимость присваивать мет-
кам значения и текстовые строки.

     Присвоить метке числовое значение или текстовую строку  поз-
воляет директива EQU. Ссылка на метку директивы EQU транслируется
в литеральное приравнивание. Рассмотрим следующий фрагмент  прог-
раммы:

             .
             .
             .
 END_OF_DATA         EQU  '!'         ; "конец данных"
 STORAGE_BUFFER_SIZE EQU  1000        ; размер буфера
             .DATA
 StorageBuffer  DB   STORAGE_BUFFER_SIZE  DUP (?)
             .
             .
             .
             .CODE
             mov   ax,@Data
             mov   ds,ax
             sub   di,di               ; установить указатель
                                       ; буфера в значение 0
 StorageLoop:
             mov   ah,1
             int   21h                 ; получить следующую
                                       ; нажатую клавишу
             mov   [StarageBuffer+di],al ; сохранить следующую
                                       ; нажатую клавишу
             cmp   al,END_OF_DATA      ; это клавиша "конец
                                       ; данных"?
             je    DataAckquired       ; да, перейти к
                                       ; обработке данных
             inc   di                  ; подсчитать это
                                       ; нажатие клавиши
             cmp   di,STORAGE_BUFFER_SIZE ; мы переполнили
                                       ; буфер?
             jb    StorageLoop         ; нет, получить
                                       ; следующую клавишу
 ; Буфер переполнен...

             .
             .
             .
 ; Мы получили данные
 DataAcquired:
             .
             .
             .

     Здесь директива EQU  используется для  определения двух  ме-
ток: STORAGE_BUFFER_SIZE и END_OF_DATA. Метка END_OF_DATA прирав-
нивается к символу "!" и сравнение с ней выполняется  при  каждом
нажатии  клавиши, чтобы определить, не встретили ли мы конец дан-
ных. Это показывает одно из существенных преимуществ  использова-
ния  директивы  приравнивания (EQU), ведь метки значительно более
информативны,  чем  значения-константы.  Кроме  того,  назначение
инструкции:

            cmp   al,END_OF_DATA

определенно понятней, чем назначение инструкции:

            cmp   al,'!'

(END_OF_DATA означает "КОНЕЦ_ДАННЫХ").

     Использование  метки  STORAGE_BUFFER_SIZE  иллюстрирует  еще
один  довод  в  пользу  применения  приравнивания (присваивания).
STORAGE_BUFFER_SIZE, для которого устанавливается значение  1000,
используется  как  для  создания  буфера в памяти размером в 1000
байт, так  и  для проверки этого буфера на переполнение.  В обоих
случаях можно было бы использовать константу 1000, но это гораздо
менее информативно, чем метка STORAGE_BUFFER_SIZE ("РАЗМЕР_БУФЕРА
_В_ПАМЯТИ").

     Предположим теперь, что вы ходите изменить размер  буфера  в
памяти.  Для  этого  вам придется изменить операнд только в одной
директиве EQU, этим самым вы внесете изменения  по  всему  тексту
программы.  Конечно,  в противном случае изменить две константы в
программе было бы нетрудно, но данная константа могла бы  исполь-
зоваться в десятках или сотнях мест. А в этом случае гораздо лег-
че (и при этом меньше вероятность внесения  в  программу  ошибки)
изменить одно приравнивание, чем десятки или сотни констант.

     Операнд в директиве приравнивания метки может сам  содержать
метки, присваивание для которых выполняется в других местах. Нап-
ример:

             .
             .
             .
 TABLE_OFFSET    EQU   1000h
 INDEX_START     EQU   (TABLE_OFFSET+2)
 DICT_START      EQU   (TABLE_OFFSET+100h)
             .
             .
             .
             mov   ax,WORD PTR ]bx+INDEX_START]  ; получить
                                  ; первую индексную запись
             .
             .
             .
             lea   si,[bx+DICT_START] ; указатель на первую
                                  ; запись словаря
             .
             .
             .

что эквивалентно следующему:

             .
             .
             .
             mov   ax,WORD PTR [bx+1000h+2]
             lea   si,[bx+1000h+100h]
             .
             .
             .

     Приравненные метки удобно использовать для присваивания раз-
личным  прерываниям, портам и ячейкам памяти компьютера РС понят-
ных имен. Проиллюстрируем некоторые случаи  такого  использования
директивы EQU на следующем примере:

             .
             .
             .
 DOS_INT            EQU   21h    ; прерывания по вызову
                                 ; функции DOS
 CGA_STATUS         EQU   3dah   ; порт состояния адаптера
                                 ; CGA
 VSYNC_MASK         EQU   00001000b ; выделить бит в состоянии
                                 ; порта CGA, указывающий,
                                 ; когда вы можете изменять
                                 ; изменять экран, не вызывая
                                 ; помех ("снежных хлопьев")
 BIAS_SEGMENT       EQU   40h    ; сегмент BIOS, в котором
                                 ; хранятся данные
 EQUIPMENT_FLAG     EQU   10h    ; смещение в сегменте BIOS
                                 ; переменной флага аппарат-
                                 ; ного обеспечения
             .
             .
             .
             mov   ah,2
             mov   di,'Z'
             int   DOS_Int       ; напечатать символ "Z"
             .
             .
             .
 ; Подождать, пока можно будет обновить экран, не вызывая
 ; помех
             mov   dx,CGA_STATUS
 WaitForVerticalSync:
             in    al,dx         ; получить статус CGA
             and   al,VSYNC_MASK ; идет вертикальная
                                 ; синхронизация?
             jz    WaitForVerticalSync ; нет, завершить
                                 ; ожидание
             .
             .
             .
             mov   ax,BIOS_SEGMENT
             mov   ds,ax         ; DS указывает на сегмент
                                 ; данных BIOS
             mov   bx,EQUIPMENT_FLAG ; ссылка на флаг
                                 ; аппаратного обеспечения
             and   BYTE PTR [bx],NOT 30h
             or    BYTE PTR [bx],20h ; установить флаг
                                 ; аппаратного обеспечения
                                 ; так, чтобы был выбран
                                 ; цветной режим с 80
                                 ; позициями в строке
             .
             .
             .

     Приравненные метки, в которых используются другие приравнен-
ные  метки,  расширяют принцип использования присваиваний для об-
легчения изменения ваших программ. Например,  если  в  предыдущем
примере вы  переместите  все  ссылки в таблице на 10 байт ближе к
BX,  вам придется изменить только присваивание  для  TABLE_OFFSET
на:

 TABLE_OFFSET    EQU   (1000h - 10)

     После выполнения ассемблирования  INDEX_START  и  DICT_START
будут  настроены в соответствии с TABLE_OFFSET, так как их значе-
ния основываются на TABLE_OFFSET.

     Кстати, скобки в  которые  заключен  операнд  директивы EQU,
являются  необязательными, но они не помешают, поскольку помогают
визуально выделить операнд.

     Директиву EQU можно использовать для присваивания метке тек-
стовой  строки  или  значения. Например, далее метка используется
для хранения выводимой на печать текстовой строки:

             .
             .
             .
 EQUATED_STRING  EQU  'Пример текстовой строки'
             .
             .
             .
 TextMessage     DB   EQUATED_STRING
             .
             .
             .
             mov   dx,OFFSET TextMessage
             mov   ah,9
             int   21h       ; напечатать TextMessage
             .
             .
             .

     Метки, приравненные к тестовым строкам, могут использоваться
в качестве операндов. Например:
             .
             .
             .
 REGISTER_BX     EQU   BX
             .
             .
             .
             mov   ax,REGISTER_BX
             .
             .
             .

     Это ассемблируется в инструкцию:

             mov   ax,bx

     Обычно не возникает необходимости приравнивать метку  к  ре-
гистру,  но  вы можете, например, использовать приравненные метки
или ARG для присваивания имени передаваемым в стеке параметрам  и
выделяемой в стеке динамической памяти:

;
; Вызываемая из Си (модель NEAR) подпрограмма, выполняющая
; сложение трех целых параметров и возвращающая целый
; результат. Прототип функции:
;
; int AddThree(int I, int J, int K)
;
Temp  EQU  [bp-2]
I     EQU  [bp+4]
J     EQU  [bp+6]
K     EQU  [bp+8]
;
_AddThree  PROC
      push bp                 ; сохранить BP вызывающей
                              ; программы
      mov  bp,sp              ; ссылка на рамку стека
      sub  sp,2               ; выделить место для Temp
      mov  ax,I               ; получить I
      add  ax,J               ; вычислить I + J
      mov  ax,K               ; получить K
      mov  Temp,ax            ; вычислить I + J + K
      mov  sp,bp              ; освободить выделенное
                              ; для Temp пространство
      pop  bp                 ; восстановить значение BP
                              ; вызывающей программы
      ret
_AddThree  ENDP

     Вы можете применять директиву EQU для присваивания имени лю-
бой  текстовой  строке,  которая  может использоваться в качестве
операнда. В действительности вы можете использовать  приравненные
метки в поле инструкции или директивы, а не только в поле операн-
да, хотя трудно представить, для чего это может понадобиться.

     Чтобы операнд директивы EQU  рассматривался,  как  текстовая
строка,  а не как выражение, можно использовать угловые скобки (<
и >). Например:

 TABLE_OFFSET       EQU  1
 INDEX_START        EQU  

     Здесь  метке  INDEX_START  присваивается  текстовая   строка
"TABLE_OFFSET+2", в то время как в директивах:

 TABLE_OFFSET       EQU  1
 INDEX_START        EQU  TABLE_OFFSET+2

метке INDEX_START  присваивается значение 3 (результат сложения 1
+ 2).  В общем случае полезно всегда заключать  в  директиве  EQU
операнды,  представляющие собой текстовые строки, в угловые скоб-
ки.  Этим будет обеспечено,  что такие операнды случайно не будут
вычислены, как выражения.

     Если в данном исходном модуле с помощью директивы EQU  метка
приравнивается к значению или текстовой строке, она не может быть
переопределена в данном модуле. Например, следующий код  приведет
к ошибке:

             .
             .
             .
 X           EQU   1
             .
             .
             .
 X           EQU   101
             .
             .
             .

     Если вам требуется переопределять в  программе  приравненные
метки  (и для этого есть какая-то весомая причина),  то вам нужно
будет использовать директиву = (мы обсудим ее позднее).



                   Предопределенный идентификатор $
-----------------------------------------------------------------

     Вспомним, что в Турбо Ассемблере имеется несколько предопре-
деленных  идентификаторов (например, @data). Еще один простой, но
удивительно полезный предопределенный идентификатор - это иденти-
фикатор  $, который всегда установлен в текущее значение счетчика
адреса. Другими словами, идентификатор $  всегда  равен  текущему
смещению  в  сегменте,  в котором Турбо Ассемблер в данным момент
выполняет ассемблирование. $ представляет собой постоянное значе-
ние  смещения, аналогичное OFFSET MemVar. Это позволяет использо-
вать $ в выражениях или в любом месте, где допускается  использо-
вание константы.

     Идентификатор $ очень  удобно  использовать  для  вычисления
длины данных и кода. Предположим, например, что вы хотите прирав-
нять идентификатор STRING_LENGTH к длине  строки  в  байтах.  Без
предопределенного идентификатора  $  вам придется сделать следую-
щее:

             .
             .
             .
 StringStart    LABEL  BYTE
             db 0dh,0ah,'Текстовая строка'odh,0ah
 StringEnd      LABEL  BYTE
 STRING_LENGTH  EQU    (StringEnd-StringStart)
             .
             .
             .

а с помощью идентификатора $ вы можете записать:

             .
             .
             .
 StringStart    LABEL  BYTE
             db 0dh,0ah,'Текстовая строка'odh,0ah
 STRING_LENGTH  EQU    ($-StringStart)
             .
             .
             .

     Длину (в словах) массива слов можно вычислить следующим  об-
разом:

             .
             .
             .
 WordArray   DW  90h, 25h, 0, 16h, 23h
 WORD_ARRAY_LENGTH  EQU  (($-WordArray)/2)
             .
             .
             .

     Вы, конечно, можете сосчитать отдельные элементы вручную, но
для больших массивов и строк это довольно затруднительно.

     Три  другие  полезные  предопределенные  переменные  -   это
??DATA,  ??TIME  и ??FILENAME. ??DATE содержит дату ассемблирова-
ния в виде текстовой строки в формате 01/02/88.  ??TIME  содержит
время  ассемблирования в виде 13:45:06, а ??FILENAME - имя ассем-
блируемого файла в виде заключенной в кавычки строки из 8  симво-
лов (например, "TEST.ASM").



                              Директива =
-----------------------------------------------------------------

     Директива = аналогична директиве  EQU  во  всех  отношениях,
кроме одного: в то время как метки, определенные с помощью дирек-
тивы EQU, переопределять не допускается (в этом случае происходит
ошибка), метку, определенную с помощью директивы =, можно свобод-
но переопределять.

     Например, в следующем фрагменте директива = используется для
генерации таблицы первых 100 произведений числа 10:

             .
             .
             .
             .DATA
 MultipleOf10   LABEL  WORD
 TEMP        =   0
             REPT  100
             DW    TEMP
 TEMP        =   TEMP+10
             ENDM
             .
             .
             .
             shl   bx,1      ; BX - число, которое нужно
                             ; умножить на 10
                             ; сдвиг влево для умножения
                             ; на 2 (для формирования
                             ; таблицы слов)
             mov   ax,[MultipleOf10+bx] ; получить число
                             ; * 10
             .
             .
             .

     При вычислении всех операндов директивы = должно  получаться
числовое значение - в отличие от директивы EQU с помощью директи-
вы = меткам нельзя присваивать текстовые строки.




Строковые инструкции
-----------------------------------------------------------------

     Теперь мы подошли к рассмотрению наиболее мощных и необычных
инструкций  процессора  8086 - инструкций для работы со строками.
Строковые инструкции отличаются от прочих  инструкций  процессора
8086. Они могут (в одной инструкции) обращаться к памяти и увели-
чивать или уменьшать регистр-указатель. Одна строковая инструкция
может обращаться к памяти 130000 раз.

     Как ясно из их названия, строковые инструкции  особенно  по-
лезны  при работе с текстовыми строками. Их можно также использо-
вать при работе с массивами,  буферами  данных  и  любыми  типами
строк байт или слов. Строковыми инструкциями следует пользоваться
там,  где только это возможно, поскольку они, как правило, короче
и работают быстрее,  чем эквивалентная им комбинация обычных инс-
трукций процессора 8086, таких, как MOV, INC и LOOP.

     Мы рассмотрим две различные  по  функциональному  назначению
группы строковых инструкций: строковые инструкции для перемещения
данных (LODS, STOS и MOVS) и строковые  инструкции,  используемые
для поиска и сравнения данных (SCAS и CMPS).


Строковые инструкции перемещения данных
-----------------------------------------------------------------

     Строковые инструкции перемещения данных во многом аналогичны
инструкции MOV, но могут выполнять больше функций, чем инструкция
MOV и работают быстрее. Мы рассмотрим  сначала  инструкцию  LODS.
Заметим,  что во всех строковых инструкциях флаг указания направ-
ления задает направление, в котором изменяются  регистры-указате-
ли.

                            Инструкция LODS
-----------------------------------------------------------------

     Инструкция LODS, которая загружает байт или слово из  памяти
в  аккумулятор  (накопитель),  подразделяется на две инструкции -
LODSB и LODSW. Инструкция LODSB загружает байт, адресуемый с  по-
мощью   пары  регистров  DS:SI,  в  регистр  AL  и уменьшает  или
увеличивает регистр SI (в зависимости от состояния флага  направ-
ления). Если флаг направления равен 0 (установлен с помощью инст-
рукции CLD), то регистр SI увеличивается, а если флаг направления
равен  1  (установлен  с  помощью  инструкции STD), то регистр SI
уменьшается. И это верно не только  для  инструкции  LODSB,  флаг
направления  управляет направлением, в котором изменяются все ре-
гистры-указатели строковых инструкций.

     Например, в следующем фрагменте программы:

             .
             .
             .
             cld
             mov   si,0
             lodsb
             .
             .
             .

инструкция LODSB загружает регистр AL содержимым байта со  смеще-
нием 0 в сегменте данных и увеличивает значение регистра SI на 1.
Это эквивалентно выполнению следующих инструкций:

             .
             .
             .
             mov   si,0
             mov   al,[si]
             inc   si
             .
             .
             .

однако инструкция LODSB работает существенно быстрее (и  занимает
на два байта меньше), чем инструкции:

             mov   al,[si]
             inc   si

     Инструкция LODSW аналогична инструкции LODSB. Она  сохраняет
в регистре AX слово, адресуемое парой регистров DS:SI, а значение
регистра SI уменьшается или увеличивается на 2, а не на 1. Напри-
мер, инструкции:

             .
             .
             .
             std
             mov   si,0
             lodsw
             .
             .
             .

загружают слово со смещением 10 в сегменте данных в регистр RU, а
затем значение SI уменьшается на 2.

                            Инструкция STOS
-----------------------------------------------------------------

     Инструкция STOS - это дополнение инструкции LODS. Она  запи-
сывает значение размером в байт или слово из аккумулятора в ячей-
ку памяти, на которую указывает пара  регистров  ES:DI,  а  затем
увеличивает или уменьшает DI. Инструкция STOSB  записывает  байт,
содержащийся в регистре AL, в ячейку памяти по  адресу  ES:DI,  а
затем увеличивает или уменьшает регистр DI, в зависимости от флага
направления. Например, инструкции:

             .
             .
             .
             std
             mov   di,0ffffh
             mov   al,55h
             stosb
             .
             .
             .

записывают значение 55h в байт со смещением 0FFFFh в сегменте, на
который указывает  регистр  ES,  а затем уменьшает DI до значения
0FFFEh.

     Инструкция STOSW  работает  аналогично,  записывая  значение
размером  в слово, содержащееся в регистре AX, по адресу ES:DI, а
затем увеличивает или уменьшает значение регистра DI на 2. Напри-
мер, инструкции:

             .
             .
             .
             cld
             mov   di,0ffeh
             mov   al,102h
             stosw
             .
             .
             .

записывают значение 102h размером в слово, записанное в  регистре
AX,  по  смещению  0FFEh в сегменте, на который указывает регистр
ES, а затем значение регистра DI увеличивается до 1000h.

     Инструкции LODS и STOS можно прекрасно  использовать  вместе
для копирования буферов. Например, следующая подпрограмма копиру-
ет завершающуюся нулевым символом строку,  записанную  по  адресу
DS:SI, в строку по адресу ES:DI:

;
; Подпрограмма для копирования завершающейся нулем строки
; в другую строку
;
; Ввод:
;      DS:SI - строка, из которой выполняется копирование
;      ES:DI - строка, в которую выполняется копирование
;
; Вывод: нет
;
; Изменяемые регистры: AL, SI, DI
;
CopyString        PROC
             cld                ; обеспечить увеличение SI и
                                ; DI в строковых инструкциях
CopyStringLoop:
             lodsb              ; получить символ исходной
                                ; строки
             stosb              ; записать символ в выходную
                                ; строку
             cmp   al,0         ; последним символом строки
                                ; был 0?
             jnz   CopyStringLoop ; нет, обработать следую-
                                ; щий символ
             ret                ; да, выполнено
CopyString   ENDP

     Аналогично вы можете использовать инструкции LODS и STOS для
копирования блока байт,  которые не завершаются нулем,  используя
для этого цикл:

             .
             .
             .
             mov   cx,ARRAY_LENGTH_IN_WORDS ; размер массива
             mov   si,OFFSET SourceArray    ; исходный массив
             mov   ax,SEG SourceArray
             mov   dx,ax
             mov   di,OFFSET DestArray      ; целевой массив
             mov   ax,SEG DestArray
             mov   es,ax
             cld
 CopyLoop:
             lodsw
             stosw
             loop  CopyLoop
             .
             .
             .

     Однако для перемещения байта или слова из одного места в па-
мяти в другое есть еще более лучший способ. Это инструкция MOVS.



                            Инструкция MOVS
-----------------------------------------------------------------

     Инструкция MOVS аналогична инструкциям LODS и STOS, если  их
объединить  в  одну инструкцию. Эта инструкция считывает байт или
слово, записанное по адресу DS:SI, а затем записывает это  значе-
ние по адресу,  определяемому парой регистров  ES:DI.  Слово  или
байт  не  передается при этом через регистры,  поэтому содержимое
регистра AX не изменяется. Инструкция MOVSB имеет минимально воз-
можную для инструкции длину. Она занимает только один байт, а ра-
ботает еще быстрее, чем комбинация инструкций LODS и STOS. С при-
менением инструкции MOVS последний пример приобретает вид:

             .
             .
             .
             mov   cx,ARRAY_LENGTH_IN_WORDS
             mov   si,OFFSET SourceArray
             mov   ax,SEG SourceArray
             mov   ds,ax
             mov   di,OFFSET DestArray
             mov   ax,SEG DestArray
             mov   es,ax
             cld
   CopyLoop:
             movsw
             loop  CopyLoop
             .
             .
             .

                    Повторение строковой инструкции
-----------------------------------------------------------------

     Хотя в последнем примере код выглядит довольно  эффективным,
неплохо  было  бы избавиться от инструкции LOOP и перемещать весь
массив с помощью одной  инструкции.  Инструкции  процессора  8086
предоставляют такую возможность. Это форма строковых инструкций с
префиксом REP.

     Префикс повторения REP - это не инструкция,  а префикс  инс-
трукции.  Префикс инструкции изменяет работу последующей инструк-
ции.  Префикс REP делает следующее: он указывает, что последующую
инструкцию  нужно повторно выполнять до тех пор,  пока содержимое
регистра CX не станет равным 0. (Если регистр CX равен 0 в начале
выполнения инструкции,  то инструкция выполняется 0 раз,  другими
словами, никаких действий не производится.)

     Используя префикс REP, можно заменить  в  последнем  примере
инструкции:

   CopyLoop:
             movsw
             loop  CopyLoop

на инструкцию:

        rep  movsb

     Эта инструкция будет перемещать блок из 65535 слов  (0FFFFh)
из  памяти,  начинающейся с адреса DS:SI в память, начинающуюся с
адреса, определяемого регистрами ES:DI.

     Конечно, для выполнения инструкции 65535 раз потребуется го-
раздо  больше  времени,  чем  для выполнения инструкции один раз,
ведь для обращения ко всей этой памяти  требуется  время.  Однако
каждое  повторение  (с помощью префикса) строковой инструкции вы-
полняется быстрее, чем выполнение одной строковой инструкции. Это
позволяет  получить очень быстрый способ чтения из памяти, записи
в память и копирования.

     Префикс REP можно использовать не только с инструкцией MOVS,
но также и с инструкциями LODS и STOS (и инструкциями SCAS и CMPS
- это мы обсудим позднее). Инструкцию STOS можно с успехом повто-
рять для очистки или заполнения блоков памяти, например:

             .
             .
             .
             cld
             mov   ax,SEG WordArray
             mov   es,ax
             mov   di,OFFSET WordArray
             sub   ax,ax
             mov   cx,WORD_ARRAY_LENGTH
             rep   stosw
             .
             .
             .

     Здесь массив WordArray заполняется  нулями.  Для  повторения
инструкции  LODS  соответствующее  полезное  приложение придумать
трудно.

     Префикс REP вызывает повторение только строковой инструкции.
Инструкция типа:

             rep  mov   ax,[bx]

не имеет смысла. В этом случае префикс REP игнорируется и  выпол-
няется инструкция:

             mov   ax,[bx]




Выход указателя за границы строки
-----------------------------------------------------------------

     Заметим, что при выполнении строковой инструкции  увеличение
или  уменьшение  регистров SI  и DI выполняется после обращения к
памяти. Это означает, что после выполнения инструкции регистры не
указывают  на ту ячейку, к которой только что выполнялось обраще-
ние, они указывают на следующую ячейку, к которой нужно обратить-
ся.  В действительности это очень удобно, поскольку позволяет вам
эффективно организовывать циклы, аналогичные тем, которые  приве-
дены  в примерах последнего раздела. Однако иногда это может при-
водить к путанице, особенно когда с помощью строковой  инструкции
выполняется поиск данных.


Поиск данных с помощью строковой инструкции
-----------------------------------------------------------------

     Как работают строковые инструкции перемещения данных, вы уже
видели.  Теперь  мы  рассмотрим  строковые инструкции просмотра и
сравнения - SCAS и CMPS. Эти инструкции используются для просмот-
ра и сравнения блоков памяти.

                            Инструкция SCAS
-----------------------------------------------------------------

     Инструкция SCAS используется для просмотра памяти  и  поиска
совпадения  или  несовпадения  с  конкретным значением размером в
байт или слово. Как и все строковые инструкции,  инструкция  SCAS
имеет две формы - SCASB и SCASW.

     Инструкция SCASB сравнивает содержимое регистра AL с  байто-
вым значением по адресу ES:DI, устанавливая при этом флаги, отра-
жающие результат сравнения (как при выполнении  инструкции  CMP).
Как и при выполнении инструкции STOSB,  при выполнении инструкции
SCASB увеличивается или уменьшается значение регистра DI.  Напри-
мер, в следующем фрагменте программы находится первое t (строчная
буква) в строке TextString:

             .
             .
             .
             .DATA
TextString             DB   'Test text',0
TEXT_STRING_LENGTH     EQU  ($-TextString) ; длина строки
             .
             .
             .
             .CODE
             .
             .
             .
             mov   ax,@Data
             mov   es,ax
             mov   di,OFFSET TextString   ; ES:DI указывает
                                          ; на начало строки
                                          ; TextString
             mov   al,'t'                 ; искомый символ
             mov   cx,TEXT_STRING_LENGTH  ; длина просматри-
                                          ; ваемой строки
             cld                          ; увеличивать DI
                                          ; при просмотре
Scan_For_t_Loop:
             csasb                        ; ES:DI совпадает
                                          ; c AL?
             je   Found_t                 ; да, мы нашли "t"
             loop Scan_For_t_Loop         ; нет, анализировать
                                          ; следующий символ
; Символ "t" не найден
             .
             .
             .
; Символ "t" найден
Fount_t:
             dec   di                     ; ссылка обратно на
                                          ; смещение "t"
             .
             .
             .

     Заметим, что в данном примере после того, как найден  символ
t,  регистр  DI увеличен, что отражает выход указателя за границы
строки (как мы уже обсуждали это ранее). Когда в данной программе
успешно  выполняется  последняя инструкция SCASB, после сравнения
DI увеличивается, поскольку последнее действие строковой инструк-
ции состоит в увеличении указателя (указателей). В результате ре-
гистр DI указывает на байт после символа t и его нужно выровнять,
чтобы  компенсировать  этот выход за пределы найденного символа и
сделать так, чтобы он указывал на t.

     Лучше понять действие инструкции SCAS вам поможет  сравнение
последнего  пример с аналогичным фрагментом программы, но без ис-
пользования строковых инструкций:

             .
             .
             .
Scan_For_t_Loop:
             cmp   es:[di],al          ; ES:DI совпадает с AL?
             je   Found_t              ; да, мы нашли "t"
             inc  di
             loop Scan_For_t_Loop      ; нет, анализировать
                                       ; следующий символ
             .
             .
             .

     Последний пример не совпадает в точности с примером, в кото-
ром  используется  инструкция SCASB, так как SCASB увеличивает DI
сразу, а в последнем примере DI  увеличивается  после  инструкции
JE,  чтобы  избежать  изменения флагов, установленных инструкцией
CMP.

     Это позволяет сделать важное замечание, касающееся строковых
инструкций в целом. Строковые инструкции никогда не устанавливают
флаги таким  образом,  чтобы  они отражали изменения значений ре-
гистров SI, DI и/или CX. Инструкции STOS и MOVS вообще не изменя-
ют никаких флагов, а инструкции SCAS и CMPS изменяют флаги только
в соответствии с результатом выполняемого ими сравнения.

     Определенно, было бы удобно свести в предыдущем примере весь
цикл к одной инструкции. Как вы уже возможно догадались, это поз-
воляет сделать инструкция REP. Однако, может оказаться  желатель-
ным  прекратить выполнение цикла в случае совпадения или несовпа-
дения. Для этого существует две формы префикса REP, которые можно
использовать с инструкцией SCAS (и с CMPS) - REPE и REPNE.

     Префикс REPE (который также называется префиксом REPZ)  ука-
зывает процессору 8086, что инструкцию SCAS (или CMPS) нужно пов-
торять до тех пор, пока регистр CX не  станет  равным  нулю,  или
пока  не  произойдет несовпадение.  Префикс REPE можно рассматри-
вать, как префикс,  означающий "повторять, пока равно". Аналогич-
но, префикс REPNE (REPNZ) указывает процессору 8086, что инструк-
цию  SCAS  (CMPS) нужно повторять,  пока CX не станет равным нулю
или пока не произойдет совпадения. Префикс REPNE можно рассматри-
вать, как префикс "повторять, пока не равно".

     Приведем пример фрагмента программы, в котором для поиска  в
строке TextString символа t используется одна инструкция SCASB:

             .
             .
             .
             .DATA
TextString             DB   'Test text',0
TEXT_STRING_LENGTH     EQU  ($-TextString) ; длина строки
             .
             .
             .
             .CODE
             .
             .
             .
             mov   ax,@Data
             mov   es,ax
             mov   di,OFFSET TextString      ; ES:DI указывает
                                             ; на начало строки
                                             ; TextString
             mov   al,'t'                    ; искомый символ
             mov   cx,TEXT_STRING_LENGTH     ; длина просматри-
                                             ; ваемой строки
             cld                             ; увеличивать DI
                                             ; при просмотре
             repne csasb                     ; искать во всей
                                             ; строке символ "t"
             je   Found_t                    ; да, мы нашли "t"
             loop Scan_For_t_Loop            ; нет, анализировать
                                             ; следующий символ
; Символ "t" не найден
             .
             .
             .
; Символ "t" найден
Fount_t:
             dec   di                        ; ссылка обратно на
                                             ; смещение "t"
             .
             .
             .

     Как и все строковые инструкции, инструкция SCAS  увеличивает
регистр-указатель DI, если флаг направления равен 0 (очищен с по-
мощью инструкции CLD), и увеличивает DI,  если  флаг  направления
равен 1 (установлен с помощью инструкции STD).

     Инструкция SCASW - это форма инструкции SCASB для работы  со
словом. Она сравнивает содержимое регистра AX с содержимым памяти
по адресу  ES:DI и увеличивает или уменьшает значение регистра DI
в конце каждого выполнения на 2, а не на 1. В следующем фрагменте
программы инструкция REPE SCASW используется, чтобы найти послед-
нюю ненулевую запись в массиве целых чисел размером в слово:

             .
             .
             .
             mov   ax,SEG ShortIntArray
             mov   es,ax
             mov   di,OFFSET
 ShortIntArray+(ARRAY_LEN_IN_WORDS-1)*2)
                                        ; ES:DI указывает на
                                        ; конец
                                        ; массива ShortIntArray
             mov   cx,ARRAY_LEN_IN_WORDS ; длина массива в словах
             sub   ax,ax                ; поиск на несовпадение
                                        ; с нулем
             std                        ; поиск в обратном
                                        ; направлении, DI
                                        ; уменьшается
             repe  scasw                ; выполнять поиск, пока
                                        ; мы не встретим ненуле-
                                        ; вое слово или не вый-
                                        ; дем за границы массива
             jne   FondNonZero
; Весь массив заполнен нулями.
             .
             .
             .
; Мы нашли ненулевой элемент - настроить DI, чтобы он
; указывал на этот элемент.
             inc   di
             inc   di
             .
             .
             .



                            Инструкция CMPS
-----------------------------------------------------------------

     Инструкция CMPS  позволяет выполнять сравнение двух байт или
слов. При одном выполнении инструкции CMPS сравниваются две ячей-
ки  памяти,  а  затем увеличиваются регистры SI и DI.  Инструкцию
CMPS можно рассматривать,  как аналог  инструкции  MOVS,  который
вместо  копирования  одной  ячейки памяти в другую сравнивает две
ячейки памяти.

     Инструкция CMPSB сравнивает байт по адресу DS:SI с байтом по
адресу ES:DI, устанавливая соответствующим образом флаги и увели-
чивая или уменьшая регистры SI и DI (в зависимости от флага  нап-
равления). Регистр AX при этом не изменяется.

     Как и все строковые инструкции, инструкция  CMPS  имеет  две
формы  (для работы с байтами и для работы со словами), может уве-
личивать или уменьшать регистры SI и DI  и будет повторяться  при
наличии  префикса REP. Приведем пример фрагмента программы, в ко-
тором проверяется идентичность первых 50 элементов двух  массивов
элементов  размером  в слово, и для этого используется инструкция
CMPSW:

             .
             .
             .
             mov   si,OFFSET Array1
             mov   ax,SEG Array1
             mov   ds,ax
             mov   di,OFFSET Array2
             mov   ax,SEG Array2
             mov   es,ax
             mov   cx,50              ; сравнить первые 50
                                      ; элементов
             cd
             repe  cmpsw
             jne   ArraysAreDifferent ; массивы различны
; Первые 50 элементов совпадают.
             .
             .
             .
; В массивах отличаются по крайней мере два элемента.
ArraysAreDifferent:
             dec   si
             dec   si                 ; обеспечить, чтобы
             dec   di                 ; SI и DI указывали на
             dec   di                 ; отличающиеся
                                      ; элементы
             .
             .
             .



            Использование в строковых инструкциях операндов
-----------------------------------------------------------------

     Мы только что рассмотрели явные формы (для работы с  байтами
и  со словами) строковых инструкций. Другими словами, мы увидели,
как работают инструкции LODSB и LODSW, но не  использовали  инст-
рукцию LODS. Допускается также использование таких форм строковых
инструкций, где размер операнда явно  не  указывается.  При  этом
нужно обеспечить задание операндов таким образом, чтобы Турбо Ас-
семблер знал, работает он с байтами или со словами.

     Можно привести следующий пример, эквивалентный использованию
инструкции MOVSB:

             .
             .
             .
             .DATA
String1      LABEL   BYTE
             db      'abcdefghi'
STRING1_LENGTH   EQU   ($-String1)
String2      DB      50  DUP (?)
             .
             .
             .
             .CODE
             mov   ax,@Data
             mov   ds,ax
             mov   es,ax
             mov   si,OFFSET String1
             mov   di,OFFSET String2
             mov   cx,STRING1_LENGTH
             cld
             rep   movs  es:[String2],[String2]
             .
             .
             .

     После того, как вы в качестве операндов инструкции MOVS  за-
дадите  String1  и String2, Турбо Ассемблер использует в качестве
размера данных размер операндов  (в данном случае байт).

     Однако в строковых инструкциях использование  операндов име-
ет особый смысл. Операнды строковых инструкций -  это "не настоя-
щие" операнды в том смысле,  что  они  встроены  в  инструкцию, а
строковая инструкция  работает  в  соответствии  с указателями SI
и/или DI. Операнды используются только для задания  размера  дан-
ных, а не для действительной загрузки указателей. Взглянем на это
так: когда вы используете инструкцию типа:

             mov   al,[String1]

смещение String1 "встраивается" прямо в  инструкцию  на  машинном
языке, соответствующую инструкции MOV. Однако, когда вы использу-
ете инструкцию:

             lods   [String1]

инструкция на машинном языке будет занимать 1 байт и соответство-
вать  инструкции  LODSB:  Stirng1 в инструкцию не встраивается. В
этом случае вы должны обеспечить, чтобы регистры DS:SI  указывали
на начало String1.

     Операнды строковых инструкций аналогичны тем операндам,  ко-
торые  используются  в директиве ASSUME в качестве сегментов. Ди-
ректива ASSUME не устанавливает сегментный  регистр,  она  просто
сообщает  Турбо Ассемблеру, что вы установили сегментный регистр,
поэтому Турбо Ассемблер может выполнить для вас проверку на нали-
чие ошибок. Аналогично, операнды строковых инструкций не устанав-
ливают регистры, они просто указывают Турбо  Ассемблеру,  что  вы
установили  SI и/или DI, благодаря чему Турбо Ассемблер может оп-
ределить размер операнда и выполнить проверку на наличие  ошибок.
Дальнейшее  обсуждение операндов строковых инструкций приведено в
разделе "Операнды строковых инструкций".

     В разделе "Ошибки при работе со строковыми инструкциями"  мы
обсудим  некоторые  моменты,  касающиеся  использования строковых
инструкций.



              Программы, состоящие из нескольких модулей
-----------------------------------------------------------------

     Рано или поздно вы подойдете к тому,  что  хранить  исходный
текст  каждой  программы станет затруднительно. Использование для
исходного кода программы одного файла прекрасно подходит для  не-
больших программ, таких, как примеры данного руководства, но даже
программы среднего размера приходится разбивать на несколько фай-
лов  или  модулей,  которые ассемблируются отдельно и компонуются
вместе.  Основное преимущество программ,  состоящих из нескольких
модулей, состоит в том, что после того, как вы отредактируете ис-
ходный код,  вам потребуется переассемблировать только те модули,
которые вы изменили,  не затрагивая остальных модулей программы.
К тому же гораздо проще ориентироваться среди  нескольких  строк,
чем в одном большом файле.

     Создать программу, состоящую из  нескольких  модулей,  очень
легко.  Для обеспечения таких программ Турбо Ассемблер предусмат-
ривает три директивы: PUBLIC, EXTRN и GLOBAL.  Мы  рассмотрим  их
поочередно, но сначала мы проанализируем пример программы, состо-
ящей из двух модулей, после чего вам будет ясен контекст, в кото-
ром  мы  будет обсуждать указанные выше директивы. Основная прог-
рамма MAIN.ASM имеет следующий вид:

             DOSSEG
             .MODEL   SMALL
             .STACK   200h
             .DATA
String1               DB  'Добрый ',0
String2               DB  'день!',0dh,0ah,'$',0
             GLOBEL   FinalString:BYTE
FinalString           DB  50 DUP (?)
             .CODE
             EXTRN    ConcatenateStrings:PROC
ProgramStart:
             mov   ax,@Data
             mov   ds,ax
             mov   ax,OFFSET String1
             mov   bx,OFFSET String2
             call  ConcatenateStrings  ; объединение двух
                                       ; строк в одну строку
             mov   ah,9
             mov   dx,OFFSET FinalString
             int   21h                 ; печать строки-
                                       ; результата
             mov   ax,4ch
             int   21h                 ; выполнить
             END   ProgramStart

     А вот другой модуль программы, SUB1.ASM:

             DOSSEG
             .MODEL   SMALL
             .DATA
             GLOBAL   FinalString:BYTE
             .CODE
;
; Подпрограмма копирует сначала одну строку,
; затем другую, в строку FinalString
;
; Ввод:
;    DS:AX = указатель на первую копируемую строку
;    DS:BX = указатель на вторую копируемую строку
;
; Вывод: отсутствует
;
; Изменяемые регистры: AL, SI, DI, ES
;
             PUBLIC   ConcatenateStrings
ConcatenateStrings    PROC
             cld                   ; отсчет в прямом
                                   ; направлении
             mov   di,SEG FinelString
             mov   es,di
             mov   di,OFFSET FinelString ; ES:DI указывает
                                   ; на целевую строку
             mov   si,ax           ; первая строка для
                                   ; копирования в StringLoop
             lodsb                 ; получить символ строки 1
             and   al,al           ; это 0?
             jz    DoString2       ; да, со строкой 1 покончено
             stosb                 ; сохранить символ стоки 1
             jmp   StringLoop
DoString2:
             mov   si,bx           ; вторая строка для копиро-
                                   ; вания в цикле String2Loop
             lodsb                 ; получить символ строки 2
             stosb                 ; сохранить символ строки 2
                                   ; (включая 0, когда мы
                                   ; его встретим)
             and   al,al           ; это 0?
             jnz   String2Loop     ; нет, обработать следующий
                                   ; символ
             ret                   ; выполнено
ConcatenateString  ENDP
             END

     Эти два модуля можно ассемблировать отдельно с  помощью  ко-
манд:

       TASM main
и
       TASM sub1

а затем скомпоновать их в программу MAIN.EXE с помощью команды:

       TLINK main+sib1

     При запуске командой:

       main

программа MAIN.EXE, как можно догадаться, выводит на экран  стро-
ку:

        Добрый день!

     Теперь, когда вы увидели программу, состоящую из  нескольких
модулей,  в действии, давайте рассмотрим три директивы, обеспечи-
вающие такое программирование.



                           Директива PUBLIC
-----------------------------------------------------------------

     Действие директивы PUBLIC достаточно просто.  Она  указывает
Турбо  Ассемблеру, что соответствующую метку или метки нужно сде-
лать доступными для  других  модулей.  Здесь  можно  использовать
практически  любые метки, включая имена процедур и переменных па-
мяти, а также приравненные метки. Директива  PUBLIC  обеспечивает
доступность этих меток другим модулям. Например:

             .
             .
             .
             .DATA
             PUBLIC   MemVar, Array1, ARRAY_LENGTH
ARRAY_LENGTH EQU      100
MemVar       DW       10
Array1       DB       ARRAY_LENGTH DUP (?)
             .
             .
             .
             .CODE
             PUBLIC   NearProc, FarProc
NearProc     PROC  NEAR
             .
             .
             .
NearProc     ENDP          ; ближняя процедура
             .
             .
             .
FarProc      LABEL PROC    ; дальняя процедура
             .
             .
             .
             END

     Здесь имена приравненной метки, переменной размером в слово,
массива  и  процедуры  с ближнем типом обращения доступны другому
модулю, который будет компоноваться с данным модулем.

     Однако имеется один тип меток, которые нельзя сделать  обще-
доступными. Это приравненные метки, которые равны значениям-конс-
тантам с размерами,  отличными от 1 или 2 байт. Например, следую-
щие метки общедоступными сделать нельзя:

 LONG_VALUE  EQU   1000h
 TEXT_SYMBOL EQU   

     При ассемблировании Турбо Ассемблер  обычно  игнорирует  ре-
гистр буквы, поэтому все общедоступные метки преобразуются в про-
писные символы (верхний регистр). Если вы хотите, чтобы  в  обще-
доступных  метках различались буквы верхнего и нижнего регистров,
то при ассемблировании всех модулей, содержащих ссылки  на  обще-
доступные  метки, нужно использовать в командной строке Турбо Ас-
семблера параметр /ML или /MX.

     Например, без параметра /MX или /ML в других модулях следую-
щие две метки будут эквивалентными:

        PUBLIC    Symbol1, SYMBOL1

     При использовании для обеспечения  различимости  строчных  и
прописных букв в общедоступных и внешних идентификаторах парамет-
ра командной строки /MX нужно внимательно указывать буквы верхне-
го или нижнего регистров в директивах PUBLIC или EXTRN. Турбо Ас-
семблер делает доступным для других  модулей  тот  идентификатор,
который указывает в директиве PUBLIC или EXTRN, а не тот на кото-
рый делается ссылка или который переопределяется  внутри  модуля.
Например, директива:

        PUBLIC  Abc
 abC    Dw

приводит к тому, что общедоступным будет имя Abc, а не abC.

     Для каждого идентификатора в директиве  PUBLIC  можно  также
задать язык:  C, FORTRAN, PASCAL, BASIC, PROLOG и NOLANGUAGE (нет
языка).  Это приводит к тому, что к имени идентификатора до того,
как  одно  в объектном файле станет общедоступным,  автоматически
применяются правила конкретного языка. Например, если вы описали:

        PUBLIC C myprog

то идентификатор myprog в исходном  файле  станет  общедоступным,
как _myproc,  поскольку  по  соглашениям  языка  Си перед именами
идентификаторов следует символ подчеркивания. Использование иден-
тификатора языка в директиве PUBLIC временно отменяет текущее за-
дание языка (используемое по умолчанию или заданное в директиве .
MODEL). (Чтобы работало данное средство,  не  обязательно  должна
действовать директива .MODEL.)

                            Директива EXTRN
-----------------------------------------------------------------

     В последнем разделе, чтобы  сделать  метки  MemVar,  Array1,
ARRAY_LENGTH,  NearProc  и FarProc доступными для других модулей,
мы использовали директиву PUBLIC. На далее возникает вопрос,  ка-
ким образом другие модули могут ссылаться на эти метки?

     Для того, чтобы сделать метки из другого модуля доступными в
данном  модуле, используется директива EXTRN. После того, как ди-
ректива EXTRN будет использована, чтобы сделать доступным  в дан-
ном модуле метку из другого модуля,  эту метку можно использовать
также, как если бы она была определена в текущем  модуле.  Приве-
дем пример другого модуля, в котором директива EXTERN использует-
ся для ссылок на общедоступные метки, описанные в последнем  раз-
деле:

             .
             .
             .
             .DATA
             EXTRN   MemVar:WORD,Array1:BYTE,ARRAY_LENGTH:ABS
             .
             .
             .
             .CODE
             EXTRN  NearProc:NEAR,FarProc:FAR
             .
             .
             .
             mov   ax,[MemVar]
             mov   bx,OFFSET Array1
             mov   cx,ARRAY_LENGTH
             .
             .
             .
             call  NearProc
             .
             .
             .
             call  FarProc
             .
             .
             .

     Заметим, что все пять меток используются как обычно. Единст-
венное  отличие  от  программы на Ассемблере, состоящей из одного
модуля, является директива EXTRN.

     За каждой меткой, объявленной  в  директиве  EXTRN,  следует
двоеточие  и тип. Тип необходимо указывать, иначе Турбо Ассемблер
не будет знать какую именно  метку вы объявляете с помощью дирек-
тивы EXTRN.   За  одним  исключением   используемые  для  внешних
(external) меток типы совпадают с типами, которые могут использо-
ваться в директиве LABEL. Допустимы следующие типы:

   ABS           - абсолютное значение;
   BYTE          - переменная (данные) размером в байт;
   DWORD         - переменная (данные) размером в двойное
                   слово (4 байта);
   DATAPTR       - указатель на данные ближнего или дальнего ти-
                   па, в зависимости от модели памяти;

   FAR           - метка кода с дальним типом обращения
                   (переход осуществляется загрузкой
                   регистров CS:IP);
   FWORD         - 6-байтовая переменная (данные);
   NEAR          - метка кода с ближним типом обращения
                   (при переходе загружается только
                   регистр IP);
   PROC          - метка процедуры (NEAR или FAR, в
                   соответствии с директивой .MODEL);
   QWORD         - переменная (данные) размером в
                   четверное слово (8 байт);
   Имя структуры - имя определенного пользователем типа STRUC;
   TBYTE         - 10-байтовая переменная (данные);
   UNKNOWN       - неизвестный тип;
   WORD          - переменная (данные) размером в слово
                   (2 байта).

     Единственным незнакомым типом внешних  данных  является  тип
ABS, который используется для объявления метки, определенной в ее
исходном модуле с помощью директивы EQU или =.  Другими  словами,
это метка,  которая  просто представляет собой имя константы и не
связана с адресами кода или данных.

     Очень важно, чтобы для внешних меток вы задавали  корректный
тип данных, так как Турбо Ассемблер будет генерировать код на ос-
нове заданных вами типов данных, и у него нет другого способа оп-
ределить,  что  ваша  спецификация некорректна. Например, если вы
случайно ввели:

             .
             .
             .
             .CODE
             EXTRN   FarProc:NEAR
             .
             .
             .
             call   FarProc
             .
             .
             .

а в другом модуле содержится:

             .
             .
             .
             PUBLIC   FarProc
 FARPROC     PROC     FAR
             .
             .
             .
             ret
 FarProc     ENDP
             .
             .
             .

то Турбо Ассемблер в соответствие с типом данных, заданным вами в
директиве EXTRN,  сгенерирует  ближнее  обращение   к   процедуре
FarProc.  Можно  с  определенностью сказать,  что такая программа
корректно работать не будет,  поскольку FarProc на самом деле яв-
ляется  процедурой  с дальним типом обращения и завершается соот-
ветствующей инструкцией RET.

     Как уже описывалось в  последнем  разделе,  Турбо  Ассемблер
обычно  (по  умолчанию)  не  различает  верхний и нижний регистры
букв, поэтому общедоступные метки  преобразуются  в  верхний  ре-
гистр.  Это означает, что в обычном состоянии внешние метки будут
интерпретироваться в верхнем регистре. Если вы ходите,  чтобы  во
внешних  метках  регистры букв различались, используйте параметры
командной строки /ML или /MX.

     Для каждого идентификатора в директиве EXTRN вы можете также
задать язык:  C,  PASCAL,  BASIC,  FORTRAN, PROLOG или NOLANGUAGE
(в последнем случае язык не используется).  Это приводит к  тому,
что перед  тем,как имя станет в объектном файле общедоступным,  к
нему автоматически применяются правила указанного  языка.  Напри-
мер, если вы описали:

       EXTRN C myprog:NEAR

то идентификатор myprog в исходном файле преобразуется во внешний
идентификатор _myprog. Использование спецификатора языка в дирек-
тиве EXTRN  временно  отменяет текущее задание языка (который ис-
пользуется по умолчанию или задан  в  директиве  .MODEL).  (Чтобы
работало данное средство,  директива .MODEL не обязательно должна
действовать.)



                           Директива GLOBAL
-----------------------------------------------------------------

     Прочитав последние разделы, вы можете удивиться,  зачем  для
выполнения  одной  работы  (обеспечения совместного использования
меток разными модулями) нужны две директивы - PUBLIC и  EXTRN?  В
действительности единственная причина использования двух директив
заключается в необходимости обеспечить совместимость с более ран-
ними  ассемблерами.  В Турбо Ассемблере имеется директива GLOBAL,
которая делает все то, что делают директивы PUBLIC и EXTRN.

     Если с помощью данной директивы вы объявите  метку  глобаль-
ной,  а  затем  определите  ее  (с помощью директив DB, DW, PROC,
LABEL или других подобных директив), то  метка  станет  доступной
другим  модулям  аналогично тому, как если бы вы вместо директивы
GLOBAL использовали директиву PUBLIC. Если же вы, с другой сторо-
ны,  объявляете метку глобально, а затем используете ее без опре-
деления, то эта метка интерпретируется, как внешняя метка, анало-
гично тому, как если бы вы объявили ее с помощью директивы EXTRN.

     Рассмотрим, например, следующий фрагмент:

             .
             .
             .
             .DATA
             GLOBAL   FinalCount:WORD,PromptString:BYTE
FinalCount            DW  ?
             .
             .
             .
             .CODE
             GLOBAL   DoReport:NEAR,TallyUp:FAR
TallyUp      PROC     FAR
             .
             .
             .
             call  DoReport
             .
             .
             .

     Здесь метки FinalCount и  TallyUp  определены,  поэтому  они
становятся  общедоступными (для других модулей) метками (public).
Метки PromptString и DoReport не определены, поэтому подразумева-
ется,  что  это внешние (external) метки, которые объявлены обще-
доступными в других модулях.

     Директиву GLOBAL очень  удобно  использовать,  например,  во
включаемых  файлах  (эти  файлы  мы обсудим в следующем разделе).
Предположим, у вас есть множество меток, которые вы  хотите  сде-
лать доступными в программе (состоящей из нескольких модулей) для
других модулей. Неплохо было бы, если бы мы смогли определить все
эти  метки во включаемом файле, а затем включить этот файл в каж-
дый модуль. К сожалению, с помощью директив PUBLIC  и  EXTRN  это
невозможно, так как директива EXTRN не будет работать в том моду-
ле, в котором определена данная метка, а директива PUBLIC   будет
работать  только в том модуле, в котором данная метка определена.
Однако директива GLOBAL допустима во всех модулях, поэтому вы мо-
жете сформировать включаемый файл, где все нужные метки объявлены
глобальными, а затем включить данный файл во все ваши модули.

     Для каждого идентификатора в директиве GLOBAL, как и для ди-
ректив  PUBLIC или EXTRN,  можно также задать язык:  C,  FORTRAN,
PASCAL,  BASIC,  PROLOG и NOLANGUAGE (нет языка).  Это приводит к
тому,  что  к имени идентификатора до того,  как одно в объектном
файле  станет  общедоступным,  автоматически  применяются правила
конкретного языка. Например, если вы описали:

         GLOBAL C myprog

то идентификатор myprog в исходном  файле  станет  общедоступным,
как _myproc,  поскольку  по  соглашениям  языка  Си перед именами
идентификаторов следует символ подчеркивания. Использование иден-
тификатора языка в директиве PUBLIC временно отменяет текущее за-
дание языка (используемое по умолчанию или  заданное в  директиве
.MODEL). (Чтобы  работало данное средство,  не обязательно должна
действовать директива .MODEL.)



                           Включаемые файлы
-----------------------------------------------------------------

     Часто оказывается желательным включить один и  тот  же  блок
исходного кода Ассемблера в несколько исходных модулей. Вы можете
захотеть использовать в различных  модулях  одной  программы  ка-
кие-либо  присваивания  или  макрокоманды,  или использовать их в
разных программах. При этом пришлось бы написать длинную програм-
му, которую нежелательно разбивать на несколько компонуемых моду-
лей (например, программу, которая должна записываться в ПЗУ),  но
такая  программа слишком велика и ее неудобно будет хранить в од-
ном файле. В этом случае чрезвычайно удобной оказывается директи-
ва INCLUDE.

     Когда Турбо Ассемблер  встречает  директиву  INCLUDE  (вклю-
чить), он помечает это место в текущем модуле Ассемблера, обраща-
ется к диску и находит указанный включаемый файл и  начинает  ас-
семблирование  этого  файла,  как  если бы все строки включаемого
файла были записаны прямо в исходном модуле. При достижении конца
включаемого  файла Турбо Ассемблер возвращается к строке, следую-
щей в исходном модуле за директивой INCLUDE, и  возобновляет  ас-
семблирование  с  этой точки. Таким образом, там, где встречается
директива INCLUDE, текст включаемого файла включается в ассембли-
рование текущего исходного модуля Ассемблера.

     Например, если файл MAINPROG.ASM содержит:

             .
             .
             .
             .CODE
             mov   ax,1
             INCLUDE   INCPROG.ASM
             push  ax
             .
             .
             .

а файл INCPROG.ASM содержит:

             mov   bx,5
             add   ax,bx

то результат ассемблирования файла MAINPROG.ASM будет в  точности
эквивалентен ассемблированию кода:

             .
             .
             .
             .CODE
             mov   ax,1
             mov   bx,5
             add   ax,bx
             push  ax
             .
             .
             .

     Допускается вложенность  включаемых  файлов  на произвольную
глубину,  другими словами, включаемые файлы также могут содержать
директивы INCLUDE (включать другие файлы). Включенные строки мож-
но легко обнаружить в файле листинга,  так  как  Турбо  Ассемблер
слева  от  каждой  включенной строки помещает номер,  указывающий
глубину вложенности файлов модулей (включаемые файлы  могут иметь
произвольную вложенность).

     Откуда Турбо Ассемблер знает, где искать  включаемые  файлы?
Если  в  операнде директивы INCLUDE, определяющим имя включаемого
файла, укажете диск или маршрут доступа к файлу, то Турбо Ассемб-
лер будет искать файл только в указанном вами месте.  Если же  вы
зададите  имя  файла без указания маршрута и диска,  то Турбо Ас-
семблер сначала ищет файл в текущем каталоге.  Если он  не  может
найти  заданный файл в текущем каталоге,  то поиск продолжается в
каталогах, заданных в параметре командной строки -I (если он ука-
зывается). Например, при задании команды:

           TASM  -ic:\include testprog

и строки:

           INCLUDE    MYMACROS.ASM

(в файле TESTPROG.ASM) Турбо Ассемблер будет сначала искать в те-
кущем  каталоге файл MYMACROS.ASM, а не найдя его, выполнит поиск
в каталоге C:\INCLUDE. Если файл MYMACROS.ASM не будет  найден  и
там, то Турбо Ассемблер выведет сообщение об ошибке.

     Кстати, в спецификации маршрута в  директиве  INCLUDE  можно
указывать  обратную  косую  черту (\). Это обеспечивает совмести-
мость с MASM.

     Включаемые файлы полезно использовать для  обеспечения  дос-
тупности  ваших библиотек и макрокоманд в самых различных модулях
на языке Ассемблера. Включаемые файлы также оказываются очень по-
лезными при совместном использовании в различных модулях програм-
мы строковых присваиваний, объявлений глобальных меток и  сегмен-
тов  данных. Во включаемых файлах исходный код Ассемблера исполь-
зуется редко, поскольку отдельные  модули  исходного  кода  можно
просто  компоновать вместе, но во включаемых файлах вполне допус-
тимо использовать строки исходного кода (любые допустимые  строки
Ассемблера).



                            Файлы листинга
-----------------------------------------------------------------

     Обычно Турбо Ассемблер создает в результате  ассемблирования
только объектный  файл  (файл  с расширением .OBJ),  имя которого
совпадает с именем исходного (.ASM) файла.

     Если вы хотите, можно также указать  Турбо  Ассемблеру,  что
нужно создать файл листинга (с расширением .LST). Для этого в ко-
мандной строке просто вводятся две  дополнительные  запятые  (или
имени файла). Например, если команда:

               TASM  hello

ассемблирует файл HELLO.ASM и создает объектный  файл  HELLO.OBJ,
то командная строка:


               TASM  hello,,

генерирует файл листинга HELLO.LST.  Вместо  последней  командной
строки можно использовать следующие эквивалентные команды:

               TASM  hello,hello,hello
 и
               TASM  /L hello

     Результат при этом будет тот же.

     Имена объектного файла и/или файла листинга  не  обязательно
должны  совпадать с именем исходного файла, однако довольно редко
возникает необходимость задавать для них разные имена.

     Основу содержания файла листинга составляет  содержание  ис-
ходного  файла,  дополненного различной информацией о результатах
ассемблирования.  Для  каждой  исходной  строки  Турбо  Ассемблер
включает  в  листинг  соответствующую инструкцию машинного кода и
смещение каждой строки машинного кода в текущем  сегменте.  Кроме
того,  Турбо  Ассемблер выводит в файле листинга таблицы, где со-
держится информация о метках и сегментах, используемых в програм-
ме,  включая  значение и тип каждой метки и атрибуты каждого сег-
мента.

     Турбо Ассемблер может также (по  запросу)  генерировать  для
всех меток исходного файла таблицу перекрестных ссылок, в которой
указывается, где была определена каждая метка и где на  нее  есть
ссылка (см. описание параметра командной строки /C в Главе 3).

     Рассмотрим сначала основные элементы листинга -  ассемблиро-
ванный машинный код и смещение каждой инструкции.



                         Пример файла листинга
-----------------------------------------------------------------

     Приведем листинг примера программы HELLO.ASM.

Turbo Assembler Version 2.0    06-29-90  16:21:27          Page 1

Hello.ASM

1                         DOSSEG
2 0000                    .MODEL SMALL
3 0000                    .STACK 100h
4 0100                    .DATA
5 0000  48 65 6C 6C 6F 2C 20 + Message DB 'Hello, word',13,10,12
6       77 6F 72 6C 64 0D 0A +
7       0C
8       = 000F            HELLO_MESSAGE_LENGTH EQU $-Message
9 000F                    .CODE
10 0000 B8 0000s          mov  ax,@Data
11 0003 8E D8             mov  ds,ax   ; установить DS в значение
12                                     ; сегмента данных
13 0005 B4 40             mov  ah,40h  ; функция DOS вывода на
14                                     ; устройство
15 0007 BB 0001           mov  bx,1    ; стандартный указатель
16                                     ; вывода
17 000A B9 000F           mov  cx,HELLO_MESSAGE_LENGTH ; число
18                                     ; выводимых символов
19 000D BA 000F           mov  dx,OFFSET Message ; выводимая
20                                     ; строка
21 001D CD 21             int  21h     ; вывести "Hello"
22 0012 B4 4C             mov  ah,4ch  ; функция DOS завершения
23                                     ; программы
24                        END

Turbo Assembler  Version 2.0    06-29-90  16:21:27         Page 2

Symbol Table

Symbol Name                     Type       Value

??DATE                          Text       "06-29-88"
??FILENAME                      Text       "HELLO   "
??TIME                          Text       "16:21:26"
??VERSION                       Number     004A
@CODE                           Text       _TEXT
@CODESIZE                       Text       0
@CPU                            Text       0101H
@CURSEG                         Text       _TEXT
@DATA                           Text       DGROUP
@DATASIZE                       Text       0
@FILENAME                       Text       HELLO
@WODRSIZE                       Text       2
MESSAGE                         Byte       DGROUP:0000
HELLO_MESSAGE_LENGTH            Number     000F

Groups  &  Segments      Bit  Size  Align  Combine  Class

DGROUP                   Group
  STASK                  16   0100  Para   Stack    STASK
  _DATA                  16   000F  Word   Public   DATA
_TEXT                    16   0016  Word   Public   CODE

     В верхней части каждой страницы листинга  выводится  заголо-
вок, состоящий из версии Турбо Ассемблера (с помощью которого вы-
полнено ассемблирование файла), даты и времени ассемблирования  и
номера страницы листинга.

     Листинг состоит из двух частей: расширенного исходного  кода
и  таблицы  идентификаторов (Symbol Table). Сначала выводится ис-
ходный код Ассемблера, с заголовком и имя файла, в котором  нахо-
дится исходный код. Исходный код Ассемблера сопровождается инфор-
мацией о машинном коде инструкций, из которых Турбо Ассемблер вы-
полнил  трансляцию. Все ошибки и предупреждения, обнаруженные при
ассемблировании, включаются  в  листинг  непосредственно  за  той
строкой, где они встретились.

     Строки кода в листинге имеют следующий формат:

<глубина> <номер_строки> <смещение> <машинный_код> <исходный_код>

     "Глубина" указывает уровень вложенности включаемых файлов  и
макрокоманд в вашем файле листинга.

     "Номер_строки" представляет собой номер строки файла листин-
га  (исключая  строки заголовка и титульные строки). Номера строк
особенно полезны при использовании средства Турбо Ассемблера  ге-
нерации  перекрестных  ссылок,  где  в ссылках указываются номера
строк. В файле HELLO.LST директива DOSSEG содержится на строке  1
файла листинга, директива .MODEL - на строке 2 и т.д.

     Учтите, что номера строки в поле "номер_строки" - это не но-
мера строк исходного модуля. Например, при расширении макрокоман-
ды или включении файла отсчет строк  продолжается,  хотя  текущая
строка  в  исходном  файле остается той же. Чтобы перевести номер
строки (сгенерированный, например, при создании перекрестных ссы-
лок),  вы должны найти соответствующую строку в листинге, а затем
(по номеру или на глаз) найти ее в исходном файле.

     "Смещение" - это смещение в текущем сегменте (от начала) ма-
шинного  кода,  генерируемого  соответствующей  строкой исходного
кода. Например, Message начинается со смещения 0 в сегменте  дан-
ных.

     "Машинный_код" представляет собой действительную  последова-
тельность шестнадцатиричного  значения  байт и слов,  которые ас-
семблируются из соответствующей исходной строки Ассемблера.  Нап-
ример, инструкция MOV AX,@Data начинается по смещению 0 в сегмен-
те кода.  Информация справа от данной инструкции -  это  машинный
код,  в который ассемблируется инструкция, то есть инструкция MOV
AX,@Data ассемблируется в B8 0000s (в шестнадцатиричном представ-
лении).  0B8h  - это инструкция на машинном языке,  загружающая в
регистр AX значение-константу,  а 0000s - это постоянное значение
@Data,  которое загружается в AX. Вся инструкция MOV AX,@Data ас-
семблируется в три байта машинного кода.

     Заметим, что в файле листинга указано, что следующая за  MOV
AX,@Data инструкция (которой является инструкция MOV DS,AX) начи-
нается со смещения 3 в сегменте кода. И это имеет совершенно чет-
кий смысл, поскольку инструкция MOV AX,@Data начинается со смеще-
ния  0  и  имеет  длину  3  байта.  Машинные код,  получающийся в
результате ассемблирования инструкции (8 D8) имеет длину 2 байта,
поэтому  следующая инструкция начинается со смещения 5.  Взглянув
на файл листинга, мы можем убедиться, что это именно так.

     Как вы можете заметить, в файле листинга за показаны  только
первые 7 байт машинного кода (за которыми следует символ +),  ге-
нерируемые строкой:

Message      DB    'Hello, word',13,10,'$'

     Поля машинного кода, которые имеют  слишком  большую  длину,
чтобы уместиться в поле "машинный_код", обычно усекаются и завер-
шаются символом +, что говорит о том, ассемблированы дополнитель-
ные  байты,  но  они  не указаны. Если вам необходимо увидеть все
байты машинного кода, можно использовать  директиву  %NOTHING  (о
ней  мы расскажем дальше). При указании данной директивы не вмес-
тившийся код будет переноситься на следующие строки.

     Наконец,поле "исходный_код" - это просто исходная строка Ас-
семблера (вместе с комментариями). Некоторые строки на  Ассембле-
ре (например, строки, содержащие только комментарии) не генериру-
ют  никакого машинного кода, и поля "смещение" и "машинный_код" в
таких строках отсутствуют. Тем не менее номер строки им  присваи-
вается.

     Значение 0000s, соответствующее @Data, это только  "замести-
теть" для действительного значения инструкции:

              mov   ax,@Data

     Это происходит потому, что значения сегментов  присваиваются
компоновщиком, а не Турбо Ассемблером, поэтому Турбо Ассемблер не
может занести сюда корректное значение. Все,  что  может  сделать
Турбо  Ассемблер - это дать вам понять, что данное значение явля-
ется значением сегмента, которое будет  вычислено  компоновщиком.
Об  этом говорит буквы s в конце генерируемого данной инструкцией
машинного кода.

     Аналогично, смещение в машинном коде, полученном из инструк-
ции:

             mov   dx,OFFSET Message

завершается буквой r, которая указывает, что смещение может  быть
перемещаемым внутри сегмента при комбинировании его компоновщиком
с другими сегментами.

     Приведем полный список обозначений, используемых  Турбо  Ас-
семблером  для указания характеристик ассемблирования (таких, как
переместимость):

-----------------------------------------------------------------
Обозначение              Значение
-----------------------------------------------------------------
   r           Указывает тип коррекции смещения идентификаторов
               в модуле.

   s           Указывает тип коррекции сегментов для идентифика-
               торов  в модуле.

   sr          Указывает тип коррекции смещений и сегментов для
               идентификаторов в модуле.

   e           Показывает коррекцию смещения для внешних иденти-
               фикаторов.

   se          Показывает коррекцию указателя для внешних иден-
               тификаторов.

   so          Показывает коррекцию только сегмента.

   +           Показывает, что объектный код усечен.
-----------------------------------------------------------------

     В листинге объектного кода обозначения r, s и sr используют-
ся  для обозначения типа коррекции смещения, сегмента и указателя
(сегмента и смещения) для идентификаторов модуля.  Обозначение  e
показывает  коррекцию  смещения для внешних идентификаторов, а нe
указывает коррекцию указателя внешнего идентификатора.  Коррекция
сегментов  для внешних идентификаторов (обозначаемая, как s) ана-
логична локальным идентификаторам. Объектный код может также  со-
держать  в  последнем  столбце символ +, указывающий, что имеется
дополнительный объектный код, который нужно вывести, но  он  усе-
чен.

     Самое левое поле листинга представляет собой счетчик уровня.
При  ассемблировании из основного файла это поле остается пустым.
При ассемблировании из включаемых файлов это поле принимает  зна-
чение 1 или 2, 3 и т.д., в зависимости от уровня вложенности каж-
дого включаемого файла. Тоже самое происходит при расширении мак-
рокоманд.

     Как вы можете заметить, в файле листинга некоторые записи  в
машинном  коде  показаны, как байтовые значения (две шестнадцати-
ричные цифры), а другие - как значения, размеров в слово. В  этом
есть  определенная  логика: когда Турбо Ассемблер транслирует ма-
шинный код, представляющий собой значение размером в слово  (нап-
ример, OFFSET Message, представляющее собой 16-битовое смещение),
то это смещение показывается, как значение размером в слово.  Это
бывает очень полезно, поскольку в противном случае использующийся
в процессоре 8086 для хранения слов механизм "младший  байт  пер-
вым" приводил бы к тому, что порядок байт в словах был бы изменен
на обратный.

     Например, инструкция:

              mov   ax,1234h

ассемблируется в 3 байта машинного кода: 0B8h, 034Hh  и  012h  (а
таком  порядке).  Если  Турбо  Ассемблер выведет эту инструкцию в
виде трех байт, то она будет показана, как:

   B8 34 12

     При этом байты значения размером в слово будет переставлены.
Турбо Ассемблер выводит такой машинный код, как:

   B8 1234

что определенно легче читается.

     Когда мы рассказывали о поле "смещение", мы уже  говорили  о
смещении в текущем сегменте меток и строк программы. Откуда же мы
можем узнать, в каком именно сегменте находится метка? Для  этого
служат таблицы листингов, о которых мы далее расскажем.




Таблицы идентификаторов листинга
-----------------------------------------------------------------

     Вторая часть файла листинга начинается с  заголовка  "Symbol
Table"  (таблица идентификаторов). Эта часть состоит из двух таб-
лиц, в одной из которых описываются используемые в исходном  коде
метки, а в другой перечисляются используемые сегменты.

     Кстати, если вы не хотите, чтобы в  листинге  генерировалась
таблица  идентификаторов, вы можете указать Турбо Ассемблеру, что
нужно генерировать только расширенный  листинг  кода  (это  можно
сделать с помощью параметра командной строки /N).



                             Таблица меток
-----------------------------------------------------------------

     В первой таблице, которую мы называем таблицей меток, приве-
ден  список  всех  меток исходного кода (в алфавитном порядке), а
также также их типы и значения. Например, файл листинга HELLO.LST
содержит следующую запись:

 MESSAGE        BYTE        DGROUP:0000

     Здесь  MESSAGE - это имя метки или идентификатор. Оно указы-
вается прописными буквами, так как если вы не указываете парамет-
ры командной строки /MX или /ML, Турбо Ассемблер преобразует  все
идентификаторы  в  верхний  регистр. BYTE указывает размер данных
для того  элемента  данных,  на  который  ссылается  имя  Message
(байт). DGROUP:0000 - это значение метки Message, означающее, что
эта метка начинается со смещения  0  в  сегменте  данных  DGROUP.
(Помните, однако, что ссылка на Message в последнем разделе поме-
чена символом r. Это означает, что метка  может  быть  перемещена
компоновщиком  по другому смещению, когда в программе выполняется
компоновка других сегментов  DGROUP.  Информация  о  перемещениях
сегментов  содержится в файле карты памяти, создаваемом компонов-
щиком.)

     Аналогично, ProgramStart показана, как метка  ближнего  типа
со  значением _TEXT:0000.  _TEXT представляет собой имя сегмента,
определенного с помощью  директивы  .CODE,  поэтому  ProgramStart
расположена  по  первому  адресу  в сегменте кода. Мы ответили на
возникавший ранее вопрос о том, как  можно  определить,  в  каком
сегменте находится каждая метка, так как это указывается значени-
ем соответствующего поля таблицы меток (поля Value).

     Другие перечисленные в листинге файла HELLO.ASM метки -  это
метки, предопределенные Турбо Ассемблером при использовании упро-
щенных директив определения сегментов. Все эти метки устанавлива-
ются  в  значения,  соответствующие текстовым строкам, и содержат
такие значения, как _TEXT и DGROUP  (поле  Value  таблицы  Symbol
Table).

     Метки могут иметь один из следующих типов данных  (см.  поле
Type):

       ABS           DWORD           NUMBER          TBYTE
       ALIAS         FAR             QWORD           TEXT
       BYTE          NEAR            STRUCT          WORD

     Как мы обсуждали в начале данной главы, с помощью присваива-
ния метки можно приравнять к любому постоянному значению или тек-
стовой строке.  В поле значения (Value) таблицы меток указываются
те значения меток,  которые вы задали. Для меток, связанных с ад-
ресами памяти (таких,  как Message),  в поле значения указывается
адрес метки.

     Таблица меток - это то место в листинге, где можно найти ин-
формацию  о типе и значении каждой метки, использованной в исход-
ном коде.



                       Таблица сегментов и групп
-----------------------------------------------------------------

     Другой таблицей в этой части листинга является таблица  сег-
ментов и групп  (Groups & Segments). Группы сегментов, такие, как
DGROUP, просто указываются здесь, как  группы,  поскольку  группы
сегментов  сами не имеют атрибутов, а состоят из одного или более
сегментов. Сегменты, образующие в данном модуле группу,  указыва-
ются в таблице сегментов и групп непосредственно под именем груп-
пы, при этом два предшествующих пробела показывают их  принадлеж-
ность к группе. В файле HELLO.LST сегменты STACK и _DATA являются
членами группы сегментов DGROUP.

     Сегменты имеют атрибуты, и в таблице сегментов и  групп  для
каждого сегмента приведен список из 5 атрибутов. Если читать сле-
ва, то в таблице указываются следующие  атрибуты:  размер  данных
(Bit),  общий размер (Size), выравнивание (Align), тип комбиниро-
вания (Combine) и класс (Class). Рассмотрим каждый из них  в  от-
дельности.

     Размер данных всегда равен 16 (за исключением сегментов, ас-
семблируемых  с  директивой  USE32  для процессора 80386; об этом
рассказывается в Главе 9).

     Размер сегмента задается в  виде  четырех  шестнадцатиричных
цифр. Например,  сегмент STACK имеет размер 0200h байт (512 в де-
сятичном виде).

     Тип выравнивания описывает, на какой  границе  памяти  может
начинаться сегмент. Имеются следующие типы выравнивания:

   BYTE        - сегмент может начинаться с любого адреса;
   DWORD       - сегмент может начинаться с любого адреса,
                 кратного 4;
   PAGE        - сегмент может начинаться с любого адреса,
                 кратного 256;
   PARA        - сегмент может начинаться с любого адреса,
                 кратного 16 (выравнивается на границу слова);
   WORD        - сегмент может начинаться с любого четного
                 адреса.

     В файле HELLO.LST сегмент STACK начинается на границе параг-
рафа,  а  сегменты  _DATA  и _TEXT выравниваются на границу слова
(более подробная информация о выравнивании приведена в Главе 9).

     Тип комбинирования определяет, как сегменты с таким же  име-
нем  будут комбинироваться с данным сегментом. Например, сегменты
с идентичными именами с типом комбинирования PUBLIC  объединяются
(конкатенируются)  в  один  сегмент  большего размера, а если эти
сегменты будут иметь тип комбинирования COMMON, то они будут сли-
ваться в один общий сегмент (перекрытие). Более подробно о комби-
нировании типов и классов сегментов рассказывается в Главе 9.

     Наконец, класс  сегмента определяет общий класс,  к которому
принадлежит сегмент (например, CODE, DATA или STACK). Компоновщик
использует эту информацию для упорядочивания сегментов при компо-
новки их в программу (см. Главу 9).



                      Таблица перекрестных ссылок
-----------------------------------------------------------------

     В таблице идентификаторов файла  листинга  приводится  масса
информации  о метках, группах и сегментах, но имеется 2 вещи, ко-
торые здесь не указаны: где определены метки, группы и сегменты и
где  они используются. Другими словами, в таблице идентификаторов
отсутствуют перекрестные ссылки для меток, групп и сегментов. Ин-
формация  о перекрестных ссылках облегчает нахождение меток и от-
слеживание поэтапного выполнения программы при ее отладке.

     Существует два способа указания Турбо Ассемблеру на  необхо-
димость  генерации в конце файла листинга информации о перекрест-
ных ссылках. Одним из них является параметр командной строки  /C,
например:

              TASM  /c  hello,,

     При этом в файле листинга HELLO.LST будет генерироваться ин-
формация  о  перекрестных ссылках. Заметим, однако, что сам пара-
метр /C недостаточен  для  генерации  информации  о  перекрестных
ссылках. Вы должны также указать Турбо Ассемблеру, что нужно соз-
давать файл листинга, в который эта информация будет помещена.

     Запросить у Турбо Ассемблера необходимость генерации  инфор-
мации  а  перекрестных ссылках можно также с помощью добавления в
командной строке четвертого поля, например:

              TASM  hello,hello,hello,hello
 или
              TASM  hello,,,

     Предположим, вы ассемблируете файл REVERSE.ASM  (см.  вторую
программу в Главе 5), указывая в командной строке параметр /C:

               TASM  /C reverse,,

     Турбо Ассемблер создает следующий файл  листинга  (с  именем
REVERSE.LST):

1                    DOSSEG
2                    .MODEL SMALL
3                    .STACK 100h
4                    .DATA
5     = 03EB     MAXIMUM_STRING_LENGTH  EQU  1000
6 0000 03EB*(??) StringToReverse DB MAXIMUM_STRING_LENGTH  DUP
 (?)
7 03E8 03E8*(??) ReverseString   DB MAXIMUM_STRING_LENGTH  DUP
 (?)
8                    .CODE
9                ProgramStart:
10 0000 B8 0000s     mov    ax,@Data
11 0003 8E D8        mov    dx,ax           ; установить регистр
                                              DS таким образом,
                                              чтобы он указывал
                                              на сегмент данных
13 0005 B4 3F        mov    ah,3fh          ; функция DOS чтения
                                              ввода
14 0007 ЕЕ 0000      mov    bx,0            ; описатель стандарт-
                                              ного ввода
15 000A B9 03E8      mov    cx,MAXIMUM_STRING_LENGTH ; считано до
16                                          ; максимального
                                              числа символов
17 000D BA 0000r     mov    dx,OFFSET StringToReverse ; сохранить
18                                          ; строку
19 0010 OD 21        int    21h             ; получить строку
20 0012 23 C0        and    ax,ax           ; были считаны
                                              символы?
21 0014 74 1F        jz     Done            ; нет, конец
22 0016 8B C8        mov    cx,ax           ; поместить длину
23                                          ; строки в регистр
                                              СХ, который
                                              можно использовать,
                                              как счетчик
24 0018 51           push   cx              ; сохранить в стеке
                                              длину строки
25 0019 BB 0000r     mov    bx,OFFSET StringToReverse
26 001C BE 03E8r     mov    si,OFFSET ReverseString
27 001F 03 F1        add    si,cx
28 0021 4E           dec    si              ; указывает на конец
29                                          ; буфера строки
30                ReverseLoop:
31 0022 8A 07        mov    al,[bx]         ; получить следу-
                                              ющий символ
32 0024 88 04        mov    [si],al         ; сохранить символы
                                              в обратном
                                              порядке
33 0026 43           inc    bx              ; указатель на
                                              следующий символ
34 0027 4E           dec    si              ; указатель на
35                                          ; предыдущую ячейку
                                              buffer
36 0028 E2 F8        loop   ReverseLoop     ; переместить
                                              следующий символ,
                                              если он имеется
37 002Д 59           pop    cx              ; извлечь длину
                                              строки
38 002Е Е4 40        mov    ax,40h          ; функция записи
                                              DOS
39 002D BB 0001      mov    bx,1            ; описатель
                                              стандартного
                                              вывода
40 00030 ЕД 03З8у    mov    dx,OFFSET ReverseString ; напечатать
                                              строку
41 0037 ЕЖ 21        кпх    21й             ; напечатать строку
42                Done:
43 0035 B4 4C        mov    ah,4ch          ; функция DOS
                                              завершения
                                              программы
44 0037 ЕЖ 21        int    21h             ; завершить
                                              программу
45                   END

Symbol Table

Symbol Name             Type      Value       Cref defined at #

@Code                   Text      _TEXT       #2  #8
@Curseg                 Text      _TEXT       #2  #3  #4  #8
DONE                    Near      _TEXT:0035  21  #42
MAXIMUM_STRING_LENGTH   Number    03E8        #5  6  7  15
PROGRAMSTART            Near      _TEXT:0000  #9  45
REVERSELOOP             Near      _Text:0022  #30 36
REVERSESTRING           Byte      DGROUP:03E8 #7 26 40
STRINGTOREVERSE         Byte      DGROUP:0000 #6 17 25

Groups & Segments  Bit  Size Align Combine Class  Cref defined at

DGROUP             Group                             #2 2 10
  STASK            16   0200 Para  Stack   STASK  #3
  _DATA            16   07D0 Word  Public  DATA   #2 #4
_TEXT              16   0039 Word  Public  CODE   #2 2 #8 8

     Этот файл листинга также содержит расширенный исходный код и
таблицы  идентификаторов. Однако в таблице идентификаторов появи-
лось новое поле - поле  перекрестных  ссылок  (поле  с  названием
"Cref defined at").

     В поле перекрестных ссылок указываются для каждого идентифи-
катора (метки, группы или сегмента) номера всех тех строк в прог-
рамме, где имеется ссылка на данный идентификатор. Перед  строка-
ми, на которых был определен идентификатор, указывается символ #.

     Например, давайте найдем, где  определяется  и  используется
метка  MAXIMUM_STRING_LENGTH.  В  файле листинга указывается, что
она была определена на строке 5. Если вы  посмотрите  на  листинг
исходного кода, то убедитесь, что это именно так. (Отметим, кста-
ти,   что   в   таблице   меток   указывается,    что    значение
MAXIMUM_STRING_LENGTH  представляет собой число 03Е8h, десятичное
значение которого 1000.)

     В поле  перекрестных  ссылок для метки MAXIMUM_STRING_LENGTH
также указывается,  что ссылки на эту метку  (но  не  определения
метки)  имеются  на  строках 6,  7 и 15. Если взглянуть но первую
часть листинга, то можно увидеть, что это так.

     Нужно иметь в виду, что между таблицей идентификаторов с пе-
рекрестными  ссылками и таблицей идентификаторов без перекрестных
ссылок существует одно отличие. Идентификатор, который был  опре-
делен,  но  на  которые нет ссылок в программе, в таблице меток с
перекрестными ссылками показан не будет,  поскольку  перекрестные
ссылки на него отсутствуют. Однако такой идентификатор указывает-
ся в обычной таблице идентификаторов (без перекрестных ссылок).

     Разрешить генерацию перекрестных ссылок для всего файла мож-
но  с помощью параметра командной строки /C. Можно с уверенностью
сказать, что вы не захотите получать листинг перекрестных  ссылок
для  каждого  идентификатора.  Для  больших исходных файлов такие
листинги будут иметь огромные размеры. В Турбо Ассемблере предус-
мотрены  директивы,  позволяющие вам разрешать и запрещать перек-
рестные ссылки для выбранных частей исходного файла.

     Директива %CREF разрешает разрешает генерацию для  последую-
щих строк текста перекрестных ссылок. Директивы %NOCREF запрещает
их генерацию. Любая из этих директив отменяет действие, указанное
в  командной  строке  с  помощью  параметра /C. Если перекрестные
ссылки разрешены где-либо в исходном модуле, то в таблице иденти-
фикаторов указываются строки, на которых были определены все сег-
менты, группы и метки. Однако в  записи  о  перекрестных  ссылках
приводятся  только  те строки, на которых имеются ссылки на соот-
ветствующие сегменты, группы и метки и  для  которых  в  исходном
файле разрешена генерация перекрестных ссылок.

     Рассмотрим, например, следующий фрагмент программы:

            .
            .
            .
            #NOCREF
 ProgrammStart    PROC                    ; строка 1
            .
            .
            .
            jmp   LoopTop                 ; строка 2
            .
            .
            .
            #CREF
 LoopTop:                                 ; строка 3
            .
            .
            .
            loop  LoopTop                 ; строка 4
            #NOCREF
            mov   ax,OFFSET ProgramStart  ; строка 5
            .
            .
            .

     Для метки ProgramStart строка 1 будет  указана,  как  строка
определения (с символом #), хотя она и находится в области, гене-
рация перекрестных ссылок "выключена". Это происходит потому, что
если где-либо в модуле генерация перекрестных ссылок задается, то
в перекрестных ссылках указываются все строки определения  меток.
Аналогично,  строка 3 будет указана, как строка определения метки
LoopTop.

     Строка 4 будет указана в перекрестных ссылках  для  LoopTop,
так  как  она  находится после директивы %CREF и перед директивой
%NOCREF. Однако, строка 2 в перекрестных ссылках для LoopTop ука-
зана не будет, потому что она находится в зоне запрещения генера-
ции перекрестных ссылок. По той же причине в перекрестных ссылках
для ProgramStart не будет указана строка 5.

     Для совместимости с другими ассемблерами в Турбо  Ассемблере
предусмотрены  директивы  .CREF  и .XCREF, управляющие генерацией
перекрестных ссылок аналогично директивам %CREF и %NOCREF.




Управление содержимым и форматом листингов
-----------------------------------------------------------------

     Турбо Ассемблер предоставляет вам широкие возможности по уп-
равлению выводом в листингах строк исходного кода и форматом лис-
тинга в целом. Директивы управления листингом можно разделить  на
две категории:  директивы  управления содержимым листинга,  с по-
мощью которых выбирается включаемая в файл листинга информация, и
директивы управления форматом листинга,  определяющие формат фай-
лов листинга.


Директивы управления содержимым листинга
-----------------------------------------------------------------

     Директивы управления содержимым листинга разрешают или  зап-
рещают  включение в файл листинга отдельных строк. В общем случае
эти директивы полезно использовать для подавления вывода  в  файл
листинга той информации, которая вас в данный момент не интересу-
ет, что позволяет уменьшить объем файла листинга и упростить  ра-
боту с ним.

                       Директивы %LIST и %NOLIST
-----------------------------------------------------------------

     Директивы %LIST и %NOLIST - это основные директивы, управля-
ющие выводом строк листинга.  Они разрешают (директива %LIST) или
запрещают (директива %NOLIST) включение последующих строк листин-
га в файл листинга. Например, при указании директив:

            .
            .
            .
            %NOLIST
            mov   ax,1
            %LIST
            mov   bx,2
            %NOLIST
            add   ax,bx
            .
            .
            .

в файл листинга будет включена только средняя строка mov bx,2. По
умолчанию выбирается директива %LIST.

                       Директивы %COND и %NOCOND
-----------------------------------------------------------------

     Директивы %COND и %NOCOND позволяют вам разрешать (директива
%COND) или запрещать (директива %NOCOND) включение в листинг бло-
ков условного ассемблирования с неудовлетворенным условием.  Лис-
тинг  таких  блоков обычно (по умолчанию) подавляется.  Например,
при наличии директив:

       .
       .
       .
     %CONDS
     IFE IS8086
       shl ax,7
     ELSE
       mov cl,7
       shl ax,cl
     ENDIF
       .
       .
       .

в файл листинга будут помещены оба условных блока вместе   дирек-
тивами условного ассемблирования, а не только тот  блок условного
ассемблирования, который во время трансляции имеет истинное  зна-
чение.


Директивы %INCL и %NOINCL
-----------------------------------------------------------------

     Директивы %INCL и %NOINCL позволяют вам разрешать (директива
%INCL) или запрещать (%NOINCL) вывод в листинге строк, включаемых
из других файлов по директиве INCLUDE.  По умолчанию вывод в лис-
тинге включаемого текста разрешен. Например, по директивам:

            .
            .
            .
            %NOINCL
            INCLUDE   HEADER.ASM
            %INCL
            INCLUDE   INIT.ASM
            .
            .
            .

строки, включаемые из файла HEADER.ASM, не будут помещены в  файл
листинга,  а  строки из файла INIT.ASM - будут (однако в листинге
будут указаны обе директивы INCLUDE).

                       Директивы %MACS и %NOMACS
-----------------------------------------------------------------

     Директивы %MACS и %NOMACS позволяют вам разрешить (директива
%MACS) или  запретить  (директива  %NOMACS)  включение  в листинг
текста макрорасширений. Листинг макрорасширений обычно подавляет-
ся. Например, в результате ассемблирования исходного кода:

            .
            .
            .
 MAKE_BYTE  MACRO  VALUE
            DB     VALUE
            ENDM
            .
            .
            .
            %NOMACS
            MAKE_BYTE 1
            %MACS
            MAKE_BYTE 1
            .
            .
            .

текст,  генерируемый   первым    макрорасширением    макрокоманды
MAKE_BYTE,  DB  1,   в файл листинга включен не будет (однако обе
директивы MACRO будут включены в файл листинга).

                       Директивы %CTLS и %NOCTLS
-----------------------------------------------------------------

     Директивы %CTLS и %NOCTLS позволяют  вам  разрешить  (%CTLS)
или запретить (директива %NOCTLS) включение в листинг  самих  уп-
равляющих директив.  По умолчанию включение в листинг управляющих
директив запрещено. Например, в результате выполнения директив:

            .
            .
            .
            %NOCTLS
            %NOINCL
            %CTLS
            %NOMACS
            .
            .
            .

директива управления листингом %NOINCL в листинг включена на  бу-
дет, а директива %NOMACS - будет.

     Директивы %UREF и %NOUREF позволяют вам разрешать или запре-
щать  включение  в таблицу идентификаторов листинга идентификато-
ров, на которые нет ссылок (другими словами, идентификаторов, ко-
торые определяются, но не используются). По умолчанию включение в
листинг таких идентификаторов разрешено. Чтобы эти директивы дей-
ствовали, нужно задать создание листинга перекрестных ссылок.

     Директивы %SYMS и %NOSYMS позволяют вам разрешать или запре-
щать  включение в файл листинга таблицы идентификаторов. По умол-
чанию включение в листинг такой таблицы (как вы уже наверное  за-
метили) разрешено.



                Директивы управления форматом листинга
-----------------------------------------------------------------

     Директивы управления форматом листинга изменяют формат файла
листинга.  Эти  директивы  можно использовать для генерации файла
листинга такого формата, который вас больше устраивает.

     Директива %TITLE задает заголовок, выводимый в верхней части
каждой  страницы расширенного листинга исходного кода. Для каждой
программы можно задать только один заголовок.  Директива  %SUBTTL
задает  подзаголовок, который должен выводиться под заголовком на
каждой странице листинга. В программе число  подзаголовком  может
быть  любым. Например, если в исходном модуле SPACEWAR.ASM содер-
жатся директивы:

            .
            .
            .
            %TITLE  'Программа игры  З в е з д н ы е  в о й н ы'
            %SUBTTL 'Подпрограммы гравитационных эффектов'
            .
            .
            .

то на каждой странице расширенного листинга исходного кода  будут
содержаться следующие строки:

Turbo Assembler  Version 2.0    06-29-90  16:21:27         Page 1
SPACEWAR.ASM
Программа игры  З в е з д н ы е  в о й н ы
Подпрограммы гравитационных эффектов

     Директива %NEWPAGE указывает Турбо Ассемблеру, что  в  файле
листинга нужно начать новую страницу.

     Директива %TRUNC указывает Турбо Ассемблеру, что нужно  усе-
кать  поля,  превышающие  максимальную  длину. Директива %NOTRUNC
указывает, что такие поля нужно переносить на  следующую  строку.
По умолчанию эти поля усекаются.

     Директива %PAGESIZE задает вертикальный (число строк) и  го-
ризонтальный  (число позиций) размер страниц листинга, генерируе-
мых Турбо Ассемблером. Например, директива:

        %PAGESIZE  66,132

указывает Турбо Ассемблеру, что нужно создавать страницы листинга
с  размером 66 строк на страницу и 132 позиции в строке. Отметим,
что директива %PAGESIZE не посылает на  принтер  команды  задания
размера  страницы,  поэтому перед печатью листинга вы должны сами
установить  параметры  принтера,  затем  использовать   директиву
%PAGESIZE для генерации Турбо Ассемблером страниц, размер которых
совпадает с теми размерами, которые вы установили на принтере.




Директивы задания размера полей
-----------------------------------------------------------------

     Размером пяти полей расширенного листинга исходного кода уп-
равляют пять директив. Полный формат строки данной части листинга
имеет вид:

<глубина> <номер_строки> <смещение> <машинный_код> <исходный_код>

     Четыре из этих полей мы описывали ранее,  пятым  полем явля-
ется поле "глубина", которое указывает, какова для текущей строки
глубина вложенности макрокоманд или включаемых файлов.  Например,
если данная строка генерируется макрокомандой, которая сама вызы-
вается из другой макрокоманды, то поле "глубина" будет иметь зна-
чение 2.

     Размер в символах поля "глубина"  задает  директива  %DEPTH.
Директива  %LINUM  задает длину в  символах  поля "номер_строки".
Директива %PCNT определяет размер поля "смещение". Директива %BIN
определяет величину поля "машинный_код". Наконец, директива %TEXT
задает длину поля комментария.

                    Директивы %PUSHLCTL и %POPLCTL
-----------------------------------------------------------------

     Иногда вам, возможно, потребуется изменить на время  текущее
состояние управления листингом, а затем восстановить его. Возмож-
но, чтобы включить в листинг каждый байт таблицы данных, вам пот-
ребуется  разрешить  перенос  на другую строку и изменить размеры
полей, или же вы захотите в целях отладки разрешить  включение  в
листинг  всех  типов строк. После того, как вы измените состояние
управления листингом, было бы  очень  неплохо  иметь  возможность
сразу  восстановить управляющие параметры в их определенные ранее
значения, особенно, если эти значения задаются во включаемом фай-
ле или в какой-нибудь дальней части исходного модуля.

     Для управления такой ситуацией в Турбо Ассемблере предусмот-
рены  директивы %PUSHLCTL и %POPLCTL. Директива %PUSHLCTL заносит
текущее состояние управления листингом во внутренний стек, а  ди-
ректива %POPLCTL извлекает его из стека (обе директивы имеют мак-
симум 16 уровней). Эти директивы только сохраняют и  восстанавли-
вают  состояние управления листингом, который может быть разрешен
или запрещен (аналогично директивам %TRUNC и %NOTRUNC), и не тре-
буют  числовых  аргументов.  Например,  в следующем исходном коде
состояние  управления  листингом   после   выполнения   директивы
%POPLCTL то же, что и перед выполнением директивы %PUCHLCTL:

            .
            .
            .
            %LIST
            %TRUNC
            %PUSHLCTL
            %NOLIST
            %NOTRUNC
            %NEWPAGE
            .
            .
            .
            %POPLCTL
            .
            .
            .


Другие директивы управления листингом
-----------------------------------------------------------------

     Чтобы обеспечить совместимость  с  другими  ассемблерами,  в
Турбо  Ассемблере предусмотрены некоторые другие директивы управ-
ления листингами. Они включают в себя директивы  TITLT,  SUBTTTL,
PAGE,  .LST,  .XLST,  .LFCOND,  .SFCOND,  .TFCOND, .LALL, .SALL и
.XALL. (Подробное описание данных директив приведено  в  Главе  2
"Справочного руководства").



               Вывод сообщения во время ассемблирования
-----------------------------------------------------------------

     В Турбо Ассемблере предусмотрены две директивы,  позволяющие
выводить  строку на экран дисплея во время ассемблирования (тран-
сляции). Эти директивы можно использовать для вывода информации о
ходе  ассемблирования,  чтобы  вы  могли  определить, какая часть
программы уже оттранслирована или о том, что Ассемблер достиг оп-
ределенной части исходного кода.

     Директива DISPLAY выводит заключенную в  кавычки  строку  на
экран.  Директива  %OUT выводит на экран строку, не заключенную в
кавычки. Например, в результате выполнения следующих директив:

            .
            .
            .
            DISPLAY  'Выведено по директиве DISPLAY'
            %OUT      Выведено по директиве OUT
            .
            .
            .

на экран выведутся сообщения:

            Выведено по директиве DISPLAY
            Выведено по директиве OUT

                Условное ассемблирование исходного кода
-----------------------------------------------------------------

     Настанет момент, когда вы поймете,  что  чрезвычайно  удобно
иметь один исходный модуль на Ассемблере, в результате трансляции
которого получается несколько различных версий программы.  Напри-
мер, для одной программы иногда желательно иметь две версии, одна
из которых использует стандартные инструкции процессора  8086,  а
другая - расширенный набор инструкций процессоров 80186  (80286).
(Конечно,  в этом случае можно использовать два  исходных  модуля
(для каждой версии свой),  но вносить в них, например, изменения,
в этом случае было бы очень неудобно).

     Турбо Ассемблер предоставляет вам такую возможность (и  даже
более). Рассмотрим, например, следующий фрагмент программы:

            .
            .
            .
   IF      IS8086
            mov   ax,3dah
            push  ax
   ELSE
            push  3dah
   ENDIF
            call  GetAdapterStatus
            .
            .
            .

     Если значение метки IS8086 будет ненулевым, то значение  па-
раметра  3dah  заносится  в  стек поэтапно (за два шага), как это
требуется в процессоре 8086. Если же, однако, IS8086 имеет  нуле-
вое  значение, то значение параметра заносится в стек непосредст-
венно, с помощью специальной формы инструкции PUSH, которую можно
использовать при работе на процессорах 80186 и  80286  (но  не  в
процессоре 8086).

     Турбо Ассемблер поддерживает  множество  директив  условного
ассемблирования,  а также дает вам возможность несколькими спосо-
бами генерировать ошибки ассемблирования. Давайте рассмотрим сна-
чала директивы условного ассемблирования.


Директивы условного ассемблирования
-----------------------------------------------------------------

     Простейшими и самыми полезными директивами условного ассемб-
лирования являются директивы  IF и  ENDIF,  которые  используются
совместно с директивами ENDIF и (необязательно) ELSE. Часто также
используются  директивы IFDEF и IFNDEF, а директивы условного ас-
семблирования IFB, IFNB, IFIDN, IFDIF, IF1 и IF2 полезны только в
отдельных случаях.



                          Директивы IF и IFE
-----------------------------------------------------------------

     Директива условного ассемблирования IF приводит к тому,  что
последующий  блок  исходного  кода  (до соответствующей директивы
ELSE или ENDIF) будет ассемблироваться только в том случае,  если
значение операнда будет ненулевым. Операнд может представлять со-
бой константу или выражение, при вычислении  которого  получается
константа. Например, в результате выполнения директив:

            .
            .
            .
      IF  REPORT_ASSEMBLY_STATUS
           DISPLAY 'Ассемблирование достигло контрольной точки 1'
      ENDIF
            .
            .
            .

в том случае, если REPORT_ASSEMBLY_STATUS  имеет  при  достижении
директивы IF ненулевое значение, на экран выводится сообщение:

        Ассемблирование достигло контрольной точки 1

     Условие IF (если) может завершаться директивами ENDIF (конец
блока)  или  ELSE (иначе). Если условие IF завершается директивой
ELSE, то следующий за ELSE исходный код ассемблируется  только  в
том случае, если операнд соответствующей  директивы  IF был нуле-
вым. Блок кода, следующего за директивой ELSE, должен завершаться
директивой ENDIF. Условия  IF  могут быть вложенными, например, в
программе:

            .
            .
            .
 ; Проверить, нужно ли определять массивы (в противном случае
 ; они распределяются динамически).
      IF DEFINE_ARRAY
 ; Убедиться, что массив не слишком длинный
      IF (ARRAY_LENGTH GT MAX_ARRAY_LENGTH)
        ARRAY_LENGTH     =   MAX_ARRAY_LENGTH
      ENDIF
 ; Если это указано, установить массив в начальное значение
        IF INITIALIZE_ARRAY
         Array    DB  ARRAY_LENGTH  DUP (?)
        ENDIF
      ENDIF
            .
            .
            .

директивы IF и IF...ELSE вложены внутри другого блока IF.

     Директива IFE аналогична директиве IF,  но  последующий  код
ассемблируется  в  том  случае, если операнд нулевой. В следующем
примере исходный код после директивы IFE ассемблируется всегда:

            .
            .
            .
        IF  0
            .
            .
            .
        ENDIF
            .
            .
            .

     Как и директива IF, директива IFE может иметь  соответствую-
щую директиву ELSE.

     Необходимо понимать, что директивы условного ассемблирования
работают  только во время ассемблирования, а не во время выполне-
ния программы. Это не то же самое, что операторы в языке  Си, вы-
полняющие  различный код в зависимости от различных условий этапа
выполнения.  Директивы  условного  ассемблирования   обеспечивают
трансляцию различного кода в зависимости от условий ассемблирова-
ния.



                       Директивы IFDEF и IFNDEF
-----------------------------------------------------------------

     Директивы условного ассемблирования IFDEF и IFNDEF - это ваш
основной  инструмент для построения программ, при ассемблировании
которых получается несколько версий. Директивы IFDEF и  IFNDEF  в
этом случае чрезвычайно полезны.

     Блок исходного кода, заключенный между  директивой  IFDEF  и
соответствующей  ей директивой ENDIF, ассемблируется только в том
случае, если метка, являющаяся операндом директивы IFDEF, сущест-
вует  (другими словами, если при выполнении директивы IFDEF метка
уже определена). Например, при трансляции исходного кода:

            .
            .
            .
  DEFINED_LABEL  EQU  0
            .
            .
            .
  IFDEF DEFINED_LABEL
            DB   0
  ENDIF
            .
            .
            .

будет ассемблироваться директива DB. Если же вы удалите директиву
EQU,  которая устанавливает значение для DEFINED_LABEL (и в пред-
положении, что эта метка нигде больше в программе не  определяет-
ся), то директива DB ассемблироваться не будет. Заметим, что зна-
чение метки DEFINED_LABEL для директивы IFDEF не важно.

     Действие директивы IFNDEF обратно действию директивы  IFDEF.
Соответствующий  код ассемблируется только в том случае, если яв-
ляющаяся операндом метка не определена.

     У вас может возникнуть вопрос, для чего используются  дирек-
тивы  IFDEF  и IFNDEF? Одним из применений является предохранение
от повторного определения метки с помощью директивы EQU в сложной
программе:  если  метка уже определена, то чтобы избежать ее пов-
торного определения (что вызовет ошибку), вы можете  использовать
директиву  IFDEF. Другое использование данной директивы - это вы-
бор версии ассемблируемой программы (аналогично тому, как это де-
лалось  ранее  с помощью директивы IF). Вместо того, чтобы прове-
рять, скажем, является ли массив  INITIALIZE_ARRAYS  нулевым  или
ненулевым, вы можете просто проверить, определен ли он вообще.

     Удобный способ выбора версии программы  предоставляет  пара-
метр командной строки Турбо Ассемблера /D. Параметр /D определяет
соответствующую локальную метку  и  (возможно)  присваивает  этой
метке значение. Поэтому вы, например, можете использовать следую-
щую команду:

        TASM /dINITIALIZE_ARRAYS=1 test

     При  этом  при  ассемблировании  программы  TEST.ASM   метка
INITIALIZE_ARRAYS будет установлена в значение 1.

     Хотя это определенно полезно, здесь могут возникнуть различ-
ные  проблемы.  Что будет в том случае, если вы будете полагаться
на определение INITIALIZE_ARRAYS в командной строке, но  забудете
указать  соответствующий  параметр  /D? Предположим также, что вы
хотите инициализировать массивы в особом случае  и  не  хотите  в
других случаях вводить /dINITIALIZE_ARRAYS.

     В этом случае вам на выручку придет директивы IFNDEF. Вы мо-
жете    использовать    ее   для   проверки   того,   что   метка
INITIALIZE_ARRAYS уже определена (в командной  строке),  а  затем
инициализировать  ее только в том случае, если ее значение еще не
задано. Таким образом, определение в командной строке имеет преи-
мущество (старшинство), но если определение в командной строке не
задано, то для метки имеется состояние, используемое  по  умолча-
нию.    Приведем    пример    программы,    в    которой   массив
INITIALIZE_ARRAYS определяется только в том случае, если  он  еще
не определен:

               .
               .
               .
 IFNDEF   INITIALIZE__ARRAYS
 INITIALIZE__ARRAYS  EQU  0   ; по умолчанию не инициализируется
 ENDIF
               .
               .
               .

     Когда вы таким образом используете директиву IFNDEF для  оп-
ределения  идентификатора, который еще не был определен, вы полу-
чите предупреждающее сообщение, показывающее, что вы  используете
конструкцию,  зависящую  от  прохода.  Если вы просто определяете
внутри условного блока IFNDEF  идентификатор,  то  это  сообщение
можно  игнорировать. Данное сообщение выводится потому, что Турбо
Ассемблер не  может сообщить вам,  что вы собираетесь поместить в
блок директивы или инструкции.  Если вы делаете  в  блоке  что-то
еще, а  не  просто определяете идентификатор,  то вы должны с по-
мощью параметра /m разрешить выполнение нескольких проходов. Если
вы только  определяете  идентификатор,  то  разрешение выполнения
нескольких проходов не приведет к выводу предупреждающего сообще-
ния.



              Другие директивы условного ассемблирования
-----------------------------------------------------------------

     Для проверки параметров, передаваемых  в  макрокоманды,  ис-
пользуются директивы IFB,  IFNB,  IFIDN и IFDIF. (О макрокомандах
рассказывается  в Главе 9 "Развитое программирование на Турбо Ас-
семблере").  Директива IFB приводит к тому,  что  соответствующий
исходный код будет ассемблирован в том случае, если параметр, яв-
ляющийся операндом директивы,  пустой (пробел). По директиве IFNB
исходный  код  будет ассемблироваться,  если параметр не пуст (не
является пробелом).  Директивы IFNB и IFB - это своего рода экви-
валент директив IFNDEF и IFDEF для параметров макрокоманд.

     Рассмотрим в качестве примера следующую  макрокоманду  TEST,
которая определена следующим образом:

;
; Макрокоманда для определения байта или слова
;
; Ввод:
;    VALUE = значение байта или слова
;    DEFINE_WIRD = 1 для определения слова и 0 для определения
;    байта
;
; Примечание: Если параметр PARM2 не задан, то определяется байт.

;
TEST MACRO       VALUE, DEFINE_WORD
IFB  
     DB   VALUE     ; определить байт, если PARM2 - пробел, иначе
  IF DEFINE_WORD
     DW   VALUE     ; определить слова, если PARM2 не = 0
  ELSE
     DB   VALUE     ; определить байт, если PARM2 = 0
  ENDIF
ENDIF
     ENDM

     Если макрокоманда TEST вызывается оператором:

        TEST 19

то определяется байт со значением 19, а если макрокоманда вызыва-
ется с помощью оператора:

        TEST 19,1

то определяется слово со значением 19.

     По директиве IFIDN соответствующий исходный код будет ассем-
блироваться в том случае,  если два ее параметра совпадают,  а по
директиве IFDIF - если параметры  различны.  Например,  следующая
макрокоманда,  преобразующая  байт  со знаком в слово со знаком в
регистре AX,  не копирует исходный операнд,  если он находится  в
регистре AL:

;
; Макрокоманда для преобразования байта со знаком в 8-битовом
; регистре или ячейке памяти в слово со знаком в регистре AX.
;
; Ввод:
;    SIGNED_BYTE - имя регистра или ячейки памяти,
;                  в которой содержится байт со знаком,
;                  преобразуемый в слово со знаком.
;
MAKE_SIGNED_WORD   MACRO   SIGNED_BYTE
IFDIFI   ,        ; убедиться, что операндом
                                   ; не является регистр AL
         mov   al,SIGNED_BYTE
ENDIF
         cwb
         ENDM

     В аргументах директив IFDIF и  IFIDN  строчные  и  прописные
буквы различаются. Чтобы интерпретировать эти буквы, как совпада-
ющие, имеются две  другие  эквивалентные  директивы  -  IFINDI  и
IFDIFI.

     Заметим, что все операнды директив IFB, IFNB, IFIDN и  IFDIF
требуется заключать в угловые скобки.

     Если вы  не  указываете для разрешения выполнения нескольких
прохoдов параметр командной строки /m, то условие IF1 всегда при-
нимает  истинное значение,  а IF2 - ложное (так как второй проход
не выполняется). Если Турбо Ассемблер встречает директиву IF1 или
IF2, то выводится предупреждающее сообщение:

     "Pass dependent construction encountered"
     (обнаружена конструкция, зависящая от прохода)

     Eсли вы используете параметр /m, то если в модуле содержатся
директивы IF1 или IF2,  автоматически выполняется два прохода.  В
этом случае  директива  IF1 принимает истинное значение на первом
проходе, а IF2 - на втором. При этом также выводится предупрежда-
ющее сообщение:

     "Module is pass dependent - compatibility pass was done"
     (модуль зависит от прохода - выполнен проход  для  совмести-
      мости)



                       Семейство директив ELSEIF
-----------------------------------------------------------------

     Каждая из директив IF (IF, IFB, IFIDN и  т.д.)  имеет  соот-
ветствующую  директиву  семейства ELSEIF (например, ELSEIF, ELSE-
IFB, ELSEIFIDN). Они работают, как сочетание директивы ELSE с од-
ной  из  директив IF. Вы можете их использовать, чтобы обеспечить
лучшую читаемость исходного кода, когда требуется проверять  мно-
жество  условий  или  значений  и ассемблировать только отдельный
блок кода. Рассмотрим следующий фрагмент программы:

IF  BUFLENGHT GT 1000
        CALL  DOBIGBUF  ; большой буфер
ELSE
        IF BUFLENGTH GT 100  ; средний буфер
               CALL  MEDIUMBUF
        ELSE
               IF BUFLENGTH GT 10  ; небольшой буфер
                      CALL  SMALLBUF
               ELSE
                      CALL  TINYBUFP  ; маленький буфер
               ENDIF
        ENDIF
ENDIF

     Чтобы улучшить читаемость кода, вы можете  использовать  ди-
рективу ELSEIF:

IF  BUFLENGHT GT 1000
        CALL  DOBIGBUF       ; большой буфер
ELSE
ELSEIF BUFLENGTH GT 100      ; средний буфер
        CALL  MEDIUMBUF
ELSEIF BUFLENGTH GT 10       ; небольшой буфер
        CALL  SMALLBUF
ELSE
        CALL  TINYBUFP       ; маленький буфер
        ENDIF

     Это приблизительно соответствует операторам case или  switch
в  Паскале и Си. Однако, такая конструкция является гораздо более
общей, поскольку  во  всем блоке условного ассемблирования вам не
требуется использовать один и тот же вид проверок ELSEIF.  Допус-
тимо, например, следующее:

PUSHREG  MACRO ARG
     IFIDN ,
           PUSH SI
           PUSH DI
     ELSEIFB 
           PUSH AX
     ENDIF
     ENDM



            Условные директивы вывода сообщений об ошибках
-----------------------------------------------------------------

     Турбо Ассемблер позволяет вам выполнять условную (по  выпол-
нению  или  невыполнению  определенного условия) генерацию ошибок
ассемблирования. Для этого используются условные директивы вывода
сообщений об ошибках .ERR, .ERR1, ERR2, .ERRDEF, .ERRNDEF, .ERRB,
.ERRNB, .ERRIDN, .ERRIDNI, .ERRDIFI, .ERRE, .ERRNZ и .ERRDIF. Для
чего нужно преднамеренно генерировать сообщение об ошибке ассемб-
лирования?  Условные директивы вывода сообщений об ошибках позво-
ляют вам перехватывать в программах множество  ошибок,  например,
присваивание меткам слишком больших или малых значений, использо-
вание неопределенных меток и пропуск аргументов макрокоманд.

     Если вглянуть на перечень условных директив вывода сообщений
об  ошибках, то можно заметить, что эти директивы очень похожи на
директивы условного ассемблирования. И это не случайное  совпаде-
ние,  поскольку большинство условных директив вывода сообщений об
ошибках проверяют те же условия. Например, директива .ERRNDEF ге-
нерирует  ошибку  в том случае, если метка, являющаяся ее операн-
дом, не определена, так же как директива IFNDEF ассемблирует  со-
ответствующий код в том случае, если метка не определена.



                     Директивы .ERR, .ERR1 и .ERR2
-----------------------------------------------------------------

     Когда Турбо Ассемблер обнаруживает директиву .ERR, то  гене-
рируется  ошибка. Само по себе это не является полезной функцией,
однако директиву .ERR полезно использовать в сочетании с директи-
вой условного ассемблирования.

     Например, предположим, вы хотите хотите сгенерировать ошибку
в том случае, если в присваивании длины для данного массива уста-
навливается слишком большое значение. Это можно сделать следующим
образом:

IF  (ARRAY_LENGTH GT MAX_ARRAY_LENGTH)
    .ERR
ENDIF

     Если массив не является достаточно  длинным  (длина  массива
ARRAY_LENGTH  не превосходит максимального значения длины массива
MAX_ARRAY_LENGTH), то Турбо Ассемблер не будет  генерировать  код
внутри блока IF и ошибка генерироваться не будет.

     Директивы .ERR1 и .ERR2 работают точно также, как  директива
.ERR,  но  только,  соответственно,  на первом и втором проходах.
Если для  разрешения выполнения нескольких проходов вы не исполь-
зуете параметр командной строки /m,  то по директиве .ERR1 всегда
будет выводиться ошибка, а по директиве .ERR2 - не будет (так как
второй проход не выполняется). В том случае, если Турбо Ассемблер
обнаруживает в модуле директивы .ERR1 или .ERR2, он выводит сооб-
щение:

     "Pass dependent construction encountered"
     (обнаружена конструкция, зависимая от прохода)

     Если вы  используете параметр командной строки /m,  то когда
ваш модуль содержит директиву .ERR1 или .ERR2,  автоматически вы-
полняется два прохода.  В этом случае директива .ERR1 будет выво-
дить сообщение об ошибке на первом проходе,  а директиве .ERR2  -
на втором проходе.  Кроме того, выводится предупреждающее сообще-
ние:

     "Module is pass dependent - compatibility pass was done"
     (модуль зависит  от  прохода - выполнен проход для совмести-
     мости)


                       Директивы .ERRE и .ERRNZ
-----------------------------------------------------------------

     Директива .ERRE генерирует ошибку в том случае, если ее опе-
ранд,  при вычислении которого должна получаться константа, равен
нулю. Директива .ERRE эквивалента выполнению директивы .IFE в со-
четании с директивой .ERR. Например:

   .ERRE       TEST_LABEL-1

эквивалентно:

   IFE     TEST_LEBEL-1
           .ERRE
   ENDIF

     Директиву .ERRE можно использовать для вывода ошибки  в  том
случае,  когда в выражении отношения генерируется ложное значение
(так как ложное выражение равно 0).

     Аналогично, директива .ERRNZ генерирует ошибку в том случае,
если  ее операнд не равен нулю. Это эквивалентно директиве IF, за
которой следует директива .ERR. Директиву .ERRNZ можно  использо-
вать  для вывода ошибки в том случае, когда в выражении отношения
генерируется истинное значение (так  как  истинное  выражение  не
равно 0). Например:

       .ERRNZ   ARRAY_LENGTH GT MAX_ARRAY_LENGTH

выполняет то же действие, что и директивы IF  и  .ERR  в  примере
последнего раздела.




Директивы .ERRDEF и .ERRNDEF
-----------------------------------------------------------------

     Директива .ERRDEF генерирует ошибку в том случае, если  мет-
ка, являющаяся ее операндом, определена, а директива .ERRNDEF ге-
нерирует ошибку в том случае, если метка-операнд является неопре-
деленной.  Эти директивы позволяют в одной строке реализовать эк-
вивалент сочетания директив IFDEF или  IFNDEF и  директивы  .ERR.
Например:

      .ERRNDEF   MAX_PATH_LENGTH

эквивалентно:

      IFNDEF   MAX_PATH_LENGTH
        .ERR
      ENDIF



        Другие условные директивы генерации сообщения об ошибке
-----------------------------------------------------------------

     Четыре оставшиеся условные  директивы  предназначены  только
для  использования  в  макрокомандах  и являются непосредственным
аналогом четырех директив условного  ассемблирования,  использую-
щихся  в  макрокомандах  и  обсуждавшихся  в  предыдущем  разделе
"Другие директивы условного ассемблирования".

     Директива .ERRB генерирует ошибку в том случае, если  являю-
щийся ее операндом параметр макрокоманды пуст, а директива .ERRNB
- в том случае, если этот параметр не пустой.  Директива  .ERRIDN
генерирует ошибку, если два параметра макрокоманды, которые явля-
ются ее операндами, совпадают, а директива .ERRDIF - в  том  слу-
чае, если они различны.

     Например, в следующей макрокоманде ошибка генерируется в том
случае,  если  она вызывается с любым числом параметров, отличным
от двух. Это реализовано с помощью директив .ERRB и  .ERRNB  (они
позволяют  проверить,  что  PARM2 не пуст, а PARM3 - пустой пара-
метр). Чтобы убедиться, что в качестве PARM2 не используется  ре-
гистр  DX,  в  макрокоманде используется также директива .ERRIND.
Макрокоманды выглядит следующим образом:

;
; Макрокоманда для сложения двух констант, регистров или
; именованных ячеек памяти и сохранения результата в DX.
;
; Ввод:
;    PARM1 - один операнд-слагаемое
;    PARM2 - другой операнд-слагаемое
;
ADD_TWO_OPERANDS  MACRO   PARM1,PARM2,PARM3
     .ERRB          ; должно быть два параметра,
     .ERRNB         ; но не три
     .ERRIDN ,  ; второй параметр не может
                           ; быть регистром DX
     mov   dx,PARM1
     add   dx,PARM2
     ENDM

     Обратите внимание  на использование в макрокоманде директивы
.ERRIDN, чтобы обеспечить, что PARM2 отличен от DX (в этом случае
при загрузке PARM1 он будет отброшен).



Типичные ошибки при программировании на Ассемблере
-----------------------------------------------------------------

     В каждом языке имеется свое множество ошибок, которые обычно
очень  легко сделать, но не всегда просто обнаружить. Не является
исключением и язык Ассемблера. Мы рассмотрим  некоторые  типичные
ошибки, которые допускаются при программировании на Ассемблере, и
дадим рекомендации, как можно их избежать.

                 Программист забывает о возврате в DOS
-----------------------------------------------------------------

     В Паскале, Си и других языках программа завершается и  возв-
ращается  в  операционную  систему  DOS  автоматически, когда нет
больше выполняемого кода, даже если в программе отсутствует явная
команда  ее  завершения. В языке Ассемблера это не так. Ассемблер
выполняет только те действия, которые вы явно  указываете.  Когда
вы запускаете программу, в которой отсутствует команда возврата в
DOS, она просто продолжает работать до  конца  выполняемого  кода
программы  и переходит в код, который находится в примыкающей па-
мяти.

     Рассмотрим, например, следующую программу:

   DOSSEG
   .MODEL   SMALL
   .CODE
DoNothing PROC NEAR
   nop
DoNothing ENDP
   END    DoNothing

     Имеющийся опыт может подсказывать вам,  что  директивы  ENDP
или  END  должным образом завершат программу, аналогично } и end.
do в Паскале и Си, но это не так. Выполняемый код,  сгенерирован-
ный  при  ассемблировании  и компоновке данной программы, состоит
только из отдельной инструкции NOP. В Ассемблере  директива  ENDP
(как  и все другие директивы) не генерирует кода, она просто уве-
домляет Ассемблер, что код для  процедуры  DoNothing  закончился.
Аналогично,  директива  END DoNothing просто сообщает Ассемблеру,
что код данного модуля закончился, и программа должна начать  вы-
полнение с метки DoNothing. Нигде в выполняемом коде не содержит-
ся инструкции для передачи управления обратно в операционную сис-
тему DOS,  когда программа закончится.  В результате, когда прог-
рамма будет запущена,  то после инструкции NOP будут  выполняться
инструкции, которые случайно окажутся в памяти непосредственно за
NOP. В этой точке управление будет потеряно и для возврата в опе-
рационную систему  DOS  может потребоваться программная или аппа-
ратная перезагрузка.

     Хотя имеется несколько способов, с помощью которых программа
на Ассемблере может вернуться в DOS, рекомендуемым способом возв-
рата в DOS является функция 4Ch. Правильно завершать работу будет
следующая версия предыдущей программы:

   DOSSEG
   .MODEL   SMALL
   .CODE
DoNothing PROC NEAR
   nop
   mov   ah,4Ch            ; функция DOS завершения процесса
   int   21h               ; вызвать DOS для завершения программы
DoNothing ENDP
   END    DoNothing

     Всегда нужно помнить о том, что директивы не генерируют  ко-
да,  и  что  Турбо Ассемблер генерирует программы, которые делают
только то, что им указывает исходный код, не больше и не меньше.

                Программист забывает об инструкции RET
-----------------------------------------------------------------

     Заметим, что правильный вызов подпрограммы состоит из вызова
подпрограммы  из  другой  части  кода,  выполнения подпрограммы и
возврата из подпрограммы  в  вызывающую  программу.  Не  забудьте
включать  в каждую подпрограмму инструкцию RET, по которой управ-
ление будет передаваться в вызывающий код. При  наборе  программы
эту директиву легко пропустить и закончить код следующим образом:

;
; Подпрограмма для умножения значения на 80
; Ввод:   AX - значение, которое нужно умножить на 80
; Вывод:  DX:AX - произведение
;
MultiplyBy80   PROC NEAR
                mov   dx,80
                mul   dx
MultiplyBy80   ENDP

; Подпрограмма для получения следующе нажатой клавиши
; Вывод: AL - следующая нажатая клавиша
; Содержимое регистра AH теряется
;
GetKey      PROC NEAR
             mov  ah,1
             int  21h
             ret
GetKey      PROC NEAR

     Директива MultipleBy80 ENDP может ввести вас в  заблуждение,
и  вы подумаете, что подпрограмма MultipleBy80 уже завершена кор-
ректно,  тогда  как  при  вызове это подпрограммы не только будет
содержимое AX умножаться на 80, но и продолжиться выполнение под-
программы GetKey,  и в регистре AL будет возвращаться код следую-
щей нажатой клавиши.  Корректной эта подпрограмма будет в следую-
щем виде:

;
; Подпрограмма для умножения значения на 80
; Ввод:   AX - значение, которое нужно умножить на 80
; Вывод:  DX:AX - произведение
;
MultiplyBy80   PROC NEAR
                mov   dx,80
                mul   dx
                ret
MultiplyBy80   ENDP

; Подпрограмма для получения следующей нажатой клавиши
; Вывод: AL - следующая нажатая клавиша
; Содержимое регистра AH теряется
;
GetKey      PROC NEAR
             mov  ah,1
             int  21h
             ret
GetKey      PROC NEAR



                   Генерация неверного типа возврата
-----------------------------------------------------------------

     Директива PROC действует двояко. Во-первых,  она  определяет
имя,  по  которому будет вызываться процедура. Во-вторых, она уп-
равляет типом (ближним или дальним) процедуры.

     Тип процедуры используется Турбо Ассемблером для определения
того,  какой  тип вызовов нужно генерировать при вызове процедуры
из того же исходного файла. Тип процедуры также используется  для
определения  типа инструкции RET, которая выполняется, когда про-
цедура возвращает управление в вызывающий код. Рассмотрим следую-
щий пример:

; Подпрограмма ближнего типа для сдвига DX:AX вправо на 2 байта
;
LongShiftRight2   PROC NEAR
     shr   dx,1
     rcr   ax,1   ; сдвиг DX:AX вправо на 1 бит
     shr   dx,1
     rcr   ax,1   ; сдвиг DX:AX вправо еще на 1 бит
     ret
LongShiftRight2   ENDP

     Турбо Ассемблер обеспечивает, что инструкция RET будет ближ-
него  типа, так как LongShiftRight2 - это процедура ближнего типа
(NEAR). Однако, если директиву PROC изменить следующим образом:

LongShiftRight2   PROC FAR

то будет генерироваться инструкция RET дальнего типа (FAR).

     Таким образом, идея здесь очевидна. Инструкции RET в  проце-
дуре должны соответствовать ее типу, не правда ли?

     Эти и так и не так. Проблема состоит в  том,  что  можно  (и
часто  желательно)  группировать  в одной и той же процедуре нес-
колько процедур. Поскольку в этих  процедурах  отсутствует  соот-
ветствующая директива PROC, их инструкции RET будут иметь тип той
процедуры, в которую они заключены, а для конкретных  подпрограмм
этот тип не всегда может оказаться корректным. Например, програм-
ма:

; Подпрограмма дальнего типа для сдвига DX:AX на 2 бита.
;
LongShiftRight2    PROC FAR
     call   LongShiftRight      ; сдвиг DX:AX вправо на 1 бит
     call   LongShiftRight      ; сдвиг DX:AX вправо еще на 1 бит
     ret
LongShiftRight:
     shr    dx,1
     rcr    ax,1                ; сдвиг DX:AX вправо на 1 бит
     ret
LongShiftRight2    ENDP

работает неправильно.  Процедура LongShiftRight2 обращается с вы-
зовом ближнего типа к LongShiftRight (так как они находятся в од-
ном  сегменте  кода).  Однако,  так как LongShiftRight встроена в
процедуру  LongShiftRight2,  то  возврат  в  конце   подпрограммы
LongShiftRight становится возвратом дальнего типа, а когда вызову
ближнего типа соответствует возврат дальнего типа,  это с большой
вероятностью  может  привести к сбою (аварийному завершению прог-
раммы).

     Хорошим решением здесь будет наличие в  каждой  подпрограмме
директивы PROC. Вложенные директивы PROC прекрасно работают:

; Подпрограмма дальнего типа для сдвига DX:AX на 2 бита.
;
LongShiftRight2    PROC FAR
     call   LongShiftRight      ; сдвиг DX:AX вправо на 1 бит
     call   LongShiftRight      ; сдвиг DX:AX вправо еще на 1 бит
     ret
LongShiftRight     PROC NEAR
     shr    dx,1
     rcr    ax,1                ; сдвиг DX:AX вправо на 1 бит
     ret
LongShiftRight2    ENDP
LongShiftRight     ENDP

также как и последовательные процедуры:

; Подпрограмма дальнего типа для сдвига DX:AX на 2 бита.
;
LongShiftRight2    PROC FAR
     call   LongShiftRight      ; сдвиг DX:AX вправо на 1 бит
     call   LongShiftRight      ; сдвиг DX:AX вправо еще на 1 бит
     ret
LongShiftRight2    ENDP
LongShiftRight     PROC NEAR
     shr    dx,1
     rcr    ax,1                ; сдвиг DX:AX вправо на 1 бит
     ret
LongShiftRight     ENDP

     Для явной генерации ближнего или дальнего возврата можно ис-
пользовать, соответственно,  инструкции RETN и  RETF.  Вы  можете
обеспечить с их помощью корректную генерацию инструкций возврата.

                    Неправильный порядок операндов
-----------------------------------------------------------------

     Многие программисты ошибаются и изменяют порядок операндов в
инструкциях процессора 8086 на обратный. Это, вероятно, связано с
тем, что строка:

        mov ax,bx

которая означает "поместить AX в BX", читается слева  направо,  и
многие  создатели микропроцессоров строят соответствующим образом
свои ассемблеры. Однако в языке Ассемблера процессора 8086  фирма
Intel  использовала другой подход, поэтому для нас эта строка оз-
начает "поместить BX в AX", что иногда приводит к путанице.

     Порядок операндов, принятый фирмой Intel, основан на порядке
операндов,  принятой  в Паскале и Си, где целевой операнд (прием-
ник) находится слева. Таким образом, чтобы не перепутать  порядок
операндов  в языке Ассемблера процессора 8086, нужно на место за-
пятой, разделяющей операнды,  поместить  знак  равенства,  придав
строке форму присваивания. Например, строку:

        mov ax,bx

можно рассматривать, как

        ax = bx

     Операнды-константы, такие, как:

        add bx,(OFFSET BaseTble * 4) + 2

можно представить в виде:

        bx += (OFFSET BaseTable * 4) + 2



      Программист забывает о стеке или резервирует маленький стек
-----------------------------------------------------------------

     В большинстве случаев не выделять явно пространство для сте-
ка,  это все равно, что ходить по тонкому льду. Иногда программы,
в которых не выделяется пространство для стека,  будут  работать,
поскольку  может оказаться так, что назначенный по умолчанию стек
попадет в неиспользуемую область памяти. Но нет никакой гарантии,
что  такие  программы  будет  работать при любых обстоятельствах,
поскольку нет гарантии, что для стека будет доступен  по  крайней
мере  один байт. В большинстве программ для резервирования прост-
ранства для стека должна присутствовать директива .STACK,  и  для
любой  программы  эта  директива должна резервировать достаточное
пространство, чтобы его хватило для максимальных  потребностей  в
программе.

     Почему это пространство должно быть более чем достаточным, а
не просто достаточным? Трудно иметь уверенность в том, какой объ-
ем стека может оказаться в программе необходимым. Ошибки, которые
возникают,  когда  увеличивающийся  стек переходит в другие части
программы и портит мат информацию, обычно бывает трудно воспроиз-
вести  и  отслеживать.  Кроме того, многие отладчики для возврата
управления  из  программы  используют  небольшое   дополнительное
пространство  в стеке. Поэтому не следует скупиться при выделении
пространства для стека. Это избавит вас от многих возможных  неп-
риятностей.  Хорошим правилом является выделение стека, минималь-
ный размер которого составляет 512 байт.

     Единственным видом программ на Ассемблере,  где  не  следует
выделять  стек, являются программы, которые предполагается преоб-
разовать в файлы типа .COM или .BIN. Файлы .BIN содержат код, ко-
торый  жестко  привязан к отдельным адресам,  и,  поскольку файлы
.BIN используются  обычно,  как  интерпретированные  подпрограммы
Бейсика,  они используют стек Бейсика.  Файлы .COM выполняются со
стеком,  расположенным в самой вершине программного сегмента (ко-
торый имеет размер 64К или меньше, если доступно меньше 64К), по-
этому максимальный размер стека в этом случае просто равен объему
памяти, оставшейся в программном сегменте. При написании программ
в формате .COM следует иметь в виду этот размер в  64К,  так  как
при  увеличении программы соответственно уменьшается стек.  Нужно
также учитывать,  что при работе больших программ в формате .COM,
выполняющиеся  на компьютерах с небольшой доступной памятью,  или
запущенных из операционной среды DOS наряду с другими  программа-
ми,  могут возникнуть проблемы со стеком. Простейший способ избе-
жать этих потенциальных проблем состоит в  написании  программ  в
формате .EXE, а не в формате .COM, и резервировании стека большо-
го объема.



    Вызов подпрограммы, которая портит содержимое нужных регистров
-----------------------------------------------------------------

     При разработке программы на Ассемблере регистры удобно расс-
матривать, как локальные переменные, выделенные для использования
в процедуре, с которой вы в данный момент работаете. В частности,
нередко подразумевают,  что при обращении к другим процедурам ре-
гистры остаются неизмененными. На самом деле это не так. Регистры
- это глобальные переменные,  и каждая процедура может  сохранить
или уничтожить содержимое любого из регистров.

     Рассмотрим следующий пример:

         .
         .
         .
         mov   bx,[TableBase]  ; BX указывает на начало таблицы
         mov   ax,[Element]    ; получить элемент
         call  DivideBy10      ; разделить элемент на 10
         add   bx,ax           ; ссылка на соответствующую запись
         .
         .
         .
; Подпрограмма для деления значения на 10.
;
; Ввод:  AX - значение, которое требуется разделить на 10
; Вывод: AX - значение, разделенное на 10
;        DX - остаток значения, деленного на 10
DivideBy10     PROC NEAR
         mov   dx,0            ; подготовить DX:AX, как
                               ; 32-битовое делимое
         mov   bx,10           ; BX - 16-битовый делитель
         div   dx
         ret
DivideBy10     ENDP

     В вызывающей программе подразумевается, что BX  в  процедуре
DivideBy10 сохраняется, хотя фактически от устанавливается проце-
дурой DivideBy10 в значение 10. В этом конкретном случае  сущест-
вует  несколько  возможных  решений. Например, в начале процедуры
DivideBy10 BX можно заносить в стек, а при выходе из процедуры  -
извлекать из стека:

         .
         .
         .
         mov   bx,[TableBase]   ; BX указывает на начало
                                ; таблицы
         mov   ax,[Element]     ; получить элемент
         call  DivideBy10       ; разделить элемент на 10
         add   bx,ax            ; ссылка на соответствующую
                                ; запись
         .
         .
         .
; Подпрограмма для деления значения на 10.
;
; Ввод:  AX - значение, которое требуется разделить на 10
; Вывод: AX - значение, разделенное на 10
;        DX - остаток значения, деленного на 10
DivideBy10     PROC NEAR
         push  bx              ; сохранить BX
         mov   dx,0            ; подготовить DX:AX, как
                               ; 32-битовое делимое
         mov   bx,10           ; BX - 16-битовый делитель
         div   dx
         pop   bx              ; восстановить BX
         ret
DivideBy10     ENDP

или сделать это в вызывающей программе до  (сохранение)  и  после
(восстановление) вызова процедуры DivideBy10:

         .
         .
         .
         mov   bx,[TableBase]   ; BX указывает на начало
                                ; таблицы
         mov   ax,[Element]     ; получить элемент
         push  bx               ; сохранить BX
         call  DivideBy10       ; разделить элемент на 10
         pop   bx               ; восстановить BX
         add   bx,ax            ; ссылка на соответствующую
                                ; запись
         .
         .
         .
; Подпрограмма для деления значения на 10.
;
; Ввод:  AX - значение, которое требуется разделить на 10
; Вывод: AX - значение, разделенное на 10
;        DX - остаток значения, деленного на 10
DivideBy10     PROC NEAR
         mov   dx,0            ; подготовить DX:AX, как
                               ; 32-битовое делимое
         mov   bx,10           ; BX - 16-битовый делитель
         div   dx
         ret
DivideBy10     ENDP

либо регистр BX можно загрузить после вызова процедуры, а не  пе-
ред ним:

         .
         .
         .
         mov   ax,[Element]     ; получить элемент
         call  DivideBy10       ; разделить элемент на 10
         mov   bx,[TableBase]   ; BX указывает на начало
                                ; таблицы
         add   bx,ax            ; ссылка на соответствующую
                                ; запись
         .
         .
         .
; Подпрограмма для деления значения на 10.
;
; Ввод:  AX - значение, которое требуется разделить на 10
; Вывод: AX - значение, разделенное на 10
;        DX - остаток значения, деленного на 10
DivideBy10     PROC NEAR
         mov   dx,0            ; подготовить DX:AX, как
                               ; 32-битовое делимое
         mov   bx,10           ; BX - 16-битовый делитель
         div   dx
         ret
DivideBy10     ENDP

     Общее решение этой проблемы состоит в том,  чтобы  все  под-
программы, которые могут изменять содержимое регистров, сохраняли
их, а затем восстанавливали. К сожалению для этого требуется  до-
полнительное  время и такие операции увеличивают объем программы,
что приводит к некоторым потерям в преимуществах программирования
на Ассемблере. Другой подход состоит в том, чтобы каждая подпрог-
рамма сопровождалась комментарием, в котором  указывается,  какие
регистры сохраняются, а какие разрушаются, и аккуратно проверять,
когда вы подразумеваете, что в подпрограмме регистр не  изменяет-
ся. Еще один подход заключается в явном сохранении нужных регист-
ров при вызове подпрограмм.



              Ошибки при использовании условных переходов
-----------------------------------------------------------------

     Использование в языке Ассемблера инструкций условных перехо-
дов  (JE,  JNE,  JC,  JNC, JA, JB, JG и т.д) обеспечивает большую
гибкость в программировании, но при этом также очень просто  оши-
биться,  выбрав  неверный  переход. Кроме того, поскольку в языке
Ассемблера анализ условия и переход требуют по крайней меру  двух
строк  исходного  кода  (а  сложных условных переходов нескольких
строк), условные переходы в языке  Ассемблера  менее  очевидны  и
больше способствуют ошибкам, чем соответствующие операторы Паска-
ля и Си.

     1.  Одной из общих ошибок является использование  инструкций
         JA,  JB,  JAE  или  JBE для сравнения значений со знаком
         или, соответственно, инструкций JG, JL, JGE или JLE  для
         сравнения беззнаковых значений.

     2.  Еще одна общая ошибка заключается в использовании,  ска-
         жем, инструкции JA там, где нужно использовать JAE. Нуж-
         но помнить о том, что без буквы E в конце  инструкции  в
         сравнении не учитывается случай, когда два операнда рав-
         ны.

     3.  Еще одной общей ошибкой является использование  инверти-
         рованной логики, например, применение инструкции JS там,
         где нужно использовать JNS.

     Один из подходов, позволяющий минимизировать ошибки при  ис-
пользовании  условных переходов состоит в комментировании перехо-
дов в соответствии с обозначениями, аналогичными языку Си. Напри-
мер:

         .
         .
         .
;
; if ( Length > MaxLength ) (
;
         mov   ax,[Length]
         cmp   ax,[MaxLength]
         jng   LengthIsLessThanMax
         .
         .
         .
         jng   EndMaxLengthTest
;
; ) else (
;
LengthIsLessThanMax:
         .
         .
         .
;
; )
;
EndMaxLengthTest:
         .
         .
         .




Ошибки в строковых инструкциях
-----------------------------------------------------------------

     Строковые инструкции - это самые мощные и  уникальные  инст-
рукции  среди  набора  инструкций процессора 8086. Эти особеннос-
ти порождают несколько описываемых далее проблем.



                Неверное понимание работы префикса REP
-----------------------------------------------------------------

     Строковые инструкции обладают любопытной особенностью: после
их выполнения используемые ими указатели ссылаются на адрес, пре-
вышающие на 1 байт (или на два байта в случае инструкции для  ра-
боты  со  словами)  последний обработанный адрес. Например, после
выполнения следующего кода:

         .
         .
         .
         cld            ; отсчет в строковой инструкции
                        ; в прямом направлении
         mov   si,0     ; ссылка на смещение 0
         lodsb          ; считать байт по смещению 0
         .
         .
         .

регистр SI будет содержать не 0, а 1. Это имеет смысл,  поскольку
в  следующей инструкции LODSB вы, вероятно, захотите обратиться к
адресу 1, а в еще одной - к адресу 2. Но при повторении строковых
инструкций  это  может  вызвать некоторую  путаницу, особенно при
использовании REP SCAS и REP CMPS. Рассмотрим следующий  фрагмент
программы:

         .
         .
         .
         cld                         ; отсчет в строковой
                                     ; инструкции в прямом
                                     ; направлении
         les   di,[bp+ScanString]    ; ES:DI указывают на
                                     ; просматриваемую строку
         mov   cx,MAX_STRING_LEN     ; проверить до самой
                                     ; длинной строки
         mov   al,0                  ; поиск завершающего нуля
         repne scasb                 ; выполнить поиск
         .
         .
         .

     Предположим, значение регистра ES равно 2000h, DI = 0, а па-
мять, начинающаяся по адресу 2000:0000 содержит значения:

     41h   61h   72h   64h   00h

     После выполнения этого кода регистр DI будет содержать  зна-
чение 5 - смещение байта после того байта, в котором найдено зна-
чение 0. Чтобы возвратить указатель на последний  символ  строки,
предыдущий фрагмент программы должен иметь следующий вид:

         .
         .
         .
         cld                         ; отсчет в строковой
                                     ; инструкции в прямом
                                     ; направлении
         les   di,[bp+ScanString]    ; ES:DI указывают на
                                     ; просматриваемую строку
         mov   cx,MAX_STRING_LEN     ; проверить до самой
                                     ; длинной строки
         mov   al,0                  ; поиск завершающего нуля
         repne scasb                 ; выполнить поиск
         jne   NoMatch               ; ошибка: завершающий 0
                                     ; не найден
         dec   di                    ; ссылка обратно на 0
         dec   di                    ; ccылка обратно на
                                     ; последний символ
         ret
NoMatch:
         mov   di,0                  ; возвратить нулевой
                                     ; указатель
         mov   es,di
         ret
         .
         .
         .

     Нужно помнить о том, что когда флаг  направления  установлен
таким  образом,  что в строковой инструкции будет выполняться об-
ратный отсчет, регистр DI будет указывать на предыдущий  байт,  а
не на последующий (после последнего найденного символа).

     Аналогичная путаница может произойти, когда при  использова-
нии инструкций REP SCAS и REP CMPS регистр CX уменьшается на еди-
ницу больше, чем этого можно ожидать. Значение регистра CX умень-
шается не только  для  каждого  байта,  удовлетворяющего  условию
"повторять,  пока  равно (или не равно)",  но и еще на 1 для того
байта,  для которого условие не выполнено (что приводит к прекра-
щению выполнения инструкции).

     Например, если в последнем примере байт  2000:0000  содержит
0, то после выполнения инструкции регистр CX содержал бы значение
MAX_STRING_LEN-1, даже  если  ни  один  ненулевой символ  не  был
найден. С учетом всего сказанного подпрограмма для подсчета числа
символов в строке должна иметь следующий вид:

; Возвращает длину в байтах строки, завершающейся нулем.
; Ввод:  ES:DI - начало строки
; Вывод: AX - длина строки, исключая завершающий 0
;        ES:DI - указывают на последний байт строки или
;        содержат 0000:0000, если завершающий 0 не был
;        найден
;
StringLength   PROC NEAR
         cld                  ; отсчет в прямом направлении
         push   cx            ; сохранить значение CX
         mov    cx,0FFFFh     ; максимальная длина поиска
         mov    al,0          ; завершающий байт, до которого
                              ; нужно выполнять поиск
         repne  scasb         ; поиск завершающего нуля
         jne    StringLengthError ; ошибка, если конец строки
                              ; не найден
         mov   ax,0FFFFh      ; максимальная длина
                              ; просматриваемой строки
         sub   ax,cx          ; посмотреть, сколько байт
                              ; было подсчитано
         dec   ax             ; не считать завершающий 0
         dec   di             ; переместить указатель обратно
                              ; на завершающий 0
         dec   di             ; переместить указатель на
                              ; последний символ
         jmp   short StringLengthEnd
StringLenghtError:
         mov   di,0           ; возвратить нулевой указатель
         mov   es,di
StringLengthEnd:
         pop   cx             ; восстановить исходное значение CX
         ret
StringLength   ENDP

     Другая потенциальная проблема, возникающая из-за  того,  что
регистр CX указывает со смещением на один байт  после  выполнения
инструкций  REP SCAS или REP CMPS,  состоит в том,  значение CX в
конце сравнения может быть нулевым,  даже если условие завершения
не обнаружено.  Следующий код не будет корректно определять, сов-
падают ли два массива,  так как регистр CX примет значение 0  при
сравнении двух несовпадающих массивов,  которые отличаются только
последним байтом:

         .
         .
         .
         repz   cmpsb
         jcxz   ArraysAreTheSame
         .
         .
         .

     Корректными инструкциями, проверяющими  равенство  массивов,
будут следующие:

         .
         .
         .
         repz   cmpsb
         jz     ArraysAreTheSame
         .
         .
         .

     Короче говоря, регистр CX следует  использовать  только  как
счетчик байт,  просматриваемых в инструкциях REP SCAS и REP CMPS,
а  не  как  указатель того,  что просматриваемые или сравниваемые
данные оказались равны или не равны.

     Если при работе в  ваших  программах  повторяемых  строковых
инструкций  вы  встретите затруднения, то лучше всего будет с по-
мощью карандаша и бумаги или отладчика отследить  по  шагам,  что
делает ваша повторяющаяся строковая инструкция.



       Нулевое содержимое регистра CX и работа с целым сегментом
-----------------------------------------------------------------

     При выполнении любой строковой инструкции с  содержимым  ре-
гистра CX,  равным 0,  не будет выполняться никаких функций.  Это
может оказаться удобным, так как перед выполнением строковой инс-
трукции не нужно делать проверку на 0. С другой стороны, нет спо-
соба получить доступ к каждому байту сегмента с  помощью байтовой
строковой инструкции.  Например, в следующем фрагменте кода прос-
матривается сегмент, заданный регистром ES, и ищется первое вхож-
дение буквы A:

         .
         .
         .
         cld             ; поиск в прямом направлении
         sub   di,di     ; начать по смещению 0
         mov   al,'A'    ; до обнаружения буквы 'A'
         mov   cx,0FFFFh ; сначала проверить первые 64К
         repne SCASb     ; просмотреть первые 64К-1 байт
         je    AFound    ; найти A
         scasb           ; еще не найдена: просмотреть
                         ; последний байт
         je    AFound    ; найти ее в последнем байте
         .               ; в данном сегменте нет буквы 'A'
         .
         .
AFound:                  ; DI - 1 указывает на букву 'A'
         .
         .
         .

     В использовании при отсчете нулевых значений регистра  CX  в
наборе  инструкций  процессора  8086  имеется "несимметрия". В то
время, как повторяющаяся строковая инструкция при нулевом  значе-
нии  CX вообще не выполняет никаких операций, инструкция LOOP при
значении CX, равным  0,  выполняется,  уменьшая  CX  до  значения
0FFFFh  и осуществляя переход на адрес цикла. Это означает, что в
одном цикле можно обработать все 64К. Предыдущий пример, где  со-
держимое  сегмента,  заданного  регистром  CX, просматривается на
предмет наличия буквы A, можно реализовать с  помощью  инструкции
LOOP следующим образом:

         .
         .
         .
         cld             ; просмотр в прямом направлении
         sub   di,di     ; начать со смещения 0
         mov   al,'A'    ;
         sub   cx,cx     ; поиск в 64К
ASearchLoop:
         scasb           ; проверить следующий байт
         je    AFound    ; это буква 'A'
         loop  ASearchLoop ; в этом сегменте нет буквы 'A'
         .
         .
         .
AFound:                  ; на букву 'A' указывает DI - 1
         .
         .
         .

     С другой стороны, случай, когда CX = 0, требует  специальной
проверки  при  использовании  инструкции LOOP (в противном случае
будут обработаны 64К кода с возможно катастрофическими для  прог-
раммы  последствиями). В таких случаях полезно использовать инст-
рукцию JCXZ:

; Подпрограмма для заполнения 64К - 1 байт заданным значением.
; Ввод:  AL - заданное значение-заполнитель
;        CX - количество заполняемых байт
;        DS:BX - начальный адрес заполнения
; Регистры BX и CX изменяются.
;
FillBytes    PROC NEAR
         jcxz FillBytesEnd        ; если число заполняемых байт
                                  ; равно 0, выполнить
FillBytesLoop:
         mov   [bx],al            ; заполнить байт
         inc   bx                 ; ссылка на следующий байт
         loop  FillBytesLoop      ; выполнить для заданного
                                  ; числа байт
FillBytesEnd:
         ret
FillBytes      ENDP

     Без инструкции JCXZ, когда значение CX  равно  0,  процедура
FillBytes заполнила бы весь сегмент, на который указывает регистр
ES, значением в регистре AL, вместо того, чтобы  оставить  память
без изменений.


         Использование некорректно заданного флага направления
-----------------------------------------------------------------

     При выполнении строковой инструкции в зависимости от состоя-
ния флага направления соответствующие регистры-указатели (или ре-
гистр) SI, DI или оба регистра увеличиваются или уменьшаются.

     Флаг направления можно очистить с  помощью  инструкции  CLD,
при этом указатели будут увеличиваться (отсчет в прямом направле-
нии), или установить с помощью инструкции STD, при этом указатели
будут уменьшаться (отсчет в обратном направлении). Будучи очищен-
ным или установленным, флаг направления остается в таком  состоя-
нии  до выполнения следующей инструкции CLD или STD или до извле-
чения флагов из стека с помощью инструкции POPF  или  IRET.  Хотя
очень  удобно бывает установить один раз в программе флаг направ-
ления, а затем выполнять серию строковых инструкций, работающих в
одном  направлении, из-за него могут возникать также трудно обна-
руживаемые ошибки, что приводит к неожиданному поведению  строко-
вых инструкций, в зависимости от кода, который выполнялся намного
раньше.

     Почему это происходит?  В большинстве программ флаг  направ-
ления  почти  всегда сбрасывается, поскольку отсчет в прямом нап-
равлении интуитивно проще, чем отсчет в обратном  направлении,  и
работает  он во многих случаях прекрасно. Однако, имеются отдель-
ные ситуации, когда можно использовать только отсчет  в  обратном
направлении.  Если  вы по привычке будете подразумевать, что флаг
направления всегда очищен, но забудете очистить флаг после  одной
(или нескольких) из процедур, где флаг направления устанавливает-
ся, то в результате та часть вашей программы, где отсчет  ведется
в  прямом направлении, будет работать прекрасно, до того момента,
пока не выполнится процедура, устанавливающая флаг направления.

     Выход очевиден. Перед использованием  строковых  инструкций,
если  есть  даже небольшая вероятность того, что флаг направления
не установлен должным образом, его всегда нужно  устанавливать  в
нужное состояние. В общем случае хорошим стилем является установ-
ка флага в соответствующее значение в начале любой процедуры, где
используются строковые инструкции.



Неправильное использование повторяемого сравнения строк
-----------------------------------------------------------------

     Инструкция CMPS сравнивает две области памяти, в  то  время,
как  инструкция  SCAS сравнивает аккумулятор области памяти. Если
перед инструкцией следует префикс REPE, то каждая из  этих  инст-
рукций  может выполнять сравнение, пока не станет равным содержи-
мое регистра CX или не  обнаружится  несовпадение.  К  сожалению,
легко  можно спутать, какой из префиксов повторения REP что дела-
ет.

     Хороший способ запомнить функцию данного префикса REP состо-
ит  в мысленном включении после REP (повторить) слова "пока". При
этом, например, REPE принимает вид "повторять, пока E"  (то  есть
"повторять,  пока  равно"),  REPNE - "повторять, пока не равно" и
т.д.



     Программист забывает об использовании сегментов по умолчанию
-----------------------------------------------------------------

     В каждой строковой инструкции используется (если он имеется)
исходный  сегмент  (источник),  заданный  регистром DS, и целевой
сегмент (приемник), заданный регистром ES. Об этом легко забыть и
попытаться применить, скажем, инструкцию STOSB к сегменту данных,
поскольку именно там обычно находятся все данные, которые вы  об-
рабатываете  с  помощью нестроковых инструкций. Можно легко напи-
сать следующее:

         .
         .
         .
         cld             ; отсчет при поиске в прямом направлении
         mov   al,0
         mov   cx,80     ; длина буфера
         repe  scasb     ; найти первый ненулевой символ, если он

                         ; имеется
         jz    AllZero   ; нет ненулевого символа
         dec   di        ; ссылка обратно на первый ненулевой
                         ; символ
         mov   al,[dl]   ; получить первый ненулевой символ
         .
         .
         .
 AllZero:
         .
         .
         .

     Проблема здесь состоит в том, что если DS и ES не совпадают,
последняя  инструкция MOV не будет загружать в AL корректное зна-
чение, так как инструкция STOSB  работает  относительно  регистра
ES,  а  MOV - относительно регистра DS. В правильном коде в инст-
рукции MOV следовало бы использовать префикс переопределения сег-
мента (пояснение этого приводится в Главе 9).

         .
         .
         .
         cld             ; отсчет при поиске в прямом направлении
         mov   al,0
         mov   cx,80     ; длина буфера
         repe  scasb     ; найти первый ненулевой символ, если он
                         ; имеется
         jz    AllZero   ; нет ненулевого символа
         dec   di        ; ссылка обратно на первый ненулевой
                         ; символ
         mov   al,es:[dl]  ; получить первый ненулевой символ
                         ; (из ES!)
         .
         .
         .
 AllZero:
         .
         .
         .

     Нужно также помнить о том,  что  хотя  можно  переопределить
сегмент DS, используемый в качестве сегмента-источника, например:

         .
         .
         .
         lods  es:[SourceArray]
         .
         .
         .

сегмент-приемник ES переопределить нельзя. Поэтому следующий  ва-
риант работать не будет:

         .
         .
         .
         stos  DS:[DestArray]
         .
         .
         .

     Такие ошибки Турбо Ассемблер распознает на этапе ассемблиро-
вания.



   Ошибки при использовании байтовых операций и операций со словами
-----------------------------------------------------------------

     В общем случае в строковых инструкциях желательно  использо-
вать  максимально  возможный размер данных (обычно это слово, или
двойное слово при работе с процессором  80386),  поскольку  такие
строковые инструкции обычно выполняются быстрее. Например:

         .
         .
         .
         mov   cx,200   ; число перемещаемых байт
         .
         .
         .
         shr   cx,1     ; преобразовать из байт в слова
         rep   movsw    ; переместить блок размером в слово
         .
         .
         .

     На процессоре 8088 это работает почти на 50% быстрее, чем:

         .
         .
         .
         mov   cx,200   ; число перемещаемых байт
         .
         .
         .
         rep   movsw    ; переместить блок размером в байт
         .
         .
         .

     Однако здесь имеется пара потенциальных  ошибок.  Во-первых,
при преобразовании счетчика байт в счетчик слов просто  с помощью
операции:

         shr   cx,1

теряется байт, если регистр CX нечетный, поскольку при сдвиге на-
именее значащий бит будет потерян.  Случаи, когда значение CX мо-
жет оказаться нечетным, можно обрабатывать следующим образом:

         .
         .
         .
         shr   cx,1     ; преобразовать в счетчик слов
         jnc   MoveWord ; счетчик байт нечетный?
         movsb          ; да, он нечетный - переместить
                        ; нечетный байт
   MoveWord:
         rep   movsw    ; перемещение четного числа байт
                        ; пословно
         .
         .
         .

     Во-вторых, нужно помнить о том,  что  инструкция  SHR  делит
счетчик байт на 2.  Использование,  скажем,  инструкции STOSW  со
счетчиком байт,  а  не со счетчиком слов,  может отбросить другие
данные и привести к различным проблемам.  Например, при использо-
вании инструкций:

         .
         .
         .
         mov   cx,200      ; число перемещаемых данных
         .
         .
         .
         rep   movsv       ; перемещать по блоку размером
                           ; в слово
         .
         .
         .

будет отброшено 200 байт (100 слов),  которые следуют  непосредс-
твенно за целевым блоком.



                  Использование нескольких префиксов
-----------------------------------------------------------------

     Строковые инструкции с несколькими префиксами надежно  рабо-
тать  не  будут.  В  общем случае их следует избегать. В качестве
примера можно привести инструкцию:

         .
         .
         .
         rep   movs es:[DestArray],ss:[SourceArray]
         .
         .
         .

где присутствует как префикс REP, так и  префикс  переопределения
сегмента SS. Множественные префиксы могут привести к ошибкам, так
как по аппаратному прерыванию строковая инструкция может  прекра-
тить  работу в процессе цикла повторения. В некоторых процессорах
фирмы Intel, включая процессоры 8086  и  8088,  после  того,  как
строковая  инструкция возобновляет работу послу обслуживания пре-
рывания, все префиксы, кроме последнего, игнорируются. В  резуль-
тате инструкция может не отработать заданное число раз, или прои-
зойдет обращение к неверному сегменту.

     Если вам абсолютно необходимо использовать  строковые  инст-
рукции  с несколькими префиксами, то на время выполнения инструк-
ции нужно запретить прерывания. Например:

         .
         .
         .
         cli
         rep   mov   es:[DestArray],ss:[SourceArray]
         sti
         .
         .
         .



        Ошибки при использовании операндов строковых инструкций
-----------------------------------------------------------------

     Необязательный операнд или операнды строковых инструкций ис-
пользуются  только  для  задания размера данных и переопределения
сегмента и не обеспечивают того, что соответствующие ячейки памя-
ти действительно будут доступны. Например, в программе:

         .
         .
         .
DestArray  DW   256  dup (?)
         .
         .
         .
         cld                     ; отсчет в прямом направлении
                                 ; при заполнении
         mov   al,'*'            ; байт для заполнения
         mov   cx,256            ; число заполняемых слов
         mov   di,0              ; адрес начала заполнения
         rep   stos es:[DestArray] ; выполнить заполнение
         .
         .
         .

256 байт, начиная со смещения 0 в сегменте ES, заполняются симво-
лом '*',  независимо от того,  где  находится  массив  DestArray.
ES:[DestArray]  просто указывает Ассемблеру,  что нужно использо-
вать инструкцию STOSW, так как DestArray - это массив слов. Имен-
но содержимое SI и/или DI,  а не операнды,  определяет, по какому
смещению будет осуществляться доступ в строковых инструкциях. Тем
не  менее,  использование  необязательных  операндов (операнда) в
строковых инструкциях обеспечивает, что вы, например, случайно не
будете  случайно осуществлять пословный доступ к байтовому масси-
ву.

     Аналогично, необязательный операнд в инструкции XLAT исполь-
зуется только для проверки типа и переопределения сегмента. Прог-
рамма:

         .
         .
         .
LookUpTable    LABEL   BYTE
         .
         .
         .
ASCIITable     LABEL   BYTE
         .
         .
         .
         mov   bx,OFFSET ASCIITabel      ; ссылка на таблицу
                                         ; просмотра
         mov   al,[CharacterToTranslate] ; получить байт
         xlat  [LookUpTable]             ; отобразить его
         .
         .
         .

отображает  байт,  задаваемый регистром AL, в таблице ASCIITable,
а  не в LookUpTable, но Ассемблер здесь будет работать правильно,
поскольку все, что делает операнд в инструкции XLAT - это обеспе-
чение  байтового  размера  и переопределение сегмента. Инструкция
XLAT всегда отображает (транслирует) содержимое по смещению BX  +
AL, независимо от используемого операнда.




Программист забывает о необычных побочных эффектах
-----------------------------------------------------------------

     Поскольку программы Ассемблера записаны  на  "родном"  языке
процессора 8086, любые изменения в состоянии регистров или флагов
процессора 8086 должны представлять для работающего на Ассемблере
программиста  особый интерес. Большинство способов, с помощью ко-
торых программа на Ассемблере может изменить состояние  процессо-
ра, достаточно непосредственны и очевидны. Например, инструкция:

             add   bx,[Grade]

прибавляет 16-битовое значение по адресу Grade к BX и, чтобы  от-
разить  результат  сложения,  изменяет флаги переполнения, знака,
нуля, дополнительного переноса, четности и переноса. Однако неко-
торые  инструкции  изменяют  состояние процессора менее очевидным
образом. Рассмотрим кратко некоторые из таких инструкций.



               Потеря содержимого регистра при умножении
-----------------------------------------------------------------

     При  умножении  (8-разрядного   значения   на   8-разрядное,
16-разрядного значения на 16-разрядное или 32-разрядного значения
на 32-разрядное) всегда теряется содержимое по крайней мере одно-
го регистра, отличного от той части аккумулятора, которая исполь-
зуется в качестве операнда-источника. Это  неизбежно  приводит  к
тому,  что результат перемножения двух 8-разрядных значений будет
занимать 16  бит,  результат перемножения 16-разрядных значений -
32 бита, а 32-разрядных значений - 64 бита. Перемножение операнда
-источника и операнда-приемника показано в Таблице 6.1.

                                                      Таблица 6.1
        Источник и приемник в операциях MUL и IMUL
-----------------------------------------------------------------
Источник               Источник                 Приемник
Размер операнда  Явный    Неявный
в байтах        операнд   операнд     Старший  Младший    Пример
-----------------------------------------------------------------
 8х8            reg8 (*)    AL          AH       AL       mul dl

16х16          reg16 (**)   AX          DX       AX      imul bx

32х32 (***)    reg32 (****) EAX         EDX      EAX     mul esi
-----------------------------------------------------------------

*     reg8 может представлять собой любой из следующих регистров:
      AH, AL, BH, BL, CH, CL, DH или DL.

**    reg16 может быть любым из следующих регистров:  AX, BX, CX,
      DX, SI, DI, BP или SP.

***   Операция  умножения  32х32  процессорами 8086, 8088, 80186,
      80188 и 80286 не поддерживается.0

****  reg32 может быть любым из  следующих  регистров:  EAX, EBX,
      ECX, EDX, ESI, EDI, EBP или ESP.

     Хотя все это выглядит достаточно простым,  в синтаксисе инс-
трукций MUL и IMUL скрыто много деталей, так как явно указывается
только один из операндов и размер,  а регистры используемые в ка-
честве операнда-приемника,  просто подразумеваются.  Эти  скрытые
детали приводят к тому,  что легко можно упустить из виду исполь-
зование какого-либо неявного регистра.  Есть много случаев, в ко-
торых,  скажем,  программист  знает,  что  результат перемножения
16-разрядного значения на 16-разрядную величину, поместится в ре-
гистр  AX.  При этом часто забывают,  что теряется содержимое ре-
гистра CX.  Поэтому всегда нужно помнить о том, что при использо-
вании  инструкций  MUL  и  IMUL уничтожается содержимое не только
регистров AL, AX, или EAX, но также и AH, DX или EDX.




В строковых инструкциях изменяется несколько регистров
-----------------------------------------------------------------

     При выполнении только одной из строковых  инструкций  (MOVS,
STOS, LODS, CMPS или SCAS) может изменяться содержимое нескольких
флагов и до трех регистров. Как и в инструкции MUL,  в  строковых
инструкциях  многие  эффекты не выражаются  явно в операндах этих
инструкций. При использовании данных инструкций нужно  помнить  о
том,  что SI или DI (или оба регистра) увеличиваются или уменьша-
ются (в зависимости от состояния флага  направления)  при  каждом
выполнении  строковой инструкции. Регистр CX также уменьшается по
крайней мере один раз, а при использовании префикса REP - возмож-
но до тех пор, пока его содержимое не станет равным нулю.

           Изменение отдельными инструкциями флага переноса
-----------------------------------------------------------------

     В то время как некоторые инструкции "непредвиденным" образом
изменяют  содержимое  регистров  или флагов, другие инструкции не
влияют на все те флаги, изменения которых вы ожидаете.  Например,
инструкция:

            inc   ah

выглядит логически эквивалентной инструкции:

            add   ah,1

и это действительно так, но с одним исключением. В то  время  как
инструкция  ADD  в случае слишком большого для операнда-приемника
результата устанавливает флаг переноса, инструкция INC никоим об-
разом не него не влияет. В результате инструкции:

            .
            .
            .
            add   ax,1
            adc   dx,0
            .
            .
            .

можно использовать для увеличения 32-битового значения,  храняще-
гося в регистрах DX:AX, а инструкции:

            .
            .
            .
            inc   ax
            adc   dx,0
            .
            .
            .

нельзя. Тоже самое имеет место для инструкции DEC, в то время как
инструкции LOOP, LOOPZ и LOOPNZ не влияют на состояние флагов. На
практике это иногда можно выгодно использовать, так как в отдель-
ных  случаях может оказаться удобным выполнить одну из этих инст-
рукций без нарушения установки флага переноса. Всегда важно точно
знать, что делает каждая используемая вами инструкция. Если у вас
есть сомнения относительно того, как влияет конкретная инструкция
на содержимое флагов, лучше обратитесь к справочнику.



           Программист долго не использует состояние флагов
-----------------------------------------------------------------

     Состояние флагов сохраняется  до  тех  пор,  пока  следующая
инструкция  их  не изменит. Обычно это не слишком долгий интервал
времени. Хорошей  практикой  программирования  является  возможно
скорейшее  использование флагов их установки, что позволяет избе-
жать многих потенциальных ошибок. Например, часто хочется  прове-
рить  условие,  задать содержимое одного-двух регистров, и только
после этого в соответствии с результатом проверки выполнить пере-
ход. Инструкции:

            .
            .
            .
            cmp   ax,1
            mov   ax,0
            jg    HandlePositive
            .
            .
            .

представляют собой вполне допустимый  способ  проверки  состояния
регистра AX, затем установки его в значение 0 и обработки резуль-
тата. С другой стороны, инструкции:

            .
            .
            .
            cmp   ax,1
            sub   ax,ax
            jg    HandlePositive
            .
            .
            .

которые выглядят более привлекательно, поскольку такой код короче
и  выполняются  быстрее, правильно работать не будут, так как при
вычитании теряется  содержимое  всех  флагов,  установленных  при
сравнении.  Это типичная проблема, возникающая, когда вы не торо-
питесь использовать состояние флагов.



       Не путайте операнды в памяти и непосредственные операнды
-----------------------------------------------------------------

     Программа на Ассемблере может ссылаться либо  на  переменную
памяти  по смещению, либо на значение, хранящееся в этой перемен-
ной. К сожалению, в языке Ассемблера  отсутствует  строгость  или
очевидность  в  отношении  того, как можно выполнять эти два вида
ссылок. В результате ссылки на переменную в памяти по смещению  и
по значению часто путают.

     На Рис. 6.1 показано различие между  смещением  и  значением
переменной  в памяти. Переменная в памяти размером в слово MemLoc
имеет смещение 5002h, а значение ее равно 1234h.

                                     |
                              ---------------
                   3000 4FFE  |    0001     |
                              |-------------|  Значение MemLoc
                   3000 5000  |    205F     |         |
     Смещение                 |-------------|         |
     MemLoc        3000 5002  |    1234 <---|----------
       |                ----  |-------------|
       |                 ^    |             |
       |                 |    |             |
       -------------------    |             |
                   3000 5004  |    9145     |
                              |-------------|
                   3000 5006  |    0000     |
                              ---------------

     Рис. 6.1 Переменные в памяти: значение и смещение.

     На Рис. 6.1 смещение переменной в памяти  размером  в  слово
MemLoc  представляет собой константу 5002, которую можно получить
с помощью оператора OFFSET. Например, инструкция:

             mov   bx,OFFSET MemLoc

загружает значение 5002h в регистр BX. Значение 5002h представля-
ет  собой непосредственный операнд. Другими словами, оно встроено
непосредственно в инструкцию и не изменяется.

     Значением MemLoc является 1234h. Оно считывается  из  памяти
со смещением 5002h в сегменте данных. Один из способов считывания
данного значения состоит в загрузке в регистр BX, SI, DI  или  BP
смещения  MemLoc и использования данного регистра для адресации к
памяти. Инструкции:

             mov   bx,OFFSET MemLoc
             mov   ax,[bx]

загружают значение MemLoc в регистр  AX.  Значение  MemLoc  можно
также загрузить непосредственно в AX с помощью инструкции:

             mov   ax,MemLoc
 или
             mov   ax,[MemLoc]

     Здесь значение 1234h получается, как прямой, а не как непос-
редственный  операнд:  инструкция MOV использует встроенное в нее
смещение 5002h и загружает в AX значение по смещению 5002h, кото-
рое в данном случае равно 1234h.

     В итоге значение 1234h не связывается постоянно с переменной
MemLoc. Например, инструкции:

             mov   [MemLoc],5555h
             mov   ax,[MemLoc]

загружают в регистр AX значение 5555h, а не 1234h.

     Основная идея заключается в том, что если смещение  перемен-
ной  MemLoc представляет собой значение-константу, которая описы-
вается фиксированным  адресом  в  сегменте  данных,  то  значение
MemLoc - это изменяемое число, хранящееся по данному адресу. Пос-
ле выполнения инструкций:

             mov   [MemLoc],1
             add   [MemLoc],3

переменная MemLoc получает значение 3, но инструкция:

             add   OFFSET MemLoc,2

эквивалентна инструкции:

             add   5002h,2

которая не имеет смысла, так как  невозможно  выполнить  операцию
ADD, прибавив одну константу к другой.

     Удивительно часто встречающейся ошибкой является то, что ув-
лекшись написанием программы часто забывают использовать операцию
OFFSET, например:

             mov   si,MemLoc

где нужно использовать смещение MemLoc. на первый  взгляд  данная
строка  не выглядит неправильной, и так как MemLoc - это перемен-
ная размером в слово, то эта строка не приведет к  ошибке  ассем-
блирования. Однако при выполнении в SI будут загружены содержащи-
еся в переменной MemLoc данные (1234h), а не ее смещение (5002h),
и результаты будут непредсказуемы.

     Надежного способа избежать этой проблемы нет, но можно  при-
нять за правило заключать все ссылки на память в квадратные скоб-
ки. Когда перед ссылками на адресные константы будет  указываться
префикс  OFFSET,  а  ссылки  на память - заключаться в квадратные
скобки, это устранит двусмысленность и неопределенность  при  ис-
пользовании  имен  переменных памяти. При таком соглашении работа
инструкций:

             mov   si,OFFSET MemLoc
 и
             mov   si,[MemLoc]

становится совершенно понятной, в то время как инструкция:

             mov   si,MemLoc

будет настораживать.



                           Границы сегментов
-----------------------------------------------------------------

     Один из наиболее трудных моментов при  программировании  для
процессоров 8086 заключается в том, что к памяти нельзя обращать-
ся, как к одному большому массиву байт.  Она доступна  только  по
частям,  каждая  из  которых равна 64К и связана с сегментным ре-
гистром. Использование сегментов может приводить к трудноуловимым
ошибкам,  так  как если программа пытается обратиться к адресу за
концом сегмента,  она в действительности вернется назад  и  будет
обращаться к началу сегмента.

     В качестве примера предположим, что память,  начинающаяся  с
адреса 10000h, содержит данные, показанные на Рис. 6.2. Когда ре-
гистр DS устанавливается в значение 1000h, программа, которая об-
ращается  к строке "Testing" по адресу 1000:FFF9, после символа g
по  адресу  1000:FFFF,  возвращается  назад  к  байту  по  адресу
1000:0000, так как смещение не может превышать 0FFFFh (максималь-
ное 16-битовое значение).

                             Первый байт, адресуемый относительно
                             DS, равен 1000h (адрес 1000 0000)
                  |                       |
           ---------------                |
    10000  |     21      |<----------------
           |-------------|
    10001  |     90      |
           |-------------|
    10002  |     29      |
           |-------------|
    10003  |     52      |
           |-------------|
    10004  |     7F      |
           ---------------

           ---------------
    1FFF9  |   54 ('T')  |
           |-------------|
    1FFFA  |   65 ('e')  |
           |-------------|
    1FFFB  |   73 ('s')  |
           |-------------|
    1FFFC  |   74 ('t')  |
           |-------------|
    1FFFD  |   69 ('i')  |    Последний байт, адресуемый
           |-------------|     относительно DS = 1000h
    1FFFE  |   6E ('n')  |       (адрес 1000 FFFF)
           |-------------|               |
    1FFFF  |   67 ('g')  |<---------------
           |-------------|
    20000  |   00 (NULL) |
           ---------------

     Рис. 6.2 Пример достижения границы сегмента.

     Предположим теперь, что при DS:SI, равном 1000:FFF9, вызыва-
ется  подпрограмма  для преобразования строки "Testing" в верхний
регистр:

; Подпрограмма для преобразования завершающейся нулевым
; символом строки в верхний регистр.
;
; Ввод:   DS:DI - указатель на строку.
;
ToUpper   PROC NEAR
          mov   ax,[si]         ; получить следующий символ
          cmp   al,0            ; если 0...
          jz    ToUpperDone     ; ...преобразовать строку
          cmp   al,'a'          ; это строчная буква?
          jb    ToUpperCase     ; не строчная буква
          cmp   al,'z'
          ja    ToUpperNext     ; не строчная буква
          and   al,NOT 20h      ; строчная буква, преобразовать
                                ; ее в верхний регистр
          mov   [si],al         ; сохранить прописную букву
ToUpperNext:
          inc   si              ; ссылка на следующий символ
          jmp   ToUpper
ToUpperDone:
          ret
ToUpper   ENDP

     После того, как процедура  ToUpper  обработает  первые  семь
символов  строки,  SI  изменит  значение с 0FFFFh на 0. (SI - это
16-разрядный  регистр,  поэтому  отсчет  с  превышением  значения
0FFFFh  выполнить нельзя.) Завершающий строку нулевой байт, запи-
санный по адресу 20000h, достигнут не будет. Вместо этого  проце-
дура начнет преобразовывать не относящиеся к делу байты по адресу
10000h и не остановится, пока не встретит нулевой байт. На  более
позднем этапе эти измененные байты могут вызвать некорректную ра-
боту программы. Часто такие ошибки, вызванные случайным изменени-
ем байт при достижении программы  конца  сегмента,  бывает  очень
трудно отследить, поскольку эти ошибки могут проявляться совсем в
другом месте программы и в другое время.

     Простую рекомендацию здесь дать трудно. Нужно просто обеспе-
чивать, чтобы ваша программа непреднамеренно не выходила за конец
сегмента. Кроме того не следует  обращаться  к  слову  с  адресом
0FFFFh. Машина может "зависнуть".



        Неполное сохранение состояния в обработчике прерываний
-----------------------------------------------------------------

     Обработчик прерываний - это  программа,  на  которую  осуще-
ствляется переход при аппаратном прерывании, например, прерывании
от клавиатуры. Обработчик прерываний выполняет множество функций,
таких,  как  буферизация клавиш или изменение системного таймера.
Прерывание может произойти в любое время в процессе работы  любой
программы,  поэтому обработчик прерываний после завершения работы
(выход из обработчика) должен вернуть регистры и флаги процессора
в точности в тоже самое состояние, которое было при входе в обра-
ботчик прерываний. Если это не делается, то программа, при выпол-
нении которой произошло прерывание, может обнаружить, что состоя-
ние процессора непредвиденным образом изменилось.

     Например, при выполнении программы:

              .
              .
              .
              mov   ax,[ReturnValue]
              ret
              .
              .
              .

прерывание может произойти между этими двумя  инструкциями.  Если
обработчик прерываний не сохранит содержимое AX,  то возвращаемое
в вызывающую  программу  значение  будет основываться на том, что
делал   обработчик  прерываний,   а  не  на  значении  переменной
ReturnValue.

     Поэтому каждый  обработчик  прерываний  должен явным образом
сохранять содержимое всех регистров.  Хотя допускается  сохранять
только те регистры, которые изменяет обработчик  прерываний,  для
большей гарантии лучше при входе в обработчик занести все регист-
ры в стек, а при выходе - восстановить их из стека.  Ведь в  один
прекрасный день вы можете вернуться к коду  обработчика  прерыва-
ний, модифицируете его и используете дополнительные регистры, за-
быв при этом данные регистры сохранить.

     Сохранение  флагов  в  обработчике прерываний необходимым не
является. Когда происходит прерывание,  все  флаги  автоматически
заносятся в стек, а когда обработчик прерываний  выполняет  инст-
рукцию IRET, чтобы вернуться в прерванную программу, флаги  авто-
матически восстанавливаются из стека. Итогом абсолютной необходи-
мости сохранения в обработчике прерываний всех регистров является
следующее правило: нельзя делать предположения  о  состоянии  ре-
гистров или флагов при входе в обработчик прерываний.  Классичес-
ким примером этого является обработчик  прерываний,  который  вы-
полняет строковые  инструкции, не установив предварительно  явным
образом флаг направления. Нужно помнить о том, что когда происхо-
дит прерывание,  может выполняться любая программа, поэтому после
того,  как вы сохраните регистры прерванной программы,  вы должны
установить  регистры  (включая сегментные регистры) и флаги таким
образом,  как это необходимо. Только после этого вы можете выпол-
нять другие функции.



   Не забывайте об определении группы в операндах и таблицах данных
-----------------------------------------------------------------

     Концепция группы сегментов проста и полезна. Вы можете опре-
делить,  что  несколько  сегментов  принадлежат  к одной и той же
группе, а компоновщик комбинирует эти сегменты  в  один  сегмент.
При  этом все данные в сгруппированных сегментах адресуются отно-
сительно одного и того же сегментного регистра. На Рис. 6.3 пока-
зано три сегмента: Seg1, Seg2 и Seg3, сгруппированные в GroupSeg.
Адресация ко всем трем сегментам осуществляется одновременно  от-
носительно  одного сегментного регистра, загруженного базовым ад-
ресом GroupSeg.

                                     Смещение 0 в GroupSeg
                                     = смещению 0 в Seg1
                                             |
             / ---------------------- <-------
            |  |                    |
            |  |                    |
            |  |       Seg1         |     Смещение 2000h в
            |  |    (размер 8K)     |     GroupSeg = смещению 0
            |  |                    |     в Seg2
            |  |                    |        |
            |  |--------------------| <-------
            |  |                    |
            |  |                    |
            |  |       Seg2         |
            |  |    (размер 12K)    |
  GroupSeg  |  |                    |     Смещение 5000h в
            |  |                    |     GroupSeg = смещению 0
            |  |                    |     в Seg3
            |  |                    |        |
            |  |--------------------| <-------
            |  |                    |
            |  |                    |
            |  |       Seg3         |
            |  |    (размер 64K)    |
            |  |                    |
            |  |                    |
            |  |                    |
            |  |                    |
             \ ----------------------

     Рис. 6.3 Три сегмента, объединенные в одну группу сегментов.

     Группы сегментов позволяют вам логически разделить данные на
ряд  областей без необходимости загружать сегментный регистр каж-
дый раз, когда вы хотите перейти от одной логической области дан-
ных к другой.

     К сожалению, в обработке  групп сегментов  в Макроассемблере
фирмы  Microsoft  MASM имеется ряд проблем, поэтому, пока не поя-
вился Турбо Ассемблер, группы сегментов могли привести к неприят-
ностям. Группы сегментов использовались для компоновки  кода  Ас-
семблера с языками высокого уровня (например, Си).

     Улучшенный  режим Турбо Ассемблера (Ideal mode) избавит  вас
от  проблем,  связанных  с  определением групп сегментов. Это еще
один довод в пользу перехода от программирования в стиле  MASM  к
улучшенному режиму.

     Проблема, которую порождает MASM при работе с группами  сег-
ментов заключается в том, что MASM интерпретирует смещения, полу-
ченные с помощью операции OFFSET в данном  сегменте  группы,  как
смещение  в  этом сегменте, а не как смещение в группе сегментов.
Например, если мы имеет группу сегментов, показанную на Рис. 6.3,
то Ассемблер транслировал бы инструкцию:

               mov   ax,OFFSET Var1
в
               mov   ax,0

так как Var1  - это смещение 0 в Seg2, хотя Var1 представляет со-
бой смещение 2000h в GroupSeg. Поскольку предполагается, что дан-
ные в группе  сегментов адресуются относительно группы сегментов,
а не относительно отдельного сегмента, это порождает ряд проблем.

     Решением здесь является использование  префикса  определения
группы. Строка:

               mov   ax,OFFSET GroupSeg:Var1

позволяет выполнить корректное ассемблирование Var1, вычисляя его
относительно группы сегментов GruopSeg.

     В MASM есть другие аналогичные проблемы, касающиеся  исполь-
зуемых  в группах сегментов таблиц данных. Как и операция OFFSET,
смещения, ассемблируемые в таблицы данных,  генерируются  относи-
тельно  сегментов,  а  не относительно групп сегментов. Суть этой
проблемы показана в следующем примере:

Stack            SEGMENT WORD STASK 'STACK'
        DB       512 DUP (?)         ; зарезервировать простран-
                                     ; ство для стека размеров
                                     ; 1/2 К
Stack            ENDS

;
; Определить группу сегмента данных DGROUP, состоящую
; из Data1 и Data2
;
DGROUP           GROUP   Data1, Data2

;
; Первый сегмент в DGROUP.
;
Data1            SEGMENT WORD PUBLIC 'DATA'
Scratch          DB      100h DUP (0)   ; буфер размером 256K
Data1            ENDS

;
; Второй сегмент в DGROUP.
;
Data2            SEGMENT WORD PUBLIC 'DATA'
Buffer           DB      100h DUP ('@') ; буфер размером 256К,
                                        ; заполненный знаками @
BufferPtr        DW      Buffer         ; указатель на буфер
Data2            ENDS

Code             SEGMENT PARA PUBLIC 'CODE'
                 ASSUME  CS:Code, DS:DGROUP

;
Start            PROC NEAR
         mov   ax,DGROUP
         mov   ds,ax          ; DS указывает на DGROUP
         mov   bx,OFFSET GROUP:BufferPtr ; ссылка на указатель
                              ; буфера
                              ; (для получения корректного
                              ; смещения используется
                              ; определение группы)
         mov   bx,[bx]        ; ccылка на сам буфер

;
; (Здесь должен следовать код для обработки буфера)
;
         mov   ah,4Ch         ; функция DOS завершения
                              ; программы
         int   21h            ; завершить программу и
                              ; выйти в DOS
Start          ENDP
Code           ENDS
         END   Start

     В данной программе смещение BufferPtr в инструкции:

         mov   bx,OFFSET DGROUP:BufferPtr

ассемблируется корректно, так как используется префикс  определе-
ния DGROUP:группа. Однако другая ссылка на смещение:

BufferPtr      DW   Buffer

которая должна приводить к инициализации значения BufferPtr  сме-
щением  Buffer, не будет корректно ассемблироваться, так как сме-
щение Buffer берется относительно сегмента Data2,  а  не  относи-
тельно группы сегментов DGROUP. Решением здесь опять является ис-
пользование префикса определения DGROUP, для чего нужно изменить

BufferPtr      DW   Buffer

на

BufferPtr      DW   DGROUP:Buffer  ; указатель на Buffer
                                   ; (для получения
                                   ; корректного указателя
                                   ; используется
                                   ; определение группы)

     Пропуск префикса определения группы при использовании  групп
сегментов  в режиме MASM приводит к некоторым неприятным ошибкам,
так как ваша программа может при этом выполнять  чтение,  переход
или  модификацию неверных областей памяти. В качестве общего пра-
вила можно порекомендовать не использовать группы при ассемблиро-
вании  в  режиме  MASM,  если без этого можно обойтись. Когда вам
приходится использовать группы сегментов (например, при организа-
ции интерфейса с языком высокого уровня) не забывайте при задании
смещений для всех данных группы использовать префикс  определения
группы.  Этим префиксом довольно легко пользоваться, нужно только
не забывать об этом.

     Полезным методом при работе с  группой  сегментов  в  режиме
MASM  является  использование  вместо  MOV OFFSET инструкции LEA.
Например, инструкция:

               lea   ax,Var1

выполняет то же действие, что и инструкция:

               mov   ax,OFFSET GroupSeg:Var1

не требуя использования префикса определения группы. Однако инст-
рукция  LEA  на  байт длиннее и выполняется несколько дольше, чем
инструкция MOV OFFSET.

     Кстати, при использовании  групп  сегментов  проблемы  могут
возникать только со смещениями, а не с доступом к памяти. В таких
строках, как:

                mov   ax,[Var1]

не требуется использовать префикс определения группы.




                 Глава 7. Интерфейс Турбо Ассемблера и Турбо Си
-----------------------------------------------------------------

     Хотя некоторые программисты  могут  разрабатывать  программы
целиком  на  языке Ассемблера (и делают это), другие предпочитают
писать основную часть программы на языке высокого уровня, обраща-
ясь к языку Ассемблера только для осуществления управления нижне-
го уровня или когда требуется высокая производительность. Некото-
рые  предпочитают  писать  преимущественно  на Ассемблере, только
иногда используя конструкции и библиотечные средства языков высо-
кого уровня.

     Для смешанного программирования на языке высокого  уровня  и
Ассемблере  прекрасно подходит Турбо Си. Для объединения кода Ас-
семблера и Си в нем предусмотрен не один, а целых два  механизма.
Средство встроенного Ассемблера в Турбо Си обеспечивает быстрый и
удобный способ для включения кода  Ассемблера  непосредственно  в
функцию Си. Для тех, кто предпочитает при программировании на Ас-
семблере использовать отдельные  модули,  целиком  написанные  на
этом  языке,  такие модули можно ассемблировать отдельно, а затем
компоновать с программами Турбо Си.

     Сначала мы рассмотрим использование в Турбо  Си  встроенного
Ассемблера.  Затем мы подробно обсудим компоновку отдельно ассем-
блированых модулей  Турбо Ассемблера с модулями Турбо Си и иссле-
дуем процесс вызова функций Турбо Ассемблера и кода Турбо Си. На-
конец,  мы рассмотрим вызов функций Турбо Си из Турбо Ассемблера.

           Примечание: Когда мы говорим о Турбо Си, речь  идет  о
                       Турбо Си версии 1.5 и выше.

            Использование в Турбо Си встроенного Ассемблера
-----------------------------------------------------------------

     Если вам приходилось искать идеальный  способ  использования
Ассемблера для тонкой доводки программы на языке Си,  у вас, воз-
можно,  возникало  желание  иметь возможность включать инструкции
Ассемблера в те критические места программы на языке Си, где ско-
рость Ассемблера и его возможности управления нижнего уровня при-
вели бы к существенному улучшению  производительности.  Если  это
так,  вам возможно не хотелось также связываться с обычными слож-
ностями организации интерфейса Турбо Ассемблера и Си. Более того,
вы,  вероятно, хотели бы все это сделать, не меняя ни одного бита
в остальной части программы на языке Си,  то есть чтобы уже отла-
женную часть программы на Си не пришлось бы изменять.

TASM2 #2-5/Док              = 141 =


     В Турбо Си все это можно осуществить с  помощью  встроенного
Ассемблера.  Встроенный  Ассемблер - это не что иное, как возмож-
ность помещать практически любой код Ассемблера  в  программы  на
языке Си, при сохранении полного доступа к константам, переменным
и даже функциям Си.  На самом деле встроенный Ассемблер прекрасно
подходит не только для тонкой  доводки  программы,  поскольку  по
своим  возможностям  он приближается к программированию на чистом
Ассемблере.  Например,  высокопроизводительный код в  библиотеках
Турбо Си написан с использованием встроенного Ассемблера.  Встро-
енный Ассемблер позволяет вам делать в ваших программах точно то,
что вы захотите.  При этом вам не придется беспокоиться о деталях
смешения языков.

     Рассмотрим следующий код на Си, который служит примером  ис-
пользование встроенного Ассемблера:

          .
          .
          .
          i = 0;       /* установить i в значение 0 (на Си) */
          asm dec   WORD PTR i; /* уменьшить значение i (на
                          Ассемблере */
          i++;         /* увеличить i (на Си) */
          .
          .
          .

     Первая и последняя строки выглядят достаточно обычно, а  что
представляет  собой  средняя  строка? Как вы уже наверное догада-
лись, строка, начинающаяся с asm, - это встроенный код на Ассемб-
лере.  Если вам приходилось использовать отладчик и просматривать
выполняемый код на Си,  полученный при компиляции исходного кода,
то применив его, вы можете обнаружить, что между скомпилированным
кодом операторов:

         i = 0;
и
         i++;

будут включены инструкции:

          .
          .
          .
          mov   WORD PTR [bp-02],0000

TASM2 #2-5/Док              = 142 =

          dec   WORD PTR [bp-02]
          inc   WORD PTR [bp-02]
          .
          .
          .

где можно видеть встроенную инструкцию Ассемблера DEC.

     Каждый раз, когда Турбо Си обнаруживает ключевое слово  asm,
указывающее, что это строка Ассемблера, он помещает данную строку
Ассемблера непосредственно в скомпилированный код с одним измене-
нием: ссылки на переменные Си преобразуются в соответствующий эк-
вивалент на Ассемблере (ссылка на i в предыдущем примере была за-
менена WORD PTR [BP-2]). Короче говоря, ключевое слово asm позво-
ляет вам включать в программу на Си практически любой код на  Ас-
семблере  (однако  здесь есть некоторые ограничения, о которых мы
расскажем далее в разделе "Ограничения при использовании встроен-
ного Ассемблера").

     Возможность включать код Ассемблера  непосредственно  в  код
Турбо Си выглядит на первый взгляд несколько рискованной. В самом
деле,  определенный риск при использовании встроенного Ассемблера
есть. Но Турбо Си компилирует свой код таким образом, чтобы избе-
жать многих потенциально опасных взаимодействий со встроенным Ас-
семблером.  Тем не менее,  неправильно функционирующий встроенный
код Ассемблера определенно может привести к серьезным ошибкам.

     С другой стороны, любой неправильно написанный код Ассембле-
ра (встроенный или в отдельном модуле) потенциально может повести
себя неправильно: это та цена, которую  в  Ассемблере  приходится
платить  за  скорость  и возможность управления на нижнем уровне.
Однако ошибки во встроенном коде Ассемблера гораздо менее вероят-
ны,  чем в программе, целиком написанной на Ассемблере, поскольку
Турбо Си  берет  на себя множество "мелочей",  таких,  как вход в
функции и выход из них,  передачу параметров и  выделение  памяти
для переменных.  Подводя итоги,  можно сказать,  что преимущества
использование в Си встроенного  кода  Ассемблера  перекрывают  те
неприятности, с которыми можно столкнуться из-за случайной ошибки
в коде Ассемблера.

     О программировании с использованием  встроенного  Ассемблера
можно сделать несколько важных замечаний:

     1.  Чтобы использовать встроенный Ассемблер, вы  должны  вы-
         звать TCC.EXE, командную версию Турбо Си. TC.EXE (диало-
         говая версия Турбо Си) не поддерживает встроенный Ассем-

TASM2 #2-5/Док              = 143 =

         блер.

     2.  Весьма возможно, что версия утилиты TLINK, которая  пос-
         тавляется  с вашей копией Турбо Ассемблера, не совпадает
         в версией этой утилиты, которая поставляется в комплекте
         Турбо  Си.  Для обеспечения поддержки Турбо Ассемблера в
         утилиту TLINK были  внесены  существенные  изменения  и,
         поскольку эти  изменения без сомнения будут продолжаться
         в дальнейшем, важно при компоновке модулей Турбо Си, со-
         держащих  встроенные инструкции Ассемблера, использовать
         самую последнюю версию TLINK, которая у вас имеется. Са-
         мым  надежным способом гарантировать это является хране-
         ние на  диске,  используемом  для  запуска  компоновщика
         TLINK, только одного файла TLINK.EXE. Номер версии этого
         файла должен быть самым последним  среди  всех  подобных
         файлов, которые вы получали от фирмы Borland.




TASM2 #2-5/Док              = 144 =

                   Как работает встроенный Ассемблер
-----------------------------------------------------------------

     Обычно Турбо Си компилирует каждый файл  исходного  кода  на
языке  Си  в  объектный  файл, а затем вызывает утилиту TLINK для
компоновки объектных файлов в выполняемую программу.  Такой  цикл
компиляции  и  компоновки показан на Рис. 7.1 Чтобы начать данный
цикл, нужно ввести команду:

        tcc имя_файла

которая указывает Турбо Си, что нужно сначала компилировать  файл
имя_файла.С  в файл имя_файла.OBJ, а затем вызвать TLINK для ком-
поновки файла имя_файла.OBJ в файл имя_файла.EXE.

               ----------------------------------
               |   Исходный файл на языке Си    |
               |          имя_файла.С           |
               ----------------------------------
                              |
                              V
                         ------------
                        (  Турбо Си  )      Компиляция
                         ------------
                              |
                              V
               ----------------------------------
               |     Объектный файл языка Си    |
               |          имя_файла.OBJ         |
               ----------------------------------
                              |
                              V
                         ------------
                        (   TLINK    )      Компоновка
                         ------------
                              |
                              V
               ----------------------------------
               |         Выполняемый файл       |
               |          имя_файла.EXE         |
               ----------------------------------

     Рис. 7.1 Цикл компиляции и компоновки Турбо Си.

     Однако при использовании встроенного Ассемблера Турбо Си до-
бавляет в цикл компиляции и компоновки дополнительный шаг.

TASM2 #2-5/Док              = 145 =


     При обработке компилятором Турбо Си каждого модуля,  где со-
держится встроенный код Ассемблера,  сначала весь модуль компили-
руется в исходный файл на языке Ассемблера,  а затем для трансля-
ции  полученного кода Ассемблера а объектный код вызывается Турбо
Ассемблер. После этого для компоновки объектных файлов вызывается
утилита TLINK.  Этот процесс показан на Рис.  7.2. Запустить этот
цикл можно с помощью командной строки:

        tcc имя_файла

которая указывает Турбо Си, что сначала нужно компилировать  файл
имя_файла.С  в  файл имя_файла.ASM, потом вызвать Турбо Ассемблер
для ассемблирования файла имя_файла.ASM в файл  имя_файла.OBJ,  а
затем вызвать  утилиту TLINK для компоновки файла имя_файла.OBJ в
файл имя_файла.EXE.

               ----------------------------------
               |   Исходный файл на языке Си    |
               |          имя_файла.С           |
               ----------------------------------
                              |
                              V
                         ------------
                        (  Турбо Си  )      Компиляция
                         ------------
                              |
                              V
               ----------------------------------
               |   Исходный файл на Ассемблере  |
               |          имя_файла.ASM         |
               ----------------------------------
                              |
                              V
                      ------------------
                     (  Турбо Ассемблер )      Ассемблирование
                      ------------------
                              |
                              V
               ----------------------------------
               |     Объектный файл языка Си    |
               |          имя_файла.OBJ         |
               ----------------------------------
                              |
                              V
                         ------------

TASM2 #2-5/Док              = 146 =

                        (   TLINK    )      Компоновка
                         ------------
                              |
                              V
               ----------------------------------
               |         Выполняемый файл       |
               |          имя_файла.EXE         |
               ----------------------------------

     Рис. 7.2 Цикл компиляции, ассемблирования и компоновки Турбо
Си.

     Встроенный код  Ассемблера  просто  передается  компилятором
Турбо Си в файл на языке Ассемблера.  Прелесть это схемы заключа-
ется в том, что Турбо Си не нужно ничего знать об ассемблировании
встроенного кода,  вместо этого Турбо Си компилирует исходный код
языка Си на тот же уровень, что и встроенный код (уровень Ассемб-
лера), а затем позволяет Турбо Ассемблеру выполнить трансляцию.

     Чтобы увидеть, как Турбо Си работает со встроенным Ассембле-
ром, введем под именем PLUSONE.C следующую программу:

#include  

int   main(void)
{
     int  TestValue;

     scanf('%d, &testValue);    /* получить значение
                                   для увеличения */
     asm   inc    WORD PTR TestValue; /* увеличить его
                                   (на Ассемблере) */
     printf("%d",TestValue);    /* напечатать увеличенное
                                   значение */
}

и скомпилируем ее с помощью командной строки:

        tcc -s plusone

     Параметр -s указывает Турбо  Си,  что  нужно  скомпилировать
программу  в  код на языке Ассемблера и остановиться. После этого
на вашем диске окажется файл PLUSONE.ASM:

              ifndef  ??version
?debug        macro

TASM2 #2-5/Док              = 147 =

              endm
              endif
              name   Plusone
_TEXT         SEGMENT BYTE PUBLIC 'CODE'
DGROUP        GROUP _DATA, _BSS
              ASSUME cs:_TEXT,ds:DGROUP,ss:DGROUP
_TEXT         ENDS
_DATA         SEGMENT WORD PUBLIC 'DATA'
_d@           label   BYTE
_d@w          label   WORD
_DATA         ENDS
_BSS          SEGMENT WORD PUBLIC 'BSS'
_b@           label   BYTE
_b@w          label   WORD
  ?debug  C E90156E11009706C75736F6E652E6
  ?debug  C E90009B9100F696E66C7564655C737464696F2E68
  ?debug  C E90009B9101010696E636C754655C7564655C7374646172672E68
_BSS          ENDS
_TEXT         SEGMENT BYTE PUBLIC 'CODE'
;             ?debug  L 3
_main         PROC    NEAR
              push    bp
              mov     bp,sp
              dec     sp
              dec     sp
              ?debug  L 8
              lea     ax,WORD PTR [bp-2]
              push    ax
              mov     ax,OFFSET DGROUP:_s@
              push    ax
              call    NEAR PTR _scanf
              pop     cx
              pop     cx
;             debug   L 9
              inc     WORD PTR [bp-2]
;             ?debug  L 10
              push    WORD PTR [bp-2]
              mov     ax,OFFSET GDROUP:_s@+3
              push    ax
              call    NEAR PTR _printf
              pop     cx
              pop     cx
@1:
;             debug   L 12
              mov     sp,bp
              pop     bp

TASM2 #2-5/Док              = 148 =

              ret
_main         ENDP
_TEXT         ENDS
_DATA         SEGMENT WORD PUBLIC 'DATA'
-s@           label   BYTE
              db      37
              db      100
              db      0
              db      37
              db      100
              db      0
_DATA         ENDS
_TEXT         SEGMENT BYTE PUBLIC CODE
              EXTRN   _printf:NEAR
              EXTRN   _scanf:NEAR
_TEXT         ENDS
              PUBLIC  _main
              END

     Взглянув на данный код, вы можете  в  полной  мере  оценить,
сколько усилий помогает вам сэкономить Турбо Си, обеспечивая под-
держку встроенного Ассемблера.

     В комментарии:

;            ?debug  L 8

вы можете видеть код Ассемблера для вызова scanf. Далее следует:

;            ?debug  L 9
             inc     WORD PTR [bp-2]

что представляет собой встроенную инструкцию Ассемблера для  уве-
личения значения переменной TestValue. (Заметим, что Турбо Си ав-
томатически  выполняет  преобразование  переменной Си TestValue в
соответствующую адресацию этой переменной на  Ассемблере [BP-2].)
За строкой встроенной инструкции Ассемблера следует код Ассембле-
ра для вызова функции printf.

     Обратите внимание,  что Турбо Си компилирует функцию scanf в
язык Ассемблера,  помещая встроенную инструкцию Ассемблера непос-
редственно в выходной файл на Ассемблере,  а затем транслирует на
язык  Ассемблера  функцию  printf.  Полученный  в результате файл
представляет собой файл на языке Ассемблера,  готовый к обработке
Турбо Ассемблером.


TASM2 #2-5/Док              = 149 =

     Если бы вы не использовали параметр -s, Турбо  Си  продолжил
бы работу, вызвав для ассемблирования файла PLUSONE.ASM Турбо Ас-
семблер, а затем для компоновки полученного в  результате  объек-
тного  файла  -  утилиту  TLINK  (будет  получен выполняемый файл
PLUSONE.EXE). Это обычный режим работы Турбо Си со встроенным Ас-
семблером. Параметр-s мы использовали только для демонстрационных
целей, чтобы вы могли ознакомиться с промежуточным этапом,  кото-
рый  выполняет  Турбо  Ассемблер.  Когда компилируемый код должен
компоноваться с другими программами, параметр -s практически бес-
полезен,  но его можно использовать в том случае, когда вы хотите
ознакомиться с кодом, окружающим ваш встроенный код на  Ассембле-
ре,  и с кодом, генерируемым Турбо Си в целом. Если вы не уверены
в результатах, генерируемых при  использовании  встроенного  кода
Ассемблера,  проверьте  полученный  с  помощью  параметра -s файл
с расширением .ASM.




TASM2 #2-5/Док              = 150 =

       Откуда Турбо Си знает об использовании режима Ассемблера?
-----------------------------------------------------------------

     Обычно Турбо Си компилирует исходный код  непосредственно  в
объектный  код.  Существует несколько способов, с помощью которых
можно сообщить Турбо Си, что нужно поддерживать встроенный Ассем-
блер  путем  компиляции  на язык Ассемблера и последующего вызова
утилиты TLINK.

     Параметр командной строки -s указывает Турбо Си,  что  нужно
транслировать  исходный  код в код Ассемблера, после чего прекра-
тить работу. Файл с расширением .ASM,  сгенерированный  Турбо  Си
при  использовании  параметра -s, можно отдельно ассемблировать и
скомпоновать с другими модулями Си и  Ассемблера.  Вызвать  Турбо
Ассемблер  автоматически  позволяет  параметр -b. Если вам это не
требуется для отладки или просто в целях ознакомления с кодом Ас-
семблера, параметр -s перед параметром -b указывать не нужно.

     Директива #pragma вида:

        #pragma inline

действуют аналогично параметру -b, указывая компилятору Турбо Си,
что нужно выполнить трансляцию в Ассемблер,  а затем для трансля-
ции  полученного результата вызвать Турбо Ассемблер.  Когда Турбо
Си встречает указание (директиву) #pragma inline, компиляция про-
должается  в  режиме  вывода Ассемблера.  Лучше помещать указание
#pragma inline возможно ближе к началу исходного кода  языка  Си,
так как любой исходный код языка Си, после которым следует следу-
ет данная директива,  будет компилироваться дважды:  один  раз  в
обычном режиме (Си -> объектный файл) а другой раз в режиме Си ->
Ассемблер.  Хотя это и не повредит, не стоит попусту тратить вре-
мя.

     Наконец, если Турбо Си обнаруживает встроенный код Ассембле-
ра при отсутствии параметра -b или -s и указания  #pragma inline,
то выдается предупреждающее сообщение:

     Warning test.c 6: Restarting compile using assembly in func-
     tion main
     (компиляция начинается повторно с использованием ассемблиро-
     вания в основной функции)

после чего компиляция возобновляется в режиме вывода  Ассемблера,
как если бы в данной точке  было обнаружено указание #pragma. Из-
бежать данного сообщения можно, если вы будете использовать пара-

TASM2 #2-5/Док              = 151 =

метр -b или указание  #pragma inline. В противном случае компиля-
ция будет выполняться медленнее.

      Вызов для ассемблирования встроенного кода Турбо Ассемблера
-----------------------------------------------------------------

     Для того, чтобы Турбо Си смог вызвать Турбо Ассемблер, нужно
сначала, чтобы он смог найти Турбо Ассемблер. В различных версиях
Турбо Си это происходит по-разному.

     В версии старше 1.5 предполагается, что Турбо Ассемблер  на-
ходится  в файле с именем TASM.EXE, который записан в текущем ка-
талоге или в одном из каталогов, указанный с  помощью  переменной
операционной  среды  DOS PATH. В общем случае Турбо Си сможет вы-
звать Турбо Ассемблер в том случае, если можно выполнить  команду
(введенную  в  ответ  на подсказку DOS) TASM. Поэтому, если Турбо
Ассемблер находится в текущем каталоге или в одном из  каталогов,
определенных  с помощью PATH, Турбо Си автоматически найдет и за-
пустит его для выполнения встроенного ассемблирования.

     Версии 1.0 и 1.5 Турбо Си ведут себя  несколько  по-другому.
Поскольку  данные  версии  Турбо Си были созданы до того, как был
разработан Турбо Ассемблер, для выполнения ассемблирования встро-
енного кода они вызывают макроассемблер фирмы Microsoft MASM. По-
этому данные версии будут искать в текущем каталоге и в каталоге,
заданном переменной PATH, файл с именем MASM.EXE, а не TASM.EXE.

           Примечание: О том, как скорректировать  данные  версии
      компилятора  TCC,  чтобы можно было использовать TASM, рас-
      сказывается в файле README на  дистрибутивном  диске  Турбо
      Ассемблера.




TASM2 #2-5/Док              = 152 =

         Когда Турбо Си транслирует встроенный код Ассемблера
-----------------------------------------------------------------

     Встроенный код Ассемблера  может  заканчиваться  в  сегменте
кода  или сегменте данных Турбо Си. Встроенный код Ассемблера мо-
жет находиться в функции и ассемблироваться в сегмент кода  Турбо
Си,  а  встроенный код Ассемблера, размещенный вне функции, может
ассемблироваться в сегмент данных Турбо Си.

     Например, программа:

/* Таблица квадратов значений */

asm   SquareLookUpTable   label  word;
asm   dw  0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100;

/* Функция для поиска квадрата значения между 0 и 10 */

int LookUpSquare(int Value)
{
      asm  mov   bx,Value;    /* получить значение для возведения
                                 в квадрат */
      asm  shl   bx,1;        /* умножить на 2 для поиска в
                                 таблице элементов размером в
                                 слово */
      asm  mov   ax,[SquareLookUpTable+bx]; /* поиск в таблице */
      return(_AX);
}

помещает данные для таблицы SquareLookUpTable  в  сегмент  данных
Турбо  Си,  а  встроенный  код Ассемблера в LookUpTable в сегмент
кода Турбо Си.  С равным успехом данные можно было  бы  поместить
данные в сегмент  кода.  Рассмотрим,  например,  следующую версию
программы LookUpSquare, где SquareLookUpTable находится в сегмен-
те кода Турбо Си:

/* Функция для поиска квадрата значения между 0 и 10 */

int LookUpSquare(int Value)
{
      asm  jmp   SkipAroundData /* пропустить таблицу данных */

/* Таблица квадратов значений */

asm   SquareLookUpTable   label  word;
asm   dw  0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100;

TASM2 #2-5/Док              = 153 =

a
SkipAroundData:
      asm  mov   bx,Value;    /* получить значение для возведения
                                 в квадрат */
      asm  shl   bx,1;        /* умножить на 2 для поиска в
                                 таблице элементов размером в
                                 слово */
      asm  mov   ax,[SquareLookUpTable+bx]; /* поиск в таблице */
      return(_AX);
}

     Так как SquareLookUpTable находится в таблице кода Турбо Си,
то  чтобы  из нее можно было считывать, казалось бы требуется ис-
пользовать префикс переопределения сегмента CS:.  Фактически  для
доступа  к SquareLookUpTable данный код автоматически ассемблиру-
ется с префиксом CS:. Турбо Си генерирует корректный  код  Ассем-
блера,  чтобы  Турбо  Ассемблер  знал, к каком сегменте находится
SquareLookUpTable, а Турбо Ассемблер затем генерирует необходимые
префиксы переопределения сегментов.




TASM2 #2-5/Док              = 154 =

     Параметр -1 для генерации инструкций процессоров 80186/80286
-----------------------------------------------------------------

     Если вы хотите использовать уникальные инструкции процессора
80186, такие как:

             shr   ax,3
и
             push  1

то проще всего использовать в командной строке Турбо Си  параметр
-1, например:

        tcc -1 -b heapmgr

где HEAPMGR.C - это программа, которая содержит встроенные  инст-
рукции Ассемблера, уникальные для процессора 80186.

     Основное назначение параметра  -1 состоит в том, чтобы  ука-
зать Турбо Си, что при компиляции можно использовать полный набор
инструкций процессора 80186. Параметр -1 приводит также  к  тому,
что начало выходного файла на языке Ассемблера будет включена ди-
ректива .186. Это укажет Турбо  Ассемблеру,  что  ассемблирование
нужно  выполнять  с использованием полного набора инструкций. Без
данной директивы Турбо Ассемблер пометит все встроенные  инструк-
ции,  уникальные для процессора 80186, как ошибочные. Если вы хо-
тите ассемблировать инструкции  процессора  80186,  не  принуждая
Турбо  Си  использовать полный набор инструкций процессора 80186,
включите в начало каждого модуля Турбо Си, содержащего встроенные
инструкции процессора 80186, строку:

         asm   .186

     Данная строка будет передана в файл Ассемблера, где она ука-
жет Турбо Ассемблеру, что нужно использовать инструкции процессо-
ра 80186.

     В компиляторе Турбо Си не предусмотрена встроенная поддержка
процессоров 80286,  80386,  80287 и 80387. Во встроенном коде Ас-
семблера, где используются инструкции этих процессоров, разрешить
их использование можно аналогичным способом,  с помощью ключевого
слова asm и директив Турбо Ассемблера .286,  .286С, .286Р,  .386,
.386С, .386Р, .287 и .387.

     Строка:


TASM2 #2-5/Док              = 155 =

        asm   .186

показывает важный момент во встроенном коде Ассемблера: с помощью
префикса  asm  в  файл Ассемблера можно передать любую допустимую
директиву Ассемблера, включая  директивы  определения  сегментов,
приравнивания, макрокоманды и т.д.




TASM2 #2-5/Док              = 156 =

           Формат встроенных операторов на языке Ассемблера
-----------------------------------------------------------------

     Встроенные операторы на языке Ассемблера во многом похожи на
обычные строки Ассемблера, но имеется несколько отличий. Встроен-
ный оператор на языке Ассемблера имеет следующий формат:

asm [<метка>] <инструкция/директива> <операнд><; или нов. строка>

где:

     - ключевое слово asm должно начинать каждый встроенный  опе-
ратор на Ассемблере;

     - [<метка>] является допустимой меткой Ассемблера.  Квадрат-
ные скобки показывают, что метка является необязательной, так же,
как в Ассемблере. (См. раздел  "Память  и  ограничения  адресации
операнда", где приводится информация о метках Ассемблера и Си.);

     - <инструкция/директива> представляет собой любую допустимую
директиву Ассемблера;

     - <операнды> содержит операнды, воспринимаемые в  инструкции
или  директиве.  Здесь может также присутствовать ссылка на конс-
танты, переменные и метки Си (при соблюдении ограничений, описан-
ных в разделе "Ограничения встроенного Ассемблера");

     - <; или нов. строка> это точка с запятой или  новая  строка
(и то и другое говорит о завершении оператора asm.

           Примечание: Важная информация относительно  меток  со-
      держится  в  разделе  "Память и ограничения при адресации к
      операнду".




TASM2 #2-5/Док              = 157 =


Использование во встроенном Ассемблере точки с запятой
-----------------------------------------------------------------

     В использовании встроенного Ассемблера есть один аспект, ко-
торый может упустить из вида программист, работающий на языке Си:
точка с запятой,  в отличие от других операторов языка Си, в опе-
раторах встроенного Ассемблера не является обязательной, хотя она
может использоваться для их завершения.  При ее отсутствии концом
оператора считается начало новой строки. Поэтому, если вы не раз-
мещаете на одной строке несколько операторов встроенного  Ассемб-
лера (чего делать не стоит,  так как текст будет менее понятным),
точка с запятой необязательна.  Хотя может показаться, что это не
соответствует принципам Си, но здесь соблюдаются соглашения, при-
нятые в некоторых компиляторах,  работающих в среде  операционной
системы UNIX.

                 Комментарии во встроенном Ассемблере
-----------------------------------------------------------------

     В предыдущем описании формата оператора  встроенного  Ассем-
блера  отсутствует один ключевой элемент - поле комментария. Хотя
комментарии можно помещать в конце операторов встроенного  Ассем-
блера,  они  только отмечают конец встроенного оператора, как и в
других операторах языка Си, ведь во  встроенном  коде  Ассемблера
комментарии не начинаются с точки с запятой.

     Как же тогда комментировать код встроенного Ассемблера?  Как
это  ни  странно,  делать это можно с помощью комментариев Си. На
самом деле это естественно, поскольку препроцессор языка Си обра-
батывает  встроенный код Ассемблера так же, как и остальную часть
кода на Си. Благодаря этому во всей программе на Си,  где  содер-
жится  код  Ассемблера, можно использовать один и тот же тип ком-
ментирования. И в коде на языке Си, и в коде  Ассемблера  в  этом
случае  можно  также использовать определенные в Си символические
имена. Например, в программе:

               .
               .
               .
   #define  CONSTANT 51
               int i;
               .
               .
               .
               i = CONSTANT;        /* присвоить i

TASM2 #2-5/Док              = 158 =

                                    постоянное значение */
               asm  sub  WORD PTR i,CONSTANT; /* вычесть
                                    из i постоянное
                                    значение */
               .
               .
               .

и в коде Си, и в коде Ассемблера используется определенный в язы-
ке  Си идентификатор CONSTANT, и при вычислении i получается зна-
чение 0.

     Последний  пример  показывает  одно  замечательное  свойство
встроенного Ассемблера, которое состоит в том, что в поле операн-
да могут  содержаться  не только ссылки на имена идентификаторов,
определенные в Си,  но и на переменные Си. Как вы увидите далее в
данной главе, обычно доступ к переменным Си в Ассемблере проблемы
не составляет,  поэтому удобство ссылок на такие переменные - это
основная причина, почему в большинстве прикладных задач стоит ис-
пользовать встроенный Ассемблер, а не просто объединять Ассемблер
и Си.

              Обращение к элементам структуры/объединения
-----------------------------------------------------------------

     Встроенный код Ассемблера может ссылаться непосредственно на
элементы структуры. Например, в следующем фрагменте программы:

             .
             .
             .
    struct  Student {
         char Teacher[30];
         int  Grade;
    }    JohnQPublic;
             .
             .
             .
             asm   mov  ax,JohnQPublic.Grade;
             .
             .
             .

в регистр AX  загружается  содержимое  элемента  Grade  структуры
JohnQPublic типа Student.

TASM2 #2-5/Док              = 159 =


     Встроенный код Ассемблера может также обращаться к элементам
структуры,  адресуясь  к ним относительно базового или индексного
регистра. Например:

             .
             .
             .
             asm   mov   bx,OFFSET JohnQPublic;
             asm   mov   ax,[bx].Grade;
             .
             .
             .

     Здесь в регистр AX также загружается элемент Grade структуры
JohnQPublic.  Поскольку  Grade  находится  в структуре Student по
смещению 30, последний пример на самом деле принимает вид:

             .
             .
             .
             asm   mov   bx,OFFSET JohnQPublic;
             asm   mov   ax,[bx]+30
             .
             .
             .

     Возможность обращаться к  элементам  структуры  относительно
регистра-указателя является достаточно мощным средством, позволя-
ющим во встроенном коде Ассемблера работать с массивами и  струк-
турами и использовать указатели на структуры.

     Если же, однако, две или более структуры, к которым вы обра-
щаетесь во встроенном коде Ассемблера, содержат элемент с одним и
тем же именем, вы должны включить следующее:

            asm  mov   bx,[di].(struct tm) tm_hour > alt

     Например:

            .
            .
            .
    struct  Student {
         char Teacher[30];
         int  Grade;

TASM2 #2-5/Док              = 160 =

    }    JohnQPublic;
             .
             .
             .
    struct  Teacher {
         int  Grade;
         long Income;
    };
             .
             .
             .
       asm   mov  ax,JohnQPublic.(struct Student) Grade
             .
             .
             .





TASM2 #2-5/Док              = 161 =

              Пример использования встроенного Ассемблера
-----------------------------------------------------------------

     Теперь, когда вы увидели несколько  фрагментов  программ,  в
которых  используется встроенный Ассемблер, можно познакомиться с
реальной работающей программой со  встроенным  кодом  Ассемблера.
Это  мы  и сделаем в данном разделе. В представленной здесь прог-
рамме встроенный Ассемблер используется для  увеличения  скорости
работы  (преобразования  текста в верхний регистр). Эта программа
может служить и примером того, что можно делать с помощью  встро-
енного Ассемблера, и той схемой, на основе которой вы можете раз-
рабатывать свои собственные программы со встроенным кодом  Ассем-
блера.

     Давайте посмотрим сначала, какая задача решается  с  помощью
данной программы-примера. Нам хотелось бы написать функцию с име-
нем StringToUpper, которая копирует одну строку в другую,  преоб-
разуя  в  процессе  работы все символы нижнего регистра (строчные
буквы) в символы верхнего регистра (прописные буквы). Хотелось бы
также, чтобы эта функция работала одинаково хорошо для всех строк
и  всех  моделей памяти.  Один из хороших способов обеспечить это
заключается в передаче в функцию указателей  дальнего  типа,  так
как  указатели ближнего типа (NEAR) всегда могут быть приведены к
указателям дальнего типа (FAR), а обратное верно не всегда.

     К сожалению,  здесь возникает проблема производительности. В
то  время,  как  Турбо  Ассемблер работает с дальними указателями
достаточно хорошо,  обработка указателей дальнего типа в Турбо Си
выполняется существенно медленнее, чем обработка указателей ближ-
него типа. Это не является недостатком Турбо Си, скорее это неиз-
бежное  следствие  использования  для программирования процессора
8086 языка высокого уровня.

     С другой стороны, обработка строк и дальних указателей - это
та  область, в которой себя превосходно показывает Ассемблер. Та-
ким образом, логическое решение состоит в том, чтобы использовать
для работы с дальними указателями и копирования строки встроенный
Ассемблер, а остальную часть писать на языке Си. Это и реализова-
но в следующей программе, которая называется STRINGUP.C:

/* Программа для демонстрации использования StringToUpper().
   Для преобразования строки TestString в верхний регистр
   вызывается функция StringToUpper, после чего печатается
   полученная в результате строка UpperCaseString и ее длина. */

#pragma inline

TASM2 #2-5/Док              = 162 =

#include 

/* Прототип функции для StringToUpper() */
extern unsigned int StringToUpper(
unsigned char far  * DestFarString,
unsigned char far  * SourceFarString);

#define MAX_STRING_LENGTH 100

char *TestString = "This started Out At Lowercase!";
/* строка, преобразуемая в верхний регистр */

char UpperCaseString[MAX_STRING_LENGTH];

main()
{
   unsigned int StringLength;

   /* Скопировать строку TestString в верхнем регистре в
       UpperCaseString */
   StringLength = StringToUpper(UpperCaseString, TestString);

   /* Вывести результаты преобразования */
   printf("Исходная строка:\n%s\n\n", TestString);
   printf("Строка в верхнем регистре:\n%s\n\n", UpperCaseString);
   printf("Число символов: %d\n\n", StringLength);
}
/* Функция для выполнения быстрого преобразования в верхний
   регистр одной строки дальнего типа в другую

   :
   DestFarString  - массив для хранения преобразованной
                    в верхний регистр строки (будет
                    завершаться нулем)
   SourceFarString - строка, содержащая символы, которые
                    нужно преобразовать в верхний регистр
                    (должна завершаться нулевым символом)

   Возвращаемые результаты:
        Длина исходной строки в символах, без учета
        завершающего нулевого символа. */

unsigned int StringToUpper(unsigned char far  * DestFarSring,
                        unsigned char far  * SourceFarString)
{
   unsigned int  CharacterCount;

TASM2 #2-5/Док              = 163 =


   #define LOWER_CASE_A 'a'
   #define LOWER_CASE_Z 'z'
      asm  ADJUST_VALUE  EQU  20h;  /* число, которое нужно
                                       вычесть из значений
                                       букв в нижнем регист-
                                       ре, чтобы преобра-
                                       зовать */
      asm  cld;
      asm  push ds;       /* сохранить сегмент данных Си */
      asm  lds  si,SourceFarString; /* загрузить дальний
                             указатель на исходную строку */
      asm  les  di,DestFarString; /* загрузить указатель
                             дальнего типа на целевую
                             строку */
      CharacterCount = 0; /* число символов */
StringToUpperLoop:
      asm  lodsb;         /* получить следующий символ */
      asm  cmp  al,LOWER_CASE_A; /* если < а, то это не
                             строчная буква (нижний
                             регистр) */
      asm  jb   SaveCharacter;
      asm  cmp  al,LOWER_CASE_Z; /* если > z, то это не
                             строчная буква */
      asm  ja   SaveCharacter;
      asm  sub  al,ADJUST_VALUE; /* это нижний регистр,
                             преобразовать в верхний
                             регистр */
SaveCharacter:
      asm  stosb;         /* сохранить символ */
      CharacterCount++;   /* подсчитать этот символ */
      asm  and  al,al;    /* это завершающий символ?
                             (ноль) */
      asm  jnz  StringToUpperLoop; /* нет, обработать
                             следующий символ, если он
                             имеется */
      CharacterCount--;   /* не учитывать завершающий ноль */
      asm  pop  ds;       /* восстановить сегмент данных
                             Си */
      return(CharacterCount);
}

     Тогда при запуске STRINGUP.C на экран выводится:

     Исходная строка:
     String to convert to uppercase

TASM2 #2-5/Док              = 164 =


     Строка в верхнем регистре:
     STRING TO CONVERT TO UPPERCASE

     Число символов: 30

     Это показывает, что действительно все строчные буквы (нижний
регистр) преобразуются в прописные (верхний регистр).

     Основу    программы    STRINGUP.C     составляет     функция
StringToUpper,  которая выполняет весь процесс копирования строки
и преобразования ее в верхний регистр. Эта функция написана на Си
и  встроенном Ассемблере и воспринимает в качестве параметров два
указателя дальнего типа. Один из указателей дальнего типа  ссыла-
ется  на  строку,  содержащую текст. Другой указывает на еще одну
строку, в которую будет скопирован весь текст из  первой  строки,
строчные  символы которого будут преобразованы в верхний регистр.
Описание функции и определение параметров обрабатываются  на  Си:
действительно, прототип функции StringToUpper указывается в нача-
ле программы. Основная программа вызывает  функцию  StringToUpper
также,  как  если бы она была написана целиком на языке Си. Таким
образом, можно использовать все преимущества программирования  на
Турбо  Си, хотя функция StringToUpper содержит встроенный код Ас-
семблера.

     Тело функции StringToUpper содержит смесь кода Си  и  Ассем-
блера.  Ассемблер  используется для считывания каждого символа из
исходной строки, проверки его, и, если это необходимо, преобразо-
вания символа в верхний регистр, после чего символ записывается в
целевую строку (приемник). Встроенный Ассемблер позволяет исполь-
зовать в функции StringToUpper такие мощные строковые инструкции,
как LODSB и STOSB, которые считывают и записывают символы.

     При разработке функции StringToUpper мы знали,  что  нам  не
потребуется  обращаться  к  сегменту  данных Турбо Си, поэтому мы
просто занесли в начале функции регистр DS в стек и  использовали
его для ссылки на исходную строку (источник), не изменяя содержи-
мое DS в остальной части функции. Одно из больших преимуществ ис-
пользования встроенного Ассемблера по сравнению с чистым програм-
мированием на языке Си состоит  в  возможности  загрузки  дальних
указателей в начале функции без последующей их перезагрузки в ос-
тальной части функции. В отличие от этого Турбо Си и другие языки
высокого уровня перезагружают дальние указатели всякий раз, когда
они используются. Возможность загружать  дальний  указатель  один
раз означает, что функция StringToUpper обрабатывает строки даль-
него типа так же быстро, как и строки ближнего типа.

TASM2 #2-5/Док              = 165 =


     Другое интересное замечание по функции StringToUpper касает-
ся того,  каким  образом  чередуются операторы Ассемблера и языка
Си. Для того, чтобы установить значение LOWER_CASE_A и LOWER_CASE
_Z используется директива #define, а для задания значения ADJUST_
VALUE - директива Ассемблера EQU.  Однако в коде встроенного  Ас-
семблера  все три идентификатора используются одинаково.  Подста-
новки идентификаторов, определяемых в языке Си, выполняются преп-
роцессором  Турбо Си,  а подстановка для ADJUST_VALUE - Турбо Ас-
семблером.  При этом оба  идентификатора  можно  использовать  во
встроенном Ассемблере.

     В  теле  функции  StringToUpper  для  работы  со   счетчиком
CharacterCount используются операторы языка Си. Это сделано толь-
ко для того, чтобы показать, что код языка Си и встроенный Ассем-
блер могут чередоваться. Значение переменной CharacterCount можно
было  бы изменять и в коде встроенного Ассемблера,  используя для
этого свободный регистр (например,  регистр CX или  DX).  Функция
StringToUpper работала бы в этом случае даже быстрее.

     Свободное чередование кода языка Си и встроенного Ассемблера
довольно  рискованно, если вы не понимаете четко, какой код гене-
рирует Турбо Си между операторами встроенного Ассемблера. Исполь-
зование параметра компилятора Турбо Си -s представляет собой наи-
лучший способ исследовать, что  происходит,  когда  вы  чередуете
встроенный Ассемблер и код языка Си. Например, вы можете исследо-
вать, насколько соответствуют друг другу код Си и встроенного Ас-
семблера, если скомпилируете программу STRINGUP.C с параметром -s
и просмотрите полученный результате файл STRINGUP.ASM.

     Программа STRINGUP.C ясно демонстрирует превосходные качест-
ва  встроенного  Ассемблера. Включение в функцию StringToUp около
15 строк на языке Ассемблера почти удваивает  скорость  обработки
строки по сравнению с эквивалентным кодом на языке Си.




TASM2 #2-5/Док              = 166 =


Ограничения встроенного Ассемблера
-----------------------------------------------------------------

     При использовании встроенного Ассемблера  имеется  несколько
ограничений. Как мы уже  знаем,  операторы встроенного Ассемблера
просто передаются без изменений Турбо Ассемблеру. Однако  сущест-
вуют  отдельные  ограничения,  касающиеся  определенных операндов
в памяти и адресации, и некоторые ограничения, относящиеся к  ис-
пользованию  регистров и отсутствия во встроенном Ассемблере наз-
начения по умолчанию размера для динамических локальных  перемен-
ных Си.

              Ограничения адресации к операндам в памяти
-----------------------------------------------------------------

     Единственное изменение, которое Турбо Си вносит в  операторы
встроенного Ассемблера, состоит в преобразовании ссылок на память
и адреса памяти (например, имен переменных и адресов перехода) из
их представления в Си в соответствующий эквивалент на Ассемблере.
Такие изменения налагают два ограничения: в инструкциях  перехода
встроенного  Ассемблера  можно  ссылаться только на метки Си, а в
прочих инструкциях можно ссылаться на  что  угодно,  кроме  меток
языка Си. Например, программа:

                .
                .
                .
                asm   jz   NoDec;
                asm   dec  cx;
        NoDec:
                .
                .
                .

вполне корректна, а программа:

                .
                .
                .
                asm   jz   NoDec;
                asm   dec  cx;
                asm   NoDec:
                .
                .
                .

TASM2 #2-5/Док              = 167 =


не будет правильно компилироваться. Аналогично, в инструкциях пе-
рехода встроенного Ассемблера нельзя использовать в качестве опе-
рандов имена функций.

     Встроенные инструкции Ассемблера, отличные от переходов, мо-
гут  содержать любые операнды, кроме меток Си. Например, програм-
ма:

                .
                .
                .
                asm   BaseValue  db  '0';
                .
                .
                .
                asm   mov   al,BYTE PTR BaseValue;
                .
                .
                .

компилируется, а программа:

                .
                .
                .
        BaseValue:
                asm   db  '0';
                .
                .
                .
                asm   mov   al,BYTE PTR BaseValue;
                .
                .
                .

компилироваться не будет. Заметим, что вызов подпрограммы не счи-
тается переходом, поэтому в качестве операндов при вызове функции
Си в инструкции  встроенного  Ассемблера  можно  указывать  имена
функций  Си  и  метки  Ассемблера  (но  не метки Си). Если в коде
встроенного Ассемблера имеется ссылка на имя функции Си, то перед
именем  функции  должен  указываться  символ подчеркивания (более
подробно об этом рассказывается в разделе "Подчеркивания").




TASM2 #2-5/Док              = 168 =

    Встроенный Ассемблер и размер динамических локальных переменных
-----------------------------------------------------------------

     Когда Турбо Си заменяет в операторе  встроенного  Ассемблера
ссылку   на  динамическую  локальную  переменную  операндом  вида
[BP-02], он не помещает в измененный оператор операцию назначения
размера (типа WORD PTR или BYTE PTR). Это означает, что:

                .
                .
                .
                int   i;
                .
                .
                .
                asm   mov   ax,i;
                .
                .
                .

выводится в файл Ассемблера а виде:

                mov   ax,[bp-02]

     Проблем в данном случае не возникает, так как  использование
регистра  AX сообщает Турбо Ассемблеру, что это 16-битовая ссылка
на память.  Более того,  отсутствие операции задания размера дает
вам большую гибкость в управлении размером операнда при использо-
вании встроенного Ассемблера. Рассмотрим следующий пример:

                .
                .
                .
                int   i;
                .
                .
                .
                asm   mov   i,0;
                asm   inc   i;
                .
                .
                .

который принимает вид:

                mov   [bp-02],0

TASM2 #2-5/Док              = 169 =

                inc   [bp-02]

     Ни одна из этих инструкций здесь не содержит предполагаемого
размера,  поэтому Турбо Ассемблер не может их  ассемблировать.  В
итоге, когда вы в Турбо Ассемблере обращаетесь к динамической ло-
кальной переменной,  не используя в качестве источника или прием-
ника регистр,  то нужно указывать размер операнда.  С учетом ска-
занного последний пример должен выглядеть следующим образом:

                .
                .
                .
                int   i;
                .
                .
                .
                asm   mov   WORD PTR i,0;
                asm   inc   BYTE PTR i;
                .
                .
                .




TASM2 #2-5/Док              = 170 =


Необходимость сохранения регистров
-----------------------------------------------------------------

     В конце каждого используемого вами кода  встроенного  Ассем-
блера  регистры  BP, CS, DS и SS должны содержать те же значения,
которые они имели перед началом выполнения кода  встроенного  Ас-
семблера. Несоблюдение этого правила часто будет приводить к ава-
рийному завершению программы (crash) и перезагрузкам системы. Ре-
гистры  AX, BX, CX, DX, SI, DI, ES и флаги в коде встроенного Ас-
семблера можно свободно изменять.


Сохранение при вызовах функций регистровых переменных
-----------------------------------------------------------------

     В Турбо Си требуется, чтобы регистры DI и  SI,  которые  ис-
пользуются в виде регистровых переменных, не нарушались при вызо-
вах функций.  Однако вам не нужно беспокоиться о явном сохранении
регистров DI и SI при использовании их в коде встроенного Ассемб-
лера, поскольку он сохраняет их в начале функций и восстанавлива-
ет в конце (это еще одно из удобств использования встроенного Ас-
семблера).




TASM2 #2-5/Док              = 171 =


Подавление внутренних регистровых переменных
-----------------------------------------------------------------

     Поскольку регистровые переменные хранятся в регистрах  SI  и
DI, это, казалось бы, может приводить к возможному конфликту меж-
ду регистровыми переменными в данном модуле и встроенном коде Ас-
семблера,  в  котором DI и SI используются в том же модуле. Турбо
Си предвидит такую проблему: любое использование регистра DI  или
SI  во  встроенном  коде приведет к запрету использования данного
регистра для хранения регистровых переменных.

     В Турбо Си версии 1.0 устранение конфликта между регистровой
переменной  и встроенным кодом Ассемблера не обеспечивается. Если
вы используете версию 1.0, то нужно либо явным образом  сохранять
регистры  DI  и SI перед их использованием во встроенном коде Ас-
семблера, либо перейти к более поздней версии компилятора.


Недостатки использования встроенного Ассемблера
-----------------------------------------------------------------

     Мы посвятили уже довольно много времени  исследованию  того,
как работает встроенный Ассемблер, и изучению потенциальных преи-
муществ его  использования.  Хотя  для  многих  прикладных  задач
встроенный Ассемблер представляет собой прекрасное средство с ши-
роким спектром возможностей, он имеет также некоторые недостатки.
Давайте  рассмотрим эти недостатки, после чего вы сможете сделать
вывод, когда в ваших программах следует  использовать  встроенный
Ассемблер.




TASM2 #2-5/Док              = 172 =

        Уменьшения возможностей переносимости и обслуживаемости
-----------------------------------------------------------------

     Возможность  непосредственного  программирования  процессора
8086 - то самое качество, которое делает встроенный Ассемблер та-
ким эффективным - уменьшает  одновременно  возможности  основного
преимущества языка Си,  его переносимости.  Если  вы  используете
в программе на языке Си встроенный Ассемблер, то весьма возможно,
что вы не сможете без изменений перенести вашу программу на  дру-
гой процессор или использовать другой компилятор языка Си.

     Кроме того, Ассемблер не является структурированным языком и
не дает той ясности и понятности исходного  кода,  которую  может
обеспечить  хорошо  форматированная программа на языке Си.  В ре-
зультате встроенный код Ассемблера в общем случае гораздо труднее
читать  и обслуживать (анализировать и модифицировать программу),
чем исходный код на языке Си.

     Когда вы используете встроенный код  Ассемблера,  то  хорошо
выделять  его в отдельные модули и аккуратно и подробно комменти-
ровать. При этом программу становится  проще  обслуживать.  Кроме
того, гораздо легче будет найти встроенный код Ассемблера и пере-
писать его на языке Си,  если вы захотите перенести  программу  в
другую среду.

Более медленная компиляция
-----------------------------------------------------------------

     Компиляция модулей Си, содержащих встроенный код Ассемблера,
выполняется существенно медленнее, чем компиляция одного исходно-
го кода языка Си. Это в основном связано с  тем,  что  встроенный
код Ассемблера должен компилироваться дважды - сначала компилято-
ром Турбо Си, а затем Турбо Ассемблером. Если Турбо  Си  вынужден
повторно  начинать компиляцию, поскольку не использовались ни па-
раметры -b или -s,  ни указание (директива)  #pragma  inline,  то
время  компиляции встроенного кода Ассемблера еще более увеличит-
ся.  К счастью, медленная компиляция модулей, содержащих встроен-
ный  код  Ассемблера,  теперь  представляет собой гораздо меньшую
проблему,  чем это было раньше,  так как Турбо Ассемблер работает
гораздо быстрее, чем более ранние версии ассемблеров.

Возможность использования только компилятора ТСС
-----------------------------------------------------------------

     Как мы уже упоминали ранее, возможность использования встро-

TASM2 #2-5/Док              = 173 =

енного  Ассемблера  -  это  уникальное  средство  для компилятора
TCC.EXE (версии компилятора Турбо Си TC.EXE, работающей с  коман-
дной  строкой).  Компилятор Турбо Си с интерактивной средой прог-
раммирования TC.EXE встроенный Ассемблер не поддерживает.

                         Потери в оптимизации
-----------------------------------------------------------------

     При использовании встроенного Ассемблера происходит  некото-
рая  потеря управления Турбо Си кодом программы, поскольку непос-
редственно в исходный код программы на языке Си вы включаете  код
Ассемблера. В определенной  степени  вы, как программист, работа-
ющий на Ассемблере, можете это компенсировать, если будете  избе-
гать  определенных нежелательных действий (например, несохранения
регистра DS, или записи в неверную область памяти).

     С другой стороны, Турбо Си не требует, чтобы при использова-
нии встроенного Ассемблера вы соблюдали все его внутренние прави-
ла  (если бы это было так,  то  лучше  было  бы  не  пользоваться
встроенным Ассемблером,  а позволить сгенерировать код Ассемблера
компилятору Турбо Си). В функциях, содержащих встроенные операто-
ры Ассемблера,  Турбо Си "выключает" некоторые виды  оптимизации,
позволяя вам тем самым относительно свободно работать со встроен-
ным кодом Ассемблера. Например, при использовании встроенного Ас-
семблера  "выключаются" некоторые виды оптимизации встроенных пе-
реходов, а если во встроенном коде используются регистры DI и SI,
то запрещаются регистровые переменные. Однако такая частичная по-
теря оптимизации в значительной степени компенсируется  тем,  что
части программы,  реализованные на Ассемблере,  работают с макси-
мально возможной скоростью.

     Если при использовании встроенного Ассемблера для вас  имеет
большое  значение получение наиболее быстрого и компактного кода,
то для вас может оказаться желательным писать функции, содержащие
встроенный  код  Ассемблера, целиком на языке Ассемблера (то есть
не смешивая в одной функции встроенный Ассемблер  с  языком  Си).
Таким образом, вы получите полный контроль над кодом функции, на-
писанной целиком на встроенном Ассемблере, А Турбо Си  будет  уп-
равлять  кодом функций, реализованных на языке Си. При этом и вы,
и Турбо Си получаете полную свободу и возможность  генерации на-
илучшего кода без ограничений.




TASM2 #2-5/Док              = 174 =

                  Ограничения при обнаружении ошибок
-----------------------------------------------------------------

     Поскольку Турбо Си выполняет в операторах встроенного Ассем-
блера слабую проверку на ошибки, ошибки во встроенном коде Ассем-
блера часто распознаются Турбо Ассемблером, а не Турбо Си. С  со-
жалению, иногда оказывается довольно трудно установить связь меж-
ду выдаваемым Турбо Ассемблером сообщением об ошибке  и  исходным
кодом на языке Си, поскольку эти сообщения об ошибках и выводимые
номера строк основываются на выводимом Турбо Си файле с  расшире-
нием .ASM, а не на самом исходном коде языка Си.

     Например, в ходе компиляции программы TEST.C  (программы  на
языке  Си,  содержащей встроенный код Ассемблера) Турбо Ассемблер
может вывести сообщение о некорректном размере операнда на строке
23.  К сожалению,  номер 23 относится к номеру ошибочной строки в
файле TEST.ASM (промежуточном файле,  который компилятор Турбо Си
генерирует  для обработки его Турбо Ассемблером).  Вам самим при-
дется выяснить,  какая именно строка в программе  TEST.C  вызвала
ошибку.

     Для этого лучше всего сначала найти ошибочную строку в  про-
межуточном файле TEST.ASM, который сохраняется на диске компилят-
ором Турбо Си в том случае, если Турбо Ассемблер выдает сообщения
об ошибках. Файл .ASM содержит специальные комментарии, идентифи-
цирующие строку в файле Си, из которой генерируется  данный  блок
операторов Ассемблера. Например, строки на Ассемблере, за которы-
ми следует комментарий:

            ; Line 15

(строка 15), генерируются из строки 15 исходного файла Си.  После
того, как вы найдете в файле .ASM строку, которая вызвала ошибку,
для определения соответствующей строки в исходном файле Си  можно
использовать указанный в ней (в комментарии) номер строки.


Ограничения при отладке
-----------------------------------------------------------------

     Версии компилятора Турбо Си, номер которых не превышает 1.5,
не  могут  генерировать информацию для отладки на уровне исходных
кодов (эта информация необходима для того, чтобы при  отладке  вы
могли  просматривать  исходный  код на языке Си) для тех модулей,
которые содержат встроенный  код  Ассемблера.  При  использовании
встроенного  Ассемблера  компиляторы  Турбо Си версии 1.5 и более

TASM2 #2-5/Док              = 175 =

ранние генерируют просто код Ассемблера без включения  информации
для  отладки. Возможность отладки на уровне исходного кода оказы-
вается утраченной,  и возможна только отладка (модулей Си, содер-
жащих встроенный код Ассемблера) на уровне Ассемблера.

     Более поздние версии Турбо Си используют преимущества специ-
альных средств Турбо Ассемблера, обеспечивающих при использовании
Турбо отладчика отладку на уровне исходного кода как для модулей,
содержащих встроенных код Ассемблера, так и для модулей, написан-
ных целиком на языке Си.




TASM2 #2-5/Док              = 176 =

        Разработка на Си и последующее использование Ассемблера
-----------------------------------------------------------------

     В свете описанных нами недостатков и ограничений использова-
ния  встроенного  Ассемблера может показаться, что встроенный Ас-
семблер лучше использовать только в случае крайней необходимости.
Это  не так. При разработке программы встроенный Ассемблер до са-
мого последнего этапа может оказать существенную помощь.

     Большая часть недостатков встроенного Ассемблера сводится  к
одной  проблеме:  использование  встроенного Ассемблера может су-
щественно замедлить цикл редактирования-компиляции-отладки. Более
медленная  компиляция, невозможность использования встроенной ин-
терактивной среды и трудности в нахождении ошибок компиляции  оз-
начают, что разработка программы, содержащей встроенные операторы
Ассемблера, потребует, вероятно, больше времени,  чем  разработка
программы,  написанной целиком на языке Си. Однако правильное ис-
пользование встроенного Ассемблера может значительно улучшить ка-
чество программы. Что же делать?

     Ответ прост. На первом этапе нужно писать программу  целиком
на языке Си, полностью используя преимущества интерактивной прог-
раммной среды компилятора TC.EXE. Когда программа будет полностью
готова, отлажена и будет выдавать правильные результаты, перейди-
те к использованию командного компилятора TCC.EXE и начните  пре-
образовывать критические куски программы в код встроенного Ассем-
блера. Такой подход позволит вам эффективно разработать  и  отла-
дить  программу, а затем выделить и улучшить отдельные части кода
и перейти к тонкой ее доводке.




TASM2 #2-5/Док              = 177 =

              Вызов функций Турбо Ассемблера из Турбо Си
-----------------------------------------------------------------

     Ассемблер и Си традиционно используются совместно: отдельные
модули пишутся  целиком  на языке Си или Ассемблере,  выполняется
компиляция модулей Си и ассемблирование модулей Ассемблера, а за-
тем  компоновка этих отдельно скомпилированных модулей в один вы-
полняемый файл. Именно так можно компоновать модули Турбо Ассемб-
лера и Турбо Си (этот процесс показан на Рис. 7.3).

     Выполняемый файл получается путем "смешивания" исходных фай-
лов Си и Ассемблера. Этот процесс можно начать командой:

        tcc имя_файла1 имя_файла.asm

которая указывает Турбо Си,  что нужно сначала компилировать файл
имя_файла_1.C для получения объектного файла имя_файла_1.OBJ, за-
тем  вызвать  Турбо  Ассемблер  для  трансляции  исходного  файла
имя_файла_2.ASM в файл имя_файла_2.OBJ,  и, наконец, вызвать ути-
литу TLINK  для  компоновки  файлов  имя_файла_1.OBJ  и  имя_фай-
ла_2.OBJ и получения выполняемого файла имя_файла_1.XE.

 --------------------                --------------------
 | Исходный файл на |                | Исходный файл на |
 | языке Си FILE1.C |                | языке Ассемблера |
 |                  |                |     FILE2.ASM    |
 --------------------                --------------------
          |                                    |
          v                                    v
     ----------                        -----------------
    ( Турбо Си )                      ( Турбо Ассемблер )
     ----------                        -----------------
          |     Компиляция                     | Ассемблирование
          v                                    v
 --------------------                 -------------------
 |  Объектный файл  |                 |  Объектный файл |
 |    FILE1.OBJ     |                 |     FILE2.OBJ   |
 --------------------                 -------------------
          |                                    |
          -----------------  -------------------
                          |  |
                          v  v
                       ----------
                      (   TLINK  )
                       ----------
                            |

TASM2 #2-5/Док              = 178 =

                            v
               ------------------------------
               | Выполняемый файл FILE1.EXE |
               ------------------------------

     Рис. 7.3 Компиляция, ассемблирование и компоновка с  помощью
Турбо Си, Турбо Ассемблера и утилиты TLINK.

     Раздельная компиляция оказывается очень полезной  для  прог-
рамм, содержащих значительной объем  кода  Ассемблера,  поскольку
она позволяет полностью использовать возможности Турбо Ассемблера
и программировать на языке Ассемблера в чисто ассемблерном  окру-
жении, без ключевого слова asm, дополнительного времени на компи-
ляцию и относящихся к Си дополнительных издержек при  использова-
нии встроенного Ассемблера.

     Однако за раздельную компиляцию приходится платить следующую
цену: программист, работающий на Ассемблере,  должен  вникать  во
все детали организации интерфейса языка Си и кода Ассемблера.В то
время как Турбо Си обрабатывает спецификацию  сегментов, передачу
параметров, ссылки на переменные Си, сохранение регистровых пере-
менных и т.д., раздельно компилируемые функции, написанные на Ас-
семблере, должны все это (и даже более) делать явным образом.

     Во взаимодействии Турбо Си и Ассемблера имеется два основных
аспекта.  Во-первых,  различные части кода Си и Ассемблера должны
правильно компоноваться, а функции и переменные  в  каждой  части
кода должны быть доступны (если это необходимо) в остальной части
кода. Во-вторых, код Ассемблера должен правильно работать с вызо-
вами функций,  соответствующих соглашениям языка Си, что включает
в себя доступ к передаваемым параметрам,  возврат значений и соб-
людение правил сохранения регистров,  которых требуется придержи-
ваться в функциях Си.

     Давайте теперь приступим к изучению правил компоновки  прог-
рамм Турбо Ассемблера и Турбо Си.




TASM2 #2-5/Док              = 179 =


Основные моменты в интерфейсе Турбо Ассемблера и Турбо Си
-----------------------------------------------------------------

     Чтобы скомпоновать вместе модули Турбо Си и Турбо  Ассембле-
ра, должны быть соблюдены следующие три пункта:

     1.  В модулях Турбо Ассемблера должна  использоваться  схема
         наименования сегментов, совместимая с Турбо Си.

     2.  Турбо Си и Турбо Ассемблер должны совместно использовать
         соответствующие функции и имена переменных в форме, при-
         емлемой для Турбо Си.

     3.  Для комбинирования модулей в выполняемую программу нужно
         использовать утилиту-компоновщик TLINK.

     Здесь ничего не говориться о том, что в действительности де-
лают  модули  Турбо  Ассемблера. Пока мы коснемся только основных
моментов, обеспечивающих  разработку  функций  Турбо  Ассемблера,
совместимых с Си.




TASM2 #2-5/Док              = 180 =


Модели памяти и сегменты
-----------------------------------------------------------------

     Чтобы данная функция Ассемблера могла  могла  вызываться  из
Си,  она должна использовать ту же модель памяти, что и программа
на языке Си, а также совместимый с Си сегмент  кода.  Аналогично,
чтобы  данные,  определенные в модуле Ассемблера, были доступны в
программе на языке Си (или данные Си были  доступны  в  программе
Ассемблера), в  программе на Ассемблере должны соблюдаться согла-
шения языка Си по наименованию сегмента данных.

     Модели памяти и обработку сегментов на Ассемблере может ока-
заться  реализовать  довольно  сложно. К счастью, Турбо Ассемблер
сам выполняет почти всю работу по  реализации  моделей  памяти  и
сегментов,  совместимых  с Турбо Си, при использовании упрощенных
директив определения сегментов (см. раздел "Стандартные директивы
определения сегментов" в Главе 5, где дается введение в  упрощен-
ные директивы определения сегментов).

         Упрощенные директивы определения сегментов и Турбо Си
-----------------------------------------------------------------

     Директива DOSSEG указывает Турбо Ассемблеру, что нужно  упо-
рядочивать сегменты в соответствии с соглашениями по упорядочива-
нию сегментов фирмы Intel. Те же соглашения соблюдаются  в  Турбо
Си  (и  во многих других известных продуктах, включая языки фирмы
Microsoft).

     Директива .MODEL указывает Турбо Ассемблеру,  что  сегменты,
создаваемые  с помощью упрощенных директив определения сегментов,
должны быть совместимы с выбранной  моделью  памяти  (сверхмалой,
малой, компактной, средней, большой или сверхбольшой) и управляет
назначаемым по умолчанию типом (FAR или NEAR) процедур, создавае-
мых  по директиве PROC. Модели памяти, определенные с помощью ди-
рективы .MODEL, совместимы с моделями Турбо Си с соответствующими
именами.

     Наконец, упрощенные директивы определения  сегментов  .DATA,
.CODE,  .DATA?, .FARDATA, .FARDATA? и .CONST генерируют сегменты,
совместимые с Турбо Си.

     Например, рассмотрим следующий  модуль  Турбо  Ассемблера  с
именем DOTOTAL.ASM:

        DOSSEG               ; выбрать упорядочивание сегментов,

TASM2 #2-5/Док              = 181 =

                             ; принятое фирмой Intel
        .MODEL   SMALL       ; выбрать малую модель памяти
                             ; (ближний код и данные)
        .DATA                ; инициализация сегмента данных,
                             ; совместимого с Турбо Си
        EXTRN   _Repetitions:WORD ; внешний идентификатор
        PUBLIC  _StartingValue ; доступен для других модулей
_StartValue     DW   0
        .DATA?               ; инициализированный сегмент
                             ; данных, совместимый с Турбо Си
RunningTotal    DW   ?
        .CODE                ; сегмент кода, совместимый с
                             ; Турбо Си
        PUBLIC  _DoTotal
_DoTotal        PROC         ; функция (в малой модели памяти
                             ; вызывается с помощью вызова
                             ; ближнего типа)
        mov     cx,[_Repetitions] ; счетчик выполнения
        mov     ax,[_StartValue]
        mov     [RunningTotal],ax ; задать начальное
                             ; значение
TotalLoop:
        inc     [RunningTotal] ; RunningTotal++
        loop    TotalLoop
        mov     ax,[RunningTotal] ; возвратить конечное
                             ; значение (результат)
        ret
_DoTotal        ENDP
        END

     (Перед многими метками в процедуре DoTotal указывается  сим-
вол  подчеркивания  (_), так как это обычно требуется в Турбо Си.
Более подробно это описывается далее в разделе "Подчеркивания".)

     Написанная на Ассемблере процедура _DoTotal при  использова-
нии  малой  модели  памяти может вызываться из Турбо Си с помощью
оператора:

        DoTotal();

     Заметим, что в процедуре DoTotal предполагается, что  где-то
в   другой   части   программы   определена   внешняя  переменная
Repetitions. Аналогично,  переменная StartingValue объявлена, как
общедоступная,  поэтому  она  доступна в других частях программы.
Следующий модуль Турбо Си (который называется SHOWTOT.C)  обраща-
ется к данным в DOTOTAL.ASM и обеспечивает для модуля DOTOTAL.ASM

TASM2 #2-5/Док              = 182 =

внешние данные:

  extern  int StartingValue;
  extern  int DoTotal(word);
  int  Repetitions;
  main()
  {
     int i;
     Repetitions = 10;
     StartingValue = 2;
     print("%d\n", DoTotal());
}

     Чтобы создать из модулей DOTOTAL.ASM и SHOWTOT.C выполняемую
программу SHOWTOT.EXE, введите команду:

        tcc showtot dototal.asm

     Если бы вы захотели скомпоновать процедуру _DoTotal с  прог-
раммой на  языке  Си,  использующей компактную модель памяти,  то
пришлось бы просто заменить директиву .MODEL на .MODEL COMPACT, а
если  бы  вам  потребовалось  использовать  в DOTATOL.ASM сегмент
дальнего типа,  то можно было бы использовать директиву .FARDATA.

     Короче говоря, при использовании упрощенных директив опреде-
ления  сегментов генерация корректного  упорядочивания сегментов,
моделей памяти и имен сегментов труда не составляет.




TASM2 #2-5/Док              = 183 =

           Старые директивы определения сегментов и Турбо Си
-----------------------------------------------------------------

     Коснемся теперь проблемы организации интерфейса Турбо Ассем-
блера с кодом языка Си,  где используются  директивы  определения
сегментов старого типа (стандартные директивы определения сегмен-
тов). Например,  если вы замените в модуле DOTOTAL.ASM упрощенные
директивы определения сегментов директивами старого типа,  то по-
лучите:

DGROUP  group    _DATA,_BSS
_DATA   segment  word public 'DATA'
        EXTRN   _Repetitions:WORD ; внешний идентификатор
        PUBLIC  _StartingValue ; доступен для других модулей
_StartValue     DW   0
_DATA   ends
_BSS    segment word public 'BSS'
RunningTotal    DW   ?
_BSS    ends
_TEXT   segment byte public 'CODE'
        assume  cs:_TEXT.ds:DGROUP,ss:DGROUP
        PUBLIC  _DoTotal
_DoTotal        PROC         ; функция (в малой модели памяти
                             ; вызывается с помощью вызова
                             ; ближнего типа)
        mov     cx,[_Repetitions] ; счетчик выполнения
        mov     ax,[_StartValue]
        mov     [RunningTotal],ax ; задать начальное
                             ; значение
TotalLoop:
        inc     [RunningTotal] ; RunningTotal++
        loop    TotalLoop
        mov     ax,[RunningTotal] ; возвратить конечное
                             ; значение (результат)
        ret
_DoTotal        ENDP
_TEXT   ENDS
        END

     Данная версия директив определения сегментов старого типа не
только длиннее, то также и хуже читается. К тому же при использо-
вании в программе на языке Си различных моделей памяти ее труднее
изменять.  При организации интерфейса с Турбо Си в  общем  случае
в использовании старых директив определения сегментов нет никаких
преимуществ.  Если же вы тем не менее захотите  использовать  при
организации  интерфейса  с  Турбо Си старые директивы определения

TASM2 #2-5/Док              = 184 =

сегментов, вам придется идентифицировать корректные сегменты, со-
ответствующие используемым в коде на языке Си моделям памяти. Об-
зор использования сегментов в Турбо Си содержится  в "Руководстве
пользователя по Турбо Си".

     Простейший способ определения необходимых для  компоновки  с
данной  программой  на  Турбо  Си директив старого типа состоит в
том, чтобы скомпилировать основной модуль программы на Турбо Си с
нужной моделью памяти и параметром -S. При этом Турбо Си сгенери-
рует версию исходного кода Си на языке Ассемблера. В этом коде вы
найдете  старые директивы, используемые Турбо Си. Остается просто
скопировать их в ваш код на Ассемблере. Например, если вы введете
команду:

        tcc -s showtot.c

то генерируется файл SHOWTOT.ASM, содержащий:

        ifndef   ??version
?debug  macro
        endm
        endif
        name     showtot
_TEXT   segment byte public 'CODE'
DGROUP  group   _DATA,_BSS
        assume   cs:_TEXT.ds:DGROUP,ss:DGROUP
_TEXT   ends
_DATA   segment word public 'DATA'
_d@     label   byte
_d@w    label   word
_DATA   ends
_BSS    segment word public 'BSS'
_b@     label   byte
_b@w    label   word
        ?debug  C E91481D5100973688F77746F742E63
_BSS    ends
_TEXT   segment byte public 'CODE'
;       ?debug  L 3
_main   proc near
;       ?debug  L 6
        mov     word ptr DGROUP:_Repetitions,10
;       ?debug  L 7
        mov     word ptr DGROUP:_StartingValue,2
;       ?debug  L 8
        call    near ptr _DoTotal
        push    ax

TASM2 #2-5/Док              = 185 =

        mov     ax,offset DGROUP:_s@
        push    ax
        call    near ptr _printf
        pop     cx
        pop     cx
@1:
;       debug   L 9
        ret
_main   endp
_TEXT   ends
_BSS    segment word public 'BSS'
_Repetitions    label word
        db      2 dup (?)
        ?debug  C E9
_BSS    ends
_DATA   segment word public 'DATA'
_s@     label   byte
        db      37
        db      100
        db      10
        db      0
_DATA   ends
        extrn   _StartingValue:word
_TEXT   segment byte public 'CODE'
        extrn   _DoTotal:near
        extrn   _printf:near
_TEXT   ends
        public  _Repetitions
        public  _main
        end

     Директивы  определения  сегментов _DATA  (инициализированный
сегмент  данных), _TEXT (сегмент кода) и _BSS (неинициализирован-
ный сегмент данных), а также директивы GROUP и ASSUME имеют гото-
вый  для  ассемблирования  вид, поэтому вы можете их использовать
(так, как они указываются).




TASM2 #2-5/Док              = 186 =

      Значения по умолчанию: когда необходимо загружать сегменты?
-----------------------------------------------------------------

     В некоторых случаях вызываемые из языка Си функции Ассембле-
ра могут использовать (загружать) для обращения к данным регистры
DS и/или ES. Полезно знать соотношение между  значениями  сегмен-
тных  регистров  при вызове из Турбо Си, так как иногда Ассемблер
использует преимущества эквивалентности двух  сегментных  регист-
ров.  Давайте  рассмотрим значения сегментных регистров в тот мо-
мент, когда функция Ассемблера вызывается из Турбо  Си,  а  также
соотношения между сегментными регистрами, и случаи, когда в функ-
ции Ассемблера требуется загружать один или более сегментных  ре-
гистров.

     При входе в функцию Ассемблера из Турбо Си регистры CS и  DS
имеют  следующие значения, которые зависят от используемой модели
памяти (регистр SS всегда используется для сегмента стека,  а  ES
всегда используется, как начальный сегментный регистр):


   Значения регистров при входе в Ассемблер из Турбо Си
                                                 Таблица 7.1
 -----------------------------------------------------------
  Модель            CS                          DS
 -----------------------------------------------------------
 Сверхмалая         _TEXT                    DGROUP
 Малая              _TEXT                    DGROUP
 Компактная         _TEXT                    DGROUP
 Средняя         имя_файла_TEXT              DGROUP
 Большая         имя_файла_TEXT              DGROUP
 Сверхбольшая    имя_файла_TEXT   имя_вызывающего_файла_DATA
 -----------------------------------------------------------

     Здесь  "имя_файла"  -  это  имя  модуля  на  Ассемблере,   а
"имя_вызывающего_файла" -  это  имя модуля Турбо Си,  вызывающего
модуль на Ассемблере.

     В компактной модели памяти _TEXT и DGROUP совпадают, поэтому
при входе  в функцию содержимое регистра CS равно содержимому DS.
При использовании сверхмалой,  малой и компактной  модели  памяти
при входе в функцию содержимое SS равно содержимому регистра DS.

     Когда же в функции на Ассемблере, вызываемой из программы на
языке Си,  необходимо загружать сегментный регистр?  Отметим  для
начала, что вам никогда не придется (более того, этого не следует
делать) загружать регистры SS или CS:  при дальних вызовах, пере-

TASM2 #2-5/Док              = 187 =

ходах  или  возвратах  регистр CS автоматически устанавливается в
нужное значение, а регистр SS всегда указывает на сегмент стека и
в  ходе выполнения программы изменять его не следует (если только
вы не пишете программу,  которая "переключает" стеки; в этом слу-
чае вам нужно четко понимать, что вы делаете).

     Регистр ES вы можете всегда использовать так, как это требу-
ется.  Вы  можете установить его таким образом, чтобы он указывал
на данные с дальним типом обращения,  или  загрузить  в  ES  сег-
мент-приемник для строковой функции.

     С регистром DS дело обстоит иначе. Во  всех  моделях  памяти
Турбо Си, кроме сверхбольшой, регистр DS при входе в функцию ука-
зывает на статический сегмент данных (DGROUP),  и изменять его не
следует.  Для  доступа  к данным с дальним типом обращения всегда
можно использовать регистр ES,  хотя вы можете посчитать, что для
этого временно нужно использовать регистр DS (если вы собираетесь
осуществлять интенсивный доступ к данным), что исключит необходи-
мость использования в вашей программе множества инструкций с пре-
фиксом переопределения сегмента. Например, вы можете обратиться к
дальнему сегменту одним из следующих способов:

                  .
                  .
                  .
                  .FARDATA
Counter           DW    0
                  .
                  .
                  .
                  .CODE
                  PUBLIC  _AsmFunction
_AsmFunction      PROC
                  .
                  .
                  .
                  mov   ax,@FarData
                  mov   es,ax         ; ES указывает на
                                      ; сегмент данных с
                                      ; дальним типом
                                      ; обращения
                  inc   es:[Counter]  ; увеличить значение
                                      ; счетчика
                  .
                  .
                  .

TASM2 #2-5/Док              = 188 =

_AsmFunction      ENDP
                  .
                  .
                  .

или

                  .
                  .
                  .
                  .FARDATA
Counter           DW     0
                  .
                  .
                  .
                  .CODE
                  PUBLIC  _AsmFunction
_AsmFunction      PROC
                  .
                  .
                  .
                  assume  ds:@FarData
                  mov   ax,@FarDAta
                  mov   ds,ax          ; DS указывает на
                                       ; сегмент данных с
                                       ; дальним типом
                                       ; обращения
                  inc   [Counter]      ; увеличить значение
                                       ; счетчика
                  assume ds:@Data
                  mov   ax,@Data
                  mov   dx,ax          ; DS снова указывает
                                       ; на DGROUP
                  .
                  .
                  .
_AsmFunction      ENDP
                  .
                  .
                  .

     Второй вариант имеет то преимущество, что при каждом обраще-
нии к дальнему сегменту данных в нем не требуется переопределение
ES:. Если для обращения к дальнему сегменту вы загружаете регистр
DS,  убедитесь  в  том,  что перед обращением к другим переменным
DGROUP вы его восстанавливаете (как это  делается  в  приведенном

TASM2 #2-5/Док              = 189 =

примере). Даже если в данной функции на Ассемблере вы не обращае-
тесь к DGROUP, перед выходом из нее все равно  обязательно  нужно
восстановить  содержимое  DS, так как в Турбо Си подразумевается,
что регистр DS не изменялся.

     При использовании в функциях, вызываемых из Си, сверхбольшой
модели памяти работать с регистром DS нужно несколько по-другому.
В сверхбольшой  модели  памяти  Турбо  Си  совсем  не  использует
DGROUP.  Вместо этого каждый модуль имеет свой   собственный сег-
мент данных, который является дальним сегментом относительно всех
других  модулей в программе (нет совместно используемого ближнего
сегмента данных). При использовании сверхбольшой модели памяти на
входе  в функцию регистр DS должен быть установлен таким образом,
чтобы он указывал на этот дальний сегмент данных модуля и не  из-
менялся до конца функции, например:

                  .
                  .
                  .
                  .FARDATA
                  .
                  .
                  .
                  .CODE
                  PUBLIC  _AsmFunction
_AsmFunction      PROC
                  push    ds
                  mov     ax,@FarData
                  mov     ds,ax
                  .
                  .
                  .
                  pop     ds
                  ret
_AsmFunction      ENDP
                  .
                  .
                  .

     Заметим, что исходное состояние регистра DS сохраняется  при
входе  в  функцию  _AsmFunction с помощью инструкции PUSH и перед
выходом восстанавливается с помощью инструкции POP. Даже в сверх-
большой  модели памяти Турбо Си требует, чтобы все функции сохра-
няли регистр DS.



TASM2 #2-5/Док              = 190 =


Общедоступные и внешние идентификаторы
-----------------------------------------------------------------

     Программы Турбо Ассемблера могут вызывать функции Си и  ссы-
латься  на  внешние переменные Си. Программы Турбо Си аналогичным
образом могут вызывать общедоступные (PUBLIC) функции  Турбо  Ас-
семблера  и обращаться к переменным Турбо Ассемблера. После того,
как в Турбо Ассемблере устанавливаются  совместимые  с  Турбо  Си
сегменты (как описано в предыдущих разделах), чтобы совместно ис-
пользовать функции и переменные Турбо Си и Турбо Ассемблера, нуж-
но соблюдать несколько простых правил.

                             Подчеркивания
-----------------------------------------------------------------

     Обычно в Турбо Си предполагается, что все внешние метки  на-
чинаются  с  символа  подчеркивания  (_).  Турбо Си автоматически
включает символы подчеркивания во все имена функций и имена внеш-
них переменных, когда они используются в коде языка Си, поэтому в
коде  Ассемблера  вы должны просто добавить подчеркивания.  Нужно
убедиться,  что все ссылки в Ассемблере на функции  и  переменные
Турбо Си начинаются с подчеркивания. Все функции и переменные Ас-
семблера,  которые являются общедоступными и к которым обращается
Турбо Си, должны также начинаться с символа подчеркивания.

     Например, следующая программа на языке Си:

  extrn int  ToggleFlag();
  int   Flag;
  main()
  {
      ToggleFlag();
  }

правильно компонуется со следующей программой на Ассемблере:

            DOSSEG
            .MODEL  SMALL
            .DATA
            EXTRN   _Flag:word
            .CODE
            PUBLIC  _ToggleFlag
_ToggleFlag PROC
            cmp     [_Flag],0      ; флаг сброшен?
            jz      SetFlag        ; да, установить его

TASM2 #2-5/Док              = 191 =

            mov     [_Flag],0      ; нет, сбросить его
            jmp     short EndToggleFlag ; выполнено
SetFlag:
            mov     [_Flag],1      ; установить флаг
EndToggleFlag:
            ret
_ToggleFlag ENDP
            END

     Заметим, что в метках,  на которые нет ссылок в программе на
языке Си (например,  SetFlag), не требуется указывать символ под-
черкивания.

     Кстати, с помощью параметра командной строки -u  можно  ука-
зать  Турбо  Си,  чтобы  он не использовал символы подчеркивания.
Хотя это может показаться достаточно привлекательным способом, но
все библиотеки исполняющей системы Турбо Си скомпилированы с раз-
решением символа подчеркивания. Таким  образом,  вам  потребуется
получить  у фирмы Borland исходный код библиотечных модулей и пе-
рекомпилировать их с параметром -u (см. далее "Соглашения по  вы-
зовам Паскаля", где рассказывается о параметре -p, который запре-
щает использование символа подчеркивания и различимость  строчных
и прописных символов).




TASM2 #2-5/Док              = 192 =


Строчные и прописные символы
-----------------------------------------------------------------

     В именах идентификаторов Турбо Ассемблер обычно не различает
строчные и прописные буквы (верхний и нижний регистр).  Поскольку
в Си они различаются, желательно задать такое различие  и в Турбо
Ассемблере (по крайней мере для тех идентификаторов, которые сов-
местно используются Ассемблером и Си). Это можно  сделать  с  по-
мощью параметров /ML и /MX.

     Переключатель (параметр) командной  строки  /ML  приводит  к
тому,  что  в Турбо Ассемблере во всех идентификаторах строчные и
прописные символы будут различаться (считаться различными). Пара-
метр  командной строки /MX указывает Турбо Ассемблеру, что строч-
ные и прописные символы нужно различать в общедоступных  (PUBLIC)
идентификаторах,   внешних   (EXTRN)  идентификаторах  глобальных
(GLOBAL) идентификаторах и общих (COMM) идентификаторах.

                              Типы меток
-----------------------------------------------------------------

     Хотя в программах Турбо Ассемблера можно свободно обращаться
к любой переменной или данным любого размера (8, 16, 32 бита и т.
д.), в общем случае хорошо обращаться к переменным в соответствии
с их размером. Например, если вы записываете слово в байтовую пе-
ременную, то обычно это приводит к проблемам:

                   .
                   .
                   .
 SmallCount        DB   0
                   .
                   .
                   .
                   mov   WORD PTR [SmallCount],0ffffh
                   .
                   .
                   .

     Поэтому важно, чтобы в операторе Ассемблера EXTRN, в котором
описываются переменные Си, задавался правильный размер этих пере-
менных, так как при генерации размера  доступа  к  переменной  Си
Турбо Ассемблер основывается именно на этих описаниях.

     Если в программе на языке Си содержится оператор:

TASM2 #2-5/Док              = 193 =


        char c

то код Ассемблера:

                   .
                   .
                   .
  EXTRN            c:WORD
                   .
                   .
                   inc  [c]
                   .
                   .
                   .

может привести к весьма неприятным ошибкам, поскольку после того,
как в коде на языке Си переменная c увеличится очередные 256 раз,
ее значение будет сброшено, а так как она описана, как переменная
размером в слово,  то байт по адресу OFFSET c + 1 будет  увеличи-
ваться некорректно, что приведет к непредсказуемым результатам.

     Между типами данных Си а Ассемблера существует следующее со-
отношение:

--------------------------------------------------------------
    Тип данных Си                        Тип данных Ассемблера
--------------------------------------------------------------
    unsigned char                                   byte
    char                                            byte
    enum                                            word
    unsigned short                                  word
    short                                           word
    unsigned int                                    word
    int                                             word
    unsigned long                                   dword
    long                                            dword
    float                                           dword
    double                                          qword
    long double                                     tbyte
    near*                                           word
    far*                                            dword
---------------------------------------------------------------

Внешние дальние идентификаторы должны лежать вне любого сегмента

TASM2 #2-5/Док              = 194 =

-----------------------------------------------------------------

     Если вы используете упрощенные директивы определения сегмен-
тов,  то описания идентификаторов EXTRN в сегментах дальнего типа
не должны размещаться ни в каком сегменте, так как  Турбо  Ассем-
блер  рассматривает  идентификаторы, описанные в данном сегменте,
как связанные с данным сегментом. Это имеет свои недостатки: Тур-
бо Ассемблер не может проверить возможность адресации к идентифи-
катору, описанному, как внешний (EXTRN), вне  любого  сегмента  и
поэтому  не может в случае  необходимости сгенерировать определе-
ние сегмента или сообщить вам, что была попытка обратиться к дан-
ной переменной, когда сегмент не был загружен корректным значени-
ем. Тем  не  менее Турбо Ассемблер генерирует для ссылок на такие
внешние идентификаторы правильный код,  но  не  может  обеспечить
обычную степень проверки возможности адресации к сегменту.

     Если вы все-таки захотите, то можно использовать для  явного
описания каждого внешнего идентификатора сегмента старые директи-
вы определения сегментов, а затем поместить директиву  EXTRN  для
этого  идентификатора внутрь описания сегмента. Это довольно уто-
мительно, поэтому если вы не хотите обеспечивать загрузку коррек-
тного  значения  сегмента  при обращении к данным, то проще всего
просто разместить описания  EXTRN  для  идентификаторов  дальнего
типа   вне   всех  сегментов.  Предположим,  например,  что  файл
FILE1.ASM содержит следующее:

                      .
                      .
                      .
                      .FARDATA
 FileVariable         DB   0
                      .
                      .
                      .

и он компонуется с файлом FILE2.ASM, который содержит:

                      .
                      .
                      .
                      .DATA
                      EXTRN  FileVariable:BYTE
                      .CODE
 Start                PROC
                      mov   ax,SEG FileVariable
                      mov   ds,ax

TASM2 #2-5/Док              = 195 =

                      .
                      .
                      .

     SEG File1Variable не будет возвращать  корректного  значения
сегмента.  Директива EXTRN размещена в области действия директивы
файла FILE2.ASM DATA, поэтому Турбо Ассемблер считает, что  пере-
менная  File1Variable  должна  находиться в ближнем сегменте DATA
файла FILE2.ASM, а не в дальнем сегмента DATA.

     В следующем коде FILE2.ASM SEG File1Variable  будет  возвра-
щать корректное значение сегмента:

                      .
                      .
                      .
                      .DATA
 @CurSeg              ENDS
                      EXTRN   File1Variable:BYTE
                      .CODE
 Start                PROC
                      mov   ax,SEG File1Variable
                      mov   ds,ax
                      .
                      .
                      .

     "Фокус" здесь состоит в том, что директива @CurSeg ENDS  за-
вершает  сегмент  .DATA,  поэтому,  когда переменная FileVariable
описывается, как внешняя, никакая сегментная  директива  не  дей-
ствует.




TASM2 #2-5/Док              = 196 =

                     Командная строка компоновщика
-----------------------------------------------------------------

     Простейший способ скомпоновать модули Турбо  Си  с  модулями
Турбо Ассемблера состоит в том, чтобы ввести одну командную стро-
ку Турбо Си, после чего он выполнит всю остальную работу. При за-
дании нужной командной строки Турбо Си выполнит компиляцию исход-
ного кода Си, вызовет Турбо Ассемблер для ассемблирования, а  за-
тем  вызовет  утилиту TLINK для компоновки объектных файлов в вы-
полняемый файл. Предположим, например, что у вас есть  программа,
состоящая из файлов на языке Си MAIN.C и STAT.C и файлов  ассемб-
лера SUMM.ASM и DISPLAY.ASM. Командная строка:

        tcc main stat summ.asm display.asm

выполняет компиляцию файлов MAIN.C и STAT.C, ассемблирование фай-
лов  SUMM.ASM  и  DISPLAY.ASM и компоновку всех четырех объектных
файлов, а также кода инициализации Си и необходимых  библиотечных
функций в выполняемый файл MAIN.EXE. При вводе имен файлов Ассем-
блера нужно только помнить о расширениях .ASM.

     Если вы используете утилиту TLINK в  автономном  режиме,  то
генерируемые Турбо Ассемблером объектные файлы представляют собой
стандартные объектные модули и обрабатываются также,  как  объек-
тные модули Си.


Взаимодействие между Турбо Ассемблером и Турбо Си
-----------------------------------------------------------------

     Теперь, когда вы понимаете, как нужно строить и  компоновать
совместимые  с Си модули Ассемблера, нужно знать, какой код можно
помещать в функции Ассемблера, вызываемые из Си. Здесь нужно про-
анализировать три момента: получение передаваемых параметров, ис-
пользование регистров и возврат значений в вызывающую программу.

                          Передача параметров
-----------------------------------------------------------------

     Турбо Си передает функциям параметры через стек. Перед вызо-
вом  функции  Турбо  Си сначала заносит передаваемые этой функции
параметры, начиная с самого правого параметра и кончая  левым,  в
стек. В Си вызов функции:

         .

TASM2 #2-5/Док              = 197 =

         .
         .
         Test(i, j, 1);
         .
         .
         .

компилируется в инструкции:

         mov   ax,1
         push  ax
         push  word ptr DGROUP:_j
         push  word ptr DGROUP:_i
         call  near ptr _Test
         add   sp,6

где видно, что правый параметр (значение  1),  заносится  в  стек
первым, затем туда заносится параметр j и, наконец, i.

     При возврате из функции занесенные в стек параметры все  еще
находятся там, но они больше не используются. Поэтому непосредст-
венно после каждого вызова функции Турбо Си настраивает указатель
стека  обратно в соответствии со значением, которое он имел перед
занесением в стек параметров (параметры, таким образом,  отбрасы-
ваются). В предыдущем примере три параметра (по два байта каждый)
занимают в стеке вместе 6 байт, поэтому Турбо Си добавляет значе-
ние 6 к указателю стека,  чтобы отбросить параметры после обраще-
ния к функции Test. Важный момент здесь заключается в том, что по
соглашениям Турбо Си за удаление параметров из стека отвечает вы-
зывающая  программа  (см.  далее  раздел "Соглашения по вызовам в
Паскале").

     Функции Ассемблера могут обращаться к параметрам, передавае-
мым в стеке, относительно регистра BP. Например, предположим, что
функция Test в предыдущем примере  представляет  собой  следующую
функцию на Ассемблере:

                     DOSSEG
                     .MODEL   SMALL
                     .CODE
                     PUBLIC   _Test
_Test                PROC
                     push   bp
                     mov    bp,sp
                     mov    ax,[bp+4]    ; получить параметр 1
                     add    ax,[bp+6]    ; прибавить параметр 2

TASM2 #2-5/Док              = 198 =

                                         ; к параметру 1
                     sub    ax,[bp+8]    ; вычесть из суммы 3
                     pop    bp
                     ret
_Test                ENDP

     Как можно видеть,  функция  Test  получает  передаваемые  из
программы  на  языке  Си  параметры  через стек, относительно BP.
(Если вы помните, BP адресуется к сегменту стека.) Но откуда  она
знает, где найти параметры относительно BP?

     На Рис. 7.4 показано, как выглядит  стек  перед  выполнением
первой инструкции в функции Test:

   i = 25;
   j = 4;
   Test(1, j, 1);

                             .                       .
                             .                       .
                             |                       |
                             |-----------------------|
                             |                       |
                             |-----------------------|
                SP -->       |    Адрес возврата     |
                             |-----------------------|
                SP + 2       |        25 (i)         |
                             |-----------------------|
                SP + 4       |        4 (j)          |
                             |-----------------------|
                SP + 6       |           1           |
                             |-----------------------|
                             |                       |
                             |-----------------------|
                             |                       |
                             .                       .

     Рис. 7.4 Состояние стека перед выполнением первой инструкции
функции Test.

     Параметры функции Test представляют собой фиксированные  ад-
реса относительно SP, начиная с ячейки, на два байта старше адре-
са, по которому хранится адрес возврата, занесенный туда при  вы-
зове. После загрузки регистра BP значением SP вы можете обращать-
ся к параметрам относительно BP. Однако, вы должны сначала сохра-
нить BP,  так как в вызывающей программе предполагается,  что при

TASM2 #2-5/Док              = 199 =

возврате  BP  изменен не будет.  Занесение в стек BP изменяет все
смещения в стеке.  На Рис. 7.5 показано состояние стека после вы-
полнения следующих строк кода:

                 .
                 .
                 .
                 push  bp
                 mov   bp,sp
                 .
                 .
                 .

                          .                       .
                          .                       .
                          |                       |
                          |-----------------------|
             SP -->       | BP вызывающей прогр.  |  <-- BP
                          |-----------------------|
             SP + 2       |    Адрес возврата     |  BP + 2
                          |-----------------------|
             SP + 4       |        25 (i)         |  BP + 4
                          |-----------------------|
             SP + 6       |        4 (j)          |  BP + 6
                          |-----------------------|
             SP + 8       |           1           |  BP + 8
                          |-----------------------|
                          |                       |
                          |-----------------------|
                          |                       |
                          .                       .

     Рис. 7.5 Состояние стека после инструкций PUSH и MOVE.

     Организация передачи параметров функции через стек и исполь-
зование  его для динамических локальных переменных - это стандар-
тный прием в языке Си. Как можно заметить, неважно, сколько пара-
метров имеет программа на языке Си:  самый левый параметр  всегда
хранится  в стеке по адресу,  непосредственно следующим за сохра-
ненным в стеке адресом возврата,  следующий возвращаемый параметр
хранится  непосредственно  после  самого  левого параметра и т.д.
Поскольку порядок и  тип  передаваемых  параметров  известны,  их
всегда можно найти в стеке.

     Пространство для динамических локальных переменных можно за-
резервировать, вычитая  из  SP  требуемое  число байт.  Например,

TASM2 #2-5/Док              = 200 =

пространство для динамического локального массива размером  в 100
байт можно  зарезервировать,  если начать функцию Test с инструк-
ций:

                     .
                     .
                     .
                     push  bp
                     mov   bp,sp
                     sub   sp,100
                     .
                     .
                     .

как показано на Рис. 7.6.

                          .                       .
                          .                       .
                          |                       |
                          |-----------------------|
             SP -->       |                       |  BP - 100
                          |-----------------------|
                          |                       |
                          |-----------------------|
                          .                       .
                          .                       .

                          .                       .
                          .                       .
                          |                       |
                          |-----------------------|
             SP + 100     | BP вызывающей прогр.  |  <-- BP
                          |-----------------------|
             SP + 102     |    Адрес возврата     |  BP + 2
                          |-----------------------|
             SP + 104     |        25 (i)         |  BP + 4
                          |-----------------------|
             SP + 106     |        4 (j)          |  BP + 6
                          |-----------------------|
             SP + 108     |           1           |  BP + 8
                          |-----------------------|
                          |                       |
                          |-----------------------|
                          |                       |
                          .                       .


TASM2 #2-5/Док              = 201 =

     Рис. 7.6 Состояние стека после инструкций PUSH, MOVE и SUB.

     Поскольку та часть стека, где хранятся динамические  локаль-
ные переменные, представляет собой более младшие  адреса, чем BP,
для обращения к динамическим  локальным  переменным  используется
отрицательное смещение. Например, инструкция:

           mov   byte ptr  [bp-100]

даст   значение   первого    байта    ранее    зарезервированного
100-байтового массива. При передаче параметров всегда использует-
ся положительная адресация относительно регистра BP.

     Хотя можно выделять пространство для динамических  локальных
переменных описанным выше способом, в Турбо Ассемблере предусмот-
рена специальная версия директивы LOCAL, которая существенно  уп-
рощает  выделение памяти и присваивание имен для динамических ло-
кальных  переменных.  Когда  в  процедуре  встречается  директива
LOCAL, то подразумевается, что она определяет для данной процеду-
ры динамические локальные переменные. Например, директива:

          LOCAL  LocalArray:BYTE:100,LocalCount:WORD=AUTO_SIZE

определяет  динамические  переменные   LocalArray  и  LocalCount.
LocalArray на самом деле представляет собой метку, приравненную к
[BP-100], а LocalCount - это метка, приравненная к [BP-102].  Од-
нако  вы  можете  использовать их, как имена переменных. При этом
вам даже не нужно будет знать их значения. AUTO_SIZE - это  общее
число байт (объем памяти),  необходимых для хранения динамических
локальных переменных.  Чтобы выделить пространство для динамичес-
ких локальных переменных, это значение нужно вычесть из SP.

     Приведем  пример  того,  как  нужно  использовать  директиву
LOCAL:

                 .
                 .
                 .
 _TestSub        PROC
                 LOCAL
 LocalArray:BYTE:100,LocalCount:WORD=AUTO_SIZE
                 push  bp          ; сохранить указатель стека
                                   ; вызывающей программы
                 mov   bp,sp       ; установить собственный
                                   ; указатель стека
                 sub   sp,AUTO_SIZE ; выделить пространство для

TASM2 #2-5/Док              = 202 =

                                   ; динамических локальных
                                   ; переменных
                 mov   [LocalCount],10 ; установить переменную
                                   ; LocalCount в значение 10
                                   ; (LocalCount это  [BP-102])
                 .
                 .
                 .
                 mov   cx,[LocalCount] ; получить значение
                                    ; (счетчик) из локальной
                                    ; переменной
                 mov   al,'A'       ; заполним символом 'A'
                 lea   bx,[LocalArray] ; ссылка на локальный
                                    ; массив LocalArray
                                    ; (LocalArray это [BP-100])
 FillLoop:
                 mov   [bx],al      ; заполнить следующий байт
                 inc   bx           ; ссылка на следующий байт
                 loop  FillLoop     ; обработать следующий байт,
                                    ; если он имеется
                 mov   sp,bp        ; освободить память,
                                    ; выделенную для динамичес-
                                    ; ких локальных переменных
                                    ; (можно также использовать
                                    ; add sp,AUTO_SIZE)
                 pop   bp           ; восстановить указатель
                                    ; стека вызывающей программы
                 ret
 _TestSub        ENDP
                 .
                 .
                 .

     В данном примере следует обратить внимание не то, что первое
поле  после  определения данной динамической локальной переменной
представляет собой тип данных для этой  переменной:  BYTE,  WORD,
DWORD,  NEAR и т.д.  Второе поле после определения данной динами-
ческой локальной переменной - это число элементов указанного  ти-
па, резервируемых для данной переменной. Это поле является необя-
зательным и определяет используемый динамический локальный массив
(если он используется). Если данное поле пропущено, то резервиру-
ется один элемент указанного типа.  В итоге LocalArray состоит из
100 элементов размером в 1 байт, а LocalCount - из одного элемен-
та размером в слово (см. пример).

     Отметим также, что строка с директивой LOCAL в данном приме-

TASM2 #2-5/Док              = 203 =

ре  завершается полем =AUTO_SIZE. Это поле, начинающееся со знака
равенства, необязательно. Если оно присутствует, то метка, следу-
ющая за знаком равенства,  устанавливается в значение числа  байт
требуемой динамической локальной памяти.  Вы должны затем исполь-
зовать данную метку для выделения и освобождения памяти для дина-
мических локальных переменных, так как директива LABEL только ге-
нерирует метки и не  генерирует  никакого  кода  или  памяти  для
данных.  Иначе говоря, директива LOCAL не выделяет память для ди-
намических локальных переменных, а просто генерирует метки, кото-
рые  вы  можете использовать как для выделения памяти,  так и для
доступа к динамическим локальным переменным.

     Очень удобное свойство директивы LOCAL  заключается  в  том,
что  область  действия  меток динамических локальных переменных и
общего размера динамических локальных переменных  ограничена  той
процедурой, в которой они используются, поэтому вы можете свобод-
но использовать имя динамической локальной  переменной  в  другой
процедуре.

     Как можно заметить, с помощью директивы LOCAL  определять  и
использовать  автоматические  переменные  намного легче. Отметим,
что при использовании в макрокомандах директива LOCAL  имеет  со-
вершенно  другое  значение  (см. Главу 9). (Вы можете обратиться
также к Главе 3 "Справочного руководства", где приведена дополни-
тельная информация о видах директивы LOCAL.)

     Кстати, Турбо Си работает с границами стека так же,  как  мы
здесь  описали.  Вы можете скомпилировать несколько модулей Турбо
Си с параметром -s и посмотреть, какой код Ассемблера  генерирует
Турбо Си и как там создаются и используются границы стека.

     Все  это  прекрасно,  но  здесь  есть  некоторые  трудности.
Во-первых, такой способ доступа к параметрам, при котором исполь-
зуется постоянное смещение относительно BP достаточно  неприятен:
при  этом  не  только легко ошибиться, но если вы добавите другой
параметр, все другие смещения указателя стека  в  функции  должны
измениться.  Предположим, например, что функция Test воспринимает
три параметра:

        Test(Flag, i, j, 1);

     Тогда i находится по смещению 6, а не по смещению 4, j -  по
смещению 8, а не 6 и т.д. Для смещений параметров можно использо-
вать директиву EQU:

                    .

TASM2 #2-5/Док              = 204 =

                    .
                    .
Flag                EQU  4
AddParm1            EQU  6
AddParm2            EQU  8
SubParm1            EQU  10

                    mov   ax[bp+AddParm1]
                    add   ax,[bp+AddParm1]
                    sub   ax,[bp+SubParm1]
                    .
                    .
                    .

но вычислять смещения и работать с ними довольно  сложно.  Однако
здесь  могут возникнуть и более серьезные проблемы: в моделях па-
мяти с дальним кодом размер занесенного в  стек  адреса  возврата
увеличивается на два байта, как и размеры передаваемых указателей
на код и данные в моделях памяти с дальним кодом и дальними  дан-
ными,  соответственно. Разработка функции, которая с равным успе-
хом будет ассемблироваться и правильно работать с указателем сте-
ка при использовании любой модели памяти было бы весьма непростой
задачей.

     Однако опасения излишни. В  Турбо  Ассемблере  предусмотрена
директива  ARG,  с помощью которой можно легко выполнять передачу
параметров в программах на Ассемблере.

     Директива ARG автоматически генерирует правильные смещения в
стеке для заданных вами переменных. Например:

           arg      FillArray:WORD,Count:WORD,FillValue:BYTE

     Здесь задается три параметра: FillArray, параметр размером в
слово, Count, также параметр размером в слово и FillValue - пара-
метр  размером  в  байт.  Директива  ARG    устанавливает   метку
FillArray   в значение [BP+4] (подразумевается, что код находится
в процедуре ближнего типа), метку Count - в  значение  [BP+6],  а
метку FillValue - в значение [BP+8]. Однако особенно ценна дирек-
тива ARG тем, что вы можете использовать определенные  с  ее  по-
мощью метки не заботясь о тех значениях, в которые они установле-
ны.

     Например, предположим, что у вас есть функция FillSub  кото-
рая вызывается из Си следующим образом:


TASM2 #2-5/Док              = 205 =

      main()
      {
      #define ARRAY_LENGTH 100
         char TestArray[ARRAY_LENGTH];
         FillSub(TestArray,ARRAY_LENGTH,'*');
      }

     В FillSub директиву ARG для работы с параметрами  можно  ис-
пользовать следующим образом:

_FillSub       PROC  NEAR
         ARG   FillArray:WORD,Count:WORD,FillValue:BYTE
         push  bp           ; сохранить указатель стека
                            ; вызывающей программы
         mov   bp,sp        ; установить свой собственный
                            ; указатель стека
         mov   bx,[FillArray] ; получить указатель на
                            ; заполняемый массив
         mov   cx,[Count]   ; получить заполняемую длину
         mov   al,[FillValue] ; получить значение-заполнитель
FillLoop:
         mov   [bx],al      ; заполнить символ
         inc   bx           ; ссылка на следующий символ
         loop  FillLoop     ; обработать следующий символ
         pop   bp           ; восстановить указатель стека
                            ; вызывающей программы
         ret
_FillSub       ENDP

     Не правда ли, удобно работать с параметрами с помощью дирек-
тивы  ARG? Кроме того, директива ARG автоматически учитывает раз-
личные размеры возвратов ближнего и дальнего типа. Другое удобст-
во  состоит  в  том,  что метки, определенные с помощью директивы
ARG, ограничены по области действия той процедурой, где  они  ис-
пользуются, и вам не приходится беспокоиться о возможном конфлик-
те между именами параметров в различных процедурах.

     Дополнительная информация о директиве ARG содержится в Главе
3 "Справочного руководства".




TASM2 #2-5/Док              = 206 =

                         Сохранение регистров
-----------------------------------------------------------------

     При взаимодействии Турбо Ассемблера и Турбо Си вызываемые из
программы на языке Си функции Ассемблера  могут  делать  все  что
угодно,  но при этом они должны сохранять регистры BP, SP, CS, DS
и SS.  Хотя при выполнении функции Ассемблера эти регистры  можно
изменять,  при  возврате  из  вызываемой  подпрограммы они должны
иметь в точности такие значения,  какие они имели при ее  вызове.
Регистры AX, BX, CX, DX и ES, а также флаги могут произвольно из-
меняться.

     Регистры DI и SI представляют собой особый случай, так как в
Турбо  Си они используются для регистровых переменных. Если в мо-
дуле Си, из которого вызывается ваша функция на  Ассемблере,  ис-
пользование регистровых переменных разрешено, то вы должны сохра-
нить регистры SI и DI, если же нет, то сохранять их не нужно. Од-
нако  неплохо  всегда сохранять эти регистры, независимо от того,
разрешено или  запрещено  использование  регистровых  переменных.
Трудно  заранее  гарантировать,  что  вам не придется компоновать
данный модуль Ассемблера с другим модулем на языке Си,  или пере-
компилировать  модуль  Си с разрешением использования регистровых
переменных.  При этом вы можете забыть, что изменения нужно также
внести и в код Ассемблера.

Возврат значений
-----------------------------------------------------------------

     Вызываемые из программы на языке Си функции  на  Ассемблере,
так  же  как  и функции Си,  могут возвращать значения.  Значения
функций возвращаются следующим образом:

-----------------------------------------------------------------
Тип возвращаемого значения    Где находится возвращаемое значение
-----------------------------------------------------------------
unsigned char                                AX
char                                         AX
enum                                         AX
unsigned short                               AX
short                                        AX
unsigned int                                 AX
int                                          AX
unsigned long                                DX:AX
long                                         DX:AX
float                          регистр вершины стека сопроцессора
                               8087 (ST(0))

TASM2 #2-5/Док              = 207 =

double                         регистр вершины стека сопроцессора
                               8087 (ST(0))
long double                    регистр вершины стека сопроцессора
                               8087 (ST(0))
near*                                        AX
far*                                         DX:AX
-----------------------------------------------------------------

     В общем случае 8- и 16-битовые значения возвращаются  в  ре-
гистре  AX,  а 32-битовые значения - в AX:DX (при этом старшие 16
бит значения находятся в регистре DX).  Значения с плавающей точ-
кой возвращаются в регистре ST(0), который представляет собой ре-
гистр вершины стека сопроцессора 8087 или  эмулятора сопроцессора
8087, если используется эмулятор операций с плавающей точкой.

     Со структурами дело обстоит  несколько  сложнее.  Структуры,
имеющие  длину 1 или 2 байта, возвращаются в регистре AX, а стук-
туры длиной 4 байта - в регистрах AX:DX. Трехбайтовые структуры и
структуры,  превышающие 4 байта должны храниться в области стати-
ческих данных, при этом должен возвращаться указатель на эти ста-
тические данные. Как и все указатели, указатели на структуры, ко-
торые имеют ближний тип (NEAR), возвращаются  в  регистре  AX,  а
указатели дальнего типа - в паре регистров AX:DX.

     Давайте рассмотрим вызываемую из программы на языке Си функ-
цию  на  Ассемблере с малой моделью памяти FindLastChar,  которая
возвращает указатель на последний символ передаваемой  строки. На
языке Си прототип этой функции выглядел бы следующим образом:

     extern char * FindLastChar(char * StringToScan);

где StringToScan - это непустая строка, для которой должен  возв-
ращаться указатель на последний символ.

     Функция FindLastChar имеет следующий вид:

                  DOSSEG
                  .MODEL  SMALL
                  .CODE
                  PUBLIC _FindLastChar
_FindLastChar            PROC
                  push  bp
                  mov   bp,sp
                  cld             ; в строковой инструкции нужно
                                  ; выполнять отсчет в прямом
                                  ; направлении

TASM2 #2-5/Док              = 208 =

                  mov   ax,ds
                  mov   es,ax     ; теперь ES указывает на
                                  ; ближний сегмент данных
                  mov   di,       ; теперь ES:DI указывает на
                                  ; начало передаваемой строки
                  mov   al,0      ; найти нулевой символ,
                                  ; завершающий строку
                  mov   cx,0ffffh ; работать в пределах
                                  ; 64К-1 байт
                  repne scasb     ; найти нулевой символ
                  dec   di        ; установить указатель
                                  ; обратно на 0
                  dec   di        ; ссылка обратно на
                                  ; последний символ
                  mov   ax,dx     ; возвратить в AX указатель
                                  ; ближнего типа
                  pop   bp
                  ret
_FindLastChar     ENDP
                  END

     Конечный результат, указатель на передаваемую строку,  возв-
ращается в регистре AX.




TASM2 #2-5/Док              = 209 =

              Вызов функций Турбо Ассемблера из Турбо Си
-----------------------------------------------------------------

     Теперь мы рассмотрим пример программы на Турбо Си,  вызываю-
щей функцию Турбо Ассемблера.  Модуль Турбо Ассемблера  COUNT.ASM
содержит функцию LineCount,  которая возвращает значение счетчика
числа строк и символов в передаваемой строке:

; Вызываемая из Си функция на Ассемблере с малой моделью памяти
; для подсчета числа строк и символов в завершающейся нулем
; "строке".
;
; Прототип функции:
;      extern unsigned int LineCount(char * near StringToCount,
;             unsigned int near * CharacterCountPtr);
;
; Ввод:
;      char near * StringToCount: указатель на "строку", в
;      которой нужно выполнить подсчет строк.
;
;      unsigned int near * CharacterCountPtr: указатель на
;      целую переменную, в которую нужно записать значение
;      счетчика
NEWLINE      EQU   0ah        ; символ перевода строки в Си
             DOSSEG
             .MODEL  SMALL
             .CODE
             PUBLIC  _LinaCount
_LineCount   PROC
             push  bp
             mov   bp,sp
             push  si         ; сохранить регистровую
                              ; переменную вызывающей
                              ; программы
             mov   si,[bp+4]  ; SI указывает на строку
             sub   cx,cx      ; установить значение
                              ; счетчика символов в 0
             mov   dx,cx      ; установить в 0 счетчик
                              ; строк
LineCountLoop:
             lodsb            ; получить следующий символ
             and   al,al      ; это 0? конец строки?
             jz    EndLineCount ; да, выполнено
             inc   cx         ; нет, подсчитать следующий
                              ; символ
             cmp   al,NEWLINE ; это новая строка?

TASM2 #2-5/Док              = 210 =

             jnz   LineCountLoop ; нет, проверить
                              ; следующий символ
             inc   dx         ; да, подсчитать еще одну
                              ; строку
             jmp   LineCountLoop
EndLineCount:
             inc   dx         ; подсчитать строку, которая
                              ; завершается нулевым символом
             mov   [bx],cx    ; задать значение переменной-
                              ; счетчика
             mov   ax,dx      ; возвратить счетчик строк в
                              ; качестве значения счетчика
             pop   si         ; восстановить регистровую
                              ; переменную вызывающей
                              ; программы
             pop   bp
             ret
_LineCount         ENDP
             END

     Следующий модуль на языке Си с именем  CALLC.C  представляет
собой пример вызова функции LineCount:

  char * TestString="Line 1\nline 2\n Line3";
  extern unsigned int LineCount(char * StringToCount,
         unsigned int * CharacterCountPtr);
  main()
  {
      unsigned int LCount;
      unsigned int CCount;

      LCount = LineCount(TestString, &CCount);
      printf("Строк: %d\nCимволов: %d\n", LCount, CCount);
  }

     Два модуля компилируются и компонуются вместе с помощью  ко-
мандной строки:

        tcc -ms callc count.asm

     Как здесь показано, функция LineCount будет работать  только
при компоновке с программами на языке Си,  в которых используется
малая модель памяти,  так как в других моделях размеры указателей
и  адресов  в  стеке  изменятся.  Приведем  пример версии функции
LineCount (COUNTLG.ASM),  которая будет работать с программами на
Си,  использующим большую модель памяти (но не малую модель: пос-

TASM2 #2-5/Док              = 211 =

кольку передаются дальние указатель, функция LineCount также опи-
сана, как функция дальнего типа):

; Вызываемая из Си функция на Ассемблере для подсчета числа
; строк и символов в завершающейся нулем "строке".
;
; Прототип функции:
;      extern unsigned int LineCount(char * far StringToCount,
;             unsigned int far * CharacterCountPtr);
;
; Ввод:
;      char far * StringToCount: указатель на "строку", в
;      которой нужно выполнить подсчет строк.
;
;      unsigned int far * CharacterCountPtr: указатель на
;      целочисленную переменную, в которую нужно записать
;      значение счетчика
NEWLINE      EQU   0ah        ; символ перевода строки в Си
             DOSSEG
             .MODEL  LARGE
             .CODE
             PUBLIC  _LinaCount
_LineCount   PROC
             push  bp
             mov   bp,sp
             push  si         ; сохранить регистровую
                              ; переменную вызывающей
                              ; программы
             push  ds         ; сохранить стандартный
                              ; сегмент данных
             lds   si,[bp+6]  ; DS:SI указывает на строку
             sub   cx,cx      ; установить значение
                              ; счетчика символов в 0
             mov   dx,cx      ; установить в 0 счетчик
                              ; строк
LineCountLoop:
             lodsb            ; получить следующий символ
             and   al,al      ; это 0? конец строки?
             jz    EndLineCount ; да, выполнено
             inc   cx         ; нет, подсчитать следующий
                              ; символ
             cmp   al,NEWLINE ; это новая строка?
             jnz   LineCountLoop ; нет, проверить
                              ; следующий символ
             inc   dx         ; да, подсчитать еще одну
                              ; строку

TASM2 #2-5/Док              = 212 =

             jmp   LineCountLoop
EndLineCount:
             inc   dx         ; подсчитать строку, которая
                              ; завершается нулевым символом
             les   bx,[bp+10] ; ES:BX указывает на ячейку,
                              ; в которой возвращается
                              ; значение счетчика
             mov   es:[bx],cx ; задать значение переменной-
                              ; счетчика
             mov   ax,dx      ; возвратить счетчик строк в
                              ; качестве значения счетчика
             pop   ds         ; восстановить стандартный
                              ; сегмент данных Си
             pop   si         ; восстановить регистровую
                              ; переменную вызывающей
                              ; программы
             pop   bp
             ret
_LineCount         ENDP
             END

     Программу COUNTLG.ASM можно скомпоновать с CALLC.C с помощью
следующей командной строки:

        tcc -ml callc countlg.asm




TASM2 #2-5/Док              = 213 =

            Соглашения по вызовам, использующиеся в Паскале
-----------------------------------------------------------------

     Итак, теперь вы уже знаете, как обычно в Си передаются пара-
метры  функциям:  вызывающая  программа заносит параметры (справа
налево) в стек, вызывает функцию, и извлекает параметры из  стека
(отбрасывает  их)  после вызова. Турбо Си может также работать по
соглашениям, принятым в Паскале. Согласно этим соглашениям  пара-
метры  передаются слева направо, а отбрасывает параметры (из сте-
ка) вызываемая программа. Разрешить использование соглашений Пас-
каля в Турбо Си можно с помощью параметра командной строки -p или
ключевого слова pascal.

     Приведем пример функции на Ассемблере, в которой используют-
ся соглашения Паскаля:

;
; Вызывается, как: TEST(i, j ,k)
;
i       equ      8               ; левый параметр
j       equ      6
k       equ      4               ; правый параметр
;
                 DOSSEG
                 .MODEL   SMALL
                 .CODE
                 PUBLIC   TEST
TEST             PROC
                 push  bp
                 mov   bp,sp
                 mov   ax,[bp+i] ; получить i
                 add   ax,[bp+j] ; прибавить к i j
                 sub   ax,[bp+k] ; вычесть из суммы k
                 pop   bp
                 ret   6         ; возврат, отбросить
                                 ; 6 байт параметров
                                 ; (очистка стека)
TEST             ENDP
                 END

     На Рис. 7.7 показано состояние стека после выполнения  инст-
рукции MOV BP,SP:
                          .                       .
                          .                       .
                          |                       |
                          |-----------------------|

TASM2 #2-5/Док              = 214 =

             SP -->       | BP вызывающей прогр.  |  <-- BP
                          |-----------------------|
             SP + 2       |    Адрес возврата     |  BP + 2
                          |-----------------------|
             SP + 4       |          k            |  BP + 4
                          |-----------------------|
             SP + 6       |          j            |  BP + 6
                          |-----------------------|
             SP + 8       |          i            |  BP + 8
                          |-----------------------|
                          |                       |
                          |-----------------------|
                          |                       |
                          .                       .

     Рис. 7.7 Состояние стека после инструкции MOV BP,SP.

     Заметим, что для очистки стека  от  передаваемых  параметров
используется инструкция RET 6.

     Соглашения по вызовам Паскаля требуют также, чтобы все внеш-
ние и общедоступные идентификаторы указывались в верхнем регистре
и без предшествующих подчеркиваний. Зачем может потребоваться ис-
пользовать в программе на Си соглашения по вызовам Паскаля? Прог-
рамма, использующая соглашения Паскаля, занимает обычно несколько
меньше  места  в памяти и работает быстрее, чем обычная программа
на языке Си, так как для очистки стека от параметров не требуется
выполнять  n  инструкций ADD SP.  Более подробно о соглашениях по
вызовам, принятым в Паскале, рассказывается в Главе 8.




TASM2 #2-5/Док              = 215 =


Вызов Турбо Си из Турбо Ассемблера
-----------------------------------------------------------------

     Хотя больше принято для выполнения специальных  задач  вызы-
вать  из  Си  функции, написанные на Ассемблере, иногда вам может
потребоваться вызывать из Ассемблера функции, написанные на языке
Си.  Оказывается, на самом деле легче вызвать функцию Турбо Си из
функции Турбо Ассемблера, чем наоборот, поскольку со стороны  Ас-
семблера не требуется отслеживать границы стека. Давайте рассмот-
рим кратко требования для вызова функций Турбо Си из Турбо Ассем-
блера.

                  Компоновка с кодом инициализации Си
-----------------------------------------------------------------

     Хорошим правилом является вызов библиотечных  функций  Турбо
Си только из Ассемблера в программах, которые компонуются с моду-
лем инициализации Си (используя его в качестве первого  компонуе-
мого  модуля). Этот "надежный" класс включает в себя все програм-
мы, которые компонуются с помощью  командной  строки  TC.EXE  или
TCC.EXE, и программы, в качестве первого компонуемого файла кото-
рых используется файл C0T, C0S, C0C, C0M, C0L или C0H.

     В общем случае вам не следует вызывать библиотечные  функции
Турбо  Си из программ, которые не компонуются с модулем инициали-
зации Турбо Си, так как некоторые библиотечные функции  Турбо  Си
не будут правильно работать, если не выполнялась компоновка с ко-
дом инициализации. Если вы действительно хотите вызывать  библио-
течные  функции  Турбо  Си  из  таких программ, мы предлагаем вам
взглянуть на код инициализации(файл C0.ASM на дистрибутивных дис-
ках Турбо Си) и приобрести у фирмы Borland исходный код библиоте-
ки языка Си,  после чего вы сможете обеспечить правильную инициа-
лизацию для нужных библиотечных функций.  Другой возможный подход
состоит  просто  в  том,  чтобы  скомпоновать нужную библиотечную
функцию с программой на Ассемблере, которая называется, например,
X.ASM, и которая просто вызывает каждую функцию. Компоновку можно
выполнить с помощью командной строки типа:

        tlink x,x,,cm.lib

где m - это первая буква желаемой модели памяти (t -  сверхмалая,
s  - малая, c - компактная и т.д.). Если TLINK выдаст сообщения о
неопределенных идентификаторах, то  данную  библиотечную  функцию
без компоновки с кодом инициализации Си вызывать нельзя.


TASM2 #2-5/Док              = 216 =

           Примечание: Вызов определяемых  пользователем  функций
      Си,  которые  в  свою очередь вызывают библиотечные функции
      языка Си,  попадают в ту же категорию, что и непосредствен-
      ный вызов библиотечных функций Си.  Отсутствие кода инициа-
      лизации  Си может вызывать ошибки в любой программе Ассемб-
      лера,  которая прямо или косвенно обращается к библиотечным
      функциям Си.




TASM2 #2-5/Док              = 217 =


Убедитесь в том, что вы правильно задали сегменты
-----------------------------------------------------------------

     Как мы уже говорили ранее,  необходимо  обеспечивать,  чтобы
Турбо Си и Турбо Ассемблер использовали одну и ту же модель памя-
ти, и чтобы сегменты, которые вы используете в Турбо  Ассемблере,
совпадали  с  теми сегментами, которые использует Турбо Си. Нужно
не забывать также помещать директиву EXTRN для внешних  идентифи-
каторов вне всех сегментов или внутри правильного сегмента.

                           Выполнение вызова
-----------------------------------------------------------------

     В разделе "Вызов функций Турбо Ассемблера из  Турбо  Си"  мы
уже  узнали  о  том, как Турбо Си выполняет подготовку к вызову и
вызов функции. Мы кратко рассмотрели механизм вызов  функций  Си,
на  этот  раз с точки зрения вызова функций Турбо Си из Турбо Ас-
семблера.

     Все, что требуется от вас для передачи параметров  в функцию
Турбо  Си,  это занесение в стек самого правого параметра первым,
затем следующего по порядку параметра и так далее, пока  в  стеке
не  окажется  самый  левый параметр. После этого нужно просто вы-
звать функцию. Например, при программировании на Турбо Си для вы-
зова  библиотечной функции Турбо Си strcpy для копирования строки
SourceString в строку DestString можно ввести:

        strcpy(DestString, SourceString);

     Для выполнения того же вызова на Ассемблере нужно  использо-
вать инструкции:

            lea   ax,SourceString     ; правый параметр
            push  ax
            lea   ax,DestString       ; левый параметр
            push  ax
            call  _strcpy             ; скопировать строку
            add   sp,4                ; отбросить параметры

     При настройке SP после вызова не забывайте очищать  стек  от
параметров.

     Если вы вызываете функцию Си, которая использует  соглашения
Паскаля,  заносите  в  стек параметры слева направо. После вызова
настраивать указатель стека SP не требуется.

TASM2 #2-5/Док              = 218 =


            lea   ax,DestString       ; левый параметр
            push  ax
            lea   ax,SourceString     ; правый параметр
            push  ax
            call  CTRCPY              ; скопировать строку

     В последнем случае конечно подразумевается, что вы  переком-
пилировали  функцию strcpy с параметром -p, так как в стандартной
библиотечной версии данной функции используются соглашения по вы-
зову, принятые в Си, а не в Паскале. Функции Си возвращают значе-
ния, как описано в разделе "Возврат значений":  8-  и  16-битовые
значения  возвращаются  в  регистре AX, а 32-битовые значения - в
AX:DX (при этом старшие 16 бит значения находятся в регистре DX).
Значения с плавающей точкой возвращаются в ST(0) (регистр вершины
стека сопроцессора 8087 или эмулятора сопроцессора 8087, если ис-
пользуется эмулятор операций с плавающей точкой). Структуры возв-
ращаются различным образом, в соответствии с их размером.

     Функции Си сохраняют следующие регистры (и только  их):  SI,
DI,  BP, DS, SS, SP и CS. Регистры AX, BX, CX, DX, ES и флаги мо-
гут произвольно изменяться.




TASM2 #2-5/Док              = 219 =

              Вызов из Турбо Ассемблера функции Турбо Си
-----------------------------------------------------------------

     Одним из случаев, когда вам может потребоваться  вызвать  из
Турбо  Ассемблера функцию Турбо Си, является необходимость выпол-
нения сложных вычислений, поскольку вычисления гораздо проще  вы-
полнять на Си, чем на Ассемблера. Особенно это относится к случаю
смешанных вычислений, где используются  и  значения  с  плавающей
точкой  и целые числа. Лучше возложить функции по выполнению пре-
образования типов и реализации арифметики с плавающей  точкой  на
Си.

     Давайте рассмотрим пример программы на  Ассемблере,  которая
вызывает функцию Турбо Си, чтобы выполнить вычисления с плавающей
точкой. Фактически в данном примере  функция  Турбо  Си  передает
последовательность  целых  чисел другой функции Турбо Ассемблера,
которая суммирует числа и в свою очередь вызывает другую  функцию
Турбо Си для выполнения вычислений с плавающей точкой (вычисление
среднего значения).

     Часть программы CALCAVG.C,  реализованная  на  Си,  выглядит
следующим образом:

extern float Average(int far * ValuePtr, int NumberOfValues);
#define NUMBER_OF_TEST_VALUES 10
int TestValues(NUMBER_OF_TEST_VALUES) = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10
};

main()
{
       printf("Среднее арифметическое равно: %f\n",
              Average(TestValues, NUMBER_OF_TEST_VALUES));
}
float IntDivide(int Divedent, int Divisor)
}
      return( (float) Divident / (float) Divisor );
}

а часть программы на Ассемблере (AVERAGE.ASM) имеет вид:

;
; Вызываемая из Си функция с малой моделью памяти,
; которая возвращает среднее арифметическое последова-
; тельности целых чисел. Для выполнения завершающего
; деления вызывает функцию Си IntDivide().

TASM2 #2-5/Док              = 220 =

;
; Прототип функции:
;          extern float Average(int far * ValuePtr,
;                 int NumberOfValues);
;
; Ввод:
;          int far * ValuePtr:      ; массив значений для
;                                   ; вычисления среднего
;          int NumberOfValues:      ; число значений для
;                                   ; вычисления среднего
               DOSSEG
               .MODEL   SMALL
               EXTRN    _IntDivide:PROC
               .CODE
               PUBLIC   _Average
_Average       PROC
               push  bp
               mov   bp,sp
               les   bx,[bp+4]       ; ES:BX указывает на
                                     ; массив значений
               mov   cx,[bp+8]       ; число значений, для
                                     ; которых нужно
                                     ; вычислить среднее
               mov   ax,0
AverageLoop:
               add   ax,es:[bx]      ; прибавить текущее
                                     ; значение
               add   ax,2            ; ссылка на следующее
                                     ; значение
               loop  AverageLoop
               push  WORD PTR [bp+8] ; получить снова число
                                     ; значений, переданных
                                     ; в функцию IntDivide
                                     ; в правом параметре
               push  ax              ; передать сумму в
                                     ; левом параметре
               call  _IntDivide      ; вычислить среднее
                                     ; значение с плавающей
                                     ; точкой
               add   sp,4            ; отбросить параметры
               pop   bp
               ret                   ; среднее значение в
                                     ; регистре вершины
                                     ; стека сопроцессора
                                     ; 8087
_Average       ENDP

TASM2 #2-5/Док              = 221 =

               END

     Основная функция  на  языке  Си передает указатель на массив
целых чисел TestValues и длину массива в  функцию  на  Ассемблере
Average. Эта функция вычисляет сумму целых чисел, а затем переда-
ет эту сумму и число значений в  функцию  Си  IntDivide.  Функция
IntDivide приводит сумму и число значений к типу с плавающей точ-
кой и вычисляет среднее значение (делая это с помощью одной стро-
ки на Си, в то время как на Ассемблере для этого потребовалось бы
несколько строк).  Функция IntDivide возвращает среднее  значение
(Average)  в  регистре вершины стека сопроцессора 8087 и передает
управление обратно основной функции.

     Программы CALCAVG.C и  AVERAGE.ASM  можно  скомпилировать  и
скомпоновать в выполняемую программу CALCAVG.EXE с помощью коман-
ды:

        tcc calcavg average.asm

     Отметим, что функция Average будет работать как с малой, так
и  с большой моделью данных без необходимости изменения ее исход-
ного кода, так как во всех моделях передается указатель  дальнего
типа. Для поддержки больших моделей кода (сверхбольшой, большой и
средней) пришлось бы только  изменить  соответствующую  директиву
.MODEL.





TASM2 #2-5/Док              = 222 =

         Глава 8. Интерфейс Турбо Ассемблера с Турбо Паскалем
-----------------------------------------------------------------

     В  Турбо  Ассемблере  предусмотрены  расширенные  и   мощные
средства,  позволяющие  вам добавлять код Ассемблера к программам
Турбо Паскаля. В данной главе мы подробно расскажем  вам  о  том,
что  нужно  знать,  чтобы полностью использовать данные средства,
приведем множество примеров и дадим некоторую более глубокую  ин-
формацию.

     Для  чего  нужно  использовать  Турбо  Ассемблер   с   Турбо
Паскалем?  Большинство  программ,  которые  вы захотите написать,
можно реализовать целиком на Турбо Паскале. В  отличие  от  боль-
шинства  других компиляторов Паскаля, Турбо Паскаль позволяет вам
с помощью массивов Port[], Mem[], MemW[] и MemL[] непосредственно
обращаться ко всем ресурсам машины, а с помощью процедур Intr() и
MsDos() вы  можете  обращаться  к  базовой  системе  ввода-вывода
(BIOS) и операционной системе DOS.

     Для чего же тогда может потребоваться использовать совместно
с  Турбо  Паскалем  Ассемблер? Для этого существуют две вероятные
причины: выполнение некоторого небольшого числа операций, которые
непосредственно в Турбо Паскале недоступны, и использование преи-
муществ высокой скорости работы,  которые  дает  Ассемблер.  (Сам
Турбо  Паскаль  работает достаточно быстро, потому что он написан
на языке Ассемблера.) В данной главе мы покажем  вам,  как  можно
использовать в Турбо Паскале преимущества Ассемблера.

           Примечание: Если номер версии специально не оговарива-
      ется, то везде далее речь идет о Турбо Паскале версии 4.0 и
      старше.




TASM2 #2-5/Док              = 223 =

                      Схема памяти Турбо Паскаля
-----------------------------------------------------------------

     Перед тем, как вы начнете писать код на языке Ассемблера для
работы с Турбо Паскалем, важно понять, как компилятор располагает
информацию в памяти. Модель памяти Турбо Паскаля объединяет неко-
торые  стороны  средней и большой модели памяти, которые описыва-
лись в Главе 5. Здесь имеется один глобальные сегмент данных, ко-
торый позволяет организовать доступ к глобальным переменным и ти-
пизованным константам через регистр DS. Однако каждый модуль име-
ет свой сегмент кода, и динамически распределяемая область памяти
может увеличиваться до размера всей доступной памяти.

     Схема памяти Турбо Паскаля показана на Рис. 8.1.

               Младшие адреса памяти
----------------------------------------------------
|          Префикс программного сегмента           |
|                  (256 байт)                      |
|--------------------------------------------------|
|                                                  |
|         Главный сегмент кода программы           | Максималь-
|                                                  | ный размер
|                                                  | сегмента
|                                                  | кода - 64К
|--------------------------------------------------|
|         Сегмент кода последнего модуля           |
|--------------------------------------------------|
|                                                  |
.                                                  .
.                                                  .
.                                                  .
|                                                  |
|--------------------------------------------------|
|            Сегмент кода первого модуля           |
|--------------------------------------------------|
|                                                  |
|    Сегмент кода библиотеки исполняющей системы   |
|                                                  |
|--------------------------------------------------|<-- DS
|              Типизованные константы              |
|- - - - - - - - - - - - - - - - - - - - - - - - - |<-- Конец
|               Глобальные константы               | файла .EXE
|--------------------------------------------------|<-- SS
|                        ^                         | Размер стека
|                        |                         | Минимум: 1К

TASM2 #2-5/Док              = 224 =

|  Стек (увеличивается в сторону младших адресов)  | По умолча-
|                                                  | нию: 16K
|                                                  | Максимум:
|                                                  |  64К
|--------------------------------------------------|
|    Динамически распределяемая область памяти     | Не ограни-
|    (увеличивается в сторону старших адресов)     | чена по
|                        |                         | размеру
|                        v                         |
|--------------------------------------------------|<-- HeapPtr
|                        ^                         | Максимальный
|                        |                         | размер спис-
|      Список свободных областей динамически       | ка свободных
| распределяемой памяти (увеличивается в сторону   | областей
| младших адресов)                                 |
----------------------------------------------------
               Старшие адреса памяти

     Рис. 8.1 Схема памяти программы Турбо Паскаля версии 5.0.




TASM2 #2-5/Док              = 225 =


Префикс программного сегмента
-----------------------------------------------------------------

     Префикс программного сегмента (PSP) представляет  собой  об-
ласть памяти размером в 256 байт, создаваемую операционной систе-
мой MS-DOS при загрузке программы. Кроме всего прочего она содер-
жит  информацию  о параметрах командной строки,  используемой для
вызова программы,  объеме доступной памяти и  операционной  среде
DOS (списке используемых DOS строковых переменных).

     В версии 3.0 Турбо Паскаля адрес сегмента для  PSP  был  тот
же,  что  и  у  всего остального кода. Теперь это не так. В Турбо
Паскале версии 4.0 и старше основная программа,  используемые  ей
модули  и  библиотека исполняющей системы занимают различные сег-
менты. Турбо Паскаль, таким образом, хранит адрес сегмента PSP  в
предопределенной глобальной переменной с именем PrefixSeg, благо-
даря чему вы можете получить доступ к информации в PSP.


Сегменты кода
-----------------------------------------------------------------

     Каждая программа Турбо Паскаля содержит по крайней мере  два
сегмента  кода: в одном из них содержится код основной программы,
а в другом - библиотека исполняющей системы  (run-time  library).
Кроме  того,  подпрограммы  каждого  модуля находятся в отдельном
сегменте кода.  Так как каждый сегмент кода может иметь размер до
64К,  ваша  программа  может занимать такой объем,  какой для нее
требуется (если,  конечно, такой объем памяти на вашем компьютере
доступен).  Программисты, которые ранее использовали оверлеи, мо-
гут теперь для более быстрого выполнения  генерировать программы,
превышающие 64К, и для более быстрого выполнения хранить весь код
в памяти (в версиях Турбо Паскаля 5.0 и 5.5 вновь введена возмож-
ность  использования  оверлеев,  что  связано с нехваткой памяти,
возникающей при разработке больших программ). С точки зрения Тур-
бо Ассемблера сегмент кода, с которым компонуется модуль на языке
Ассемблера, называется CODE или CSEG.




TASM2 #2-5/Док              = 226 =


Сегмент глобальных данных
-----------------------------------------------------------------

     Сегмент глобальных данных Турбо Паскаля следует за сегментом
кода библиотеки исполняющей системы. Он содержит до 64К инициали-
зированных и неинициализированных данных - типизованных  констант
и  глобальных переменных. Как и в Турбо Паскале 3.0, типизованные
константы на самом деле вовсе не являются константами,  а  предс-
тавляют  собой  переменные, имеющие предопределенное значение при
загрузке программы. Но в отличие от Турбо Паскаля 3.0, Турбо Пас-
каль  версии 4.0 не помещает типизованные константы в сегмент ко-
да. Вместо этого он размещает их в  сегменте  глобальных  данных,
где к ним обращаться можно даже быстрее, чем это мог делать Турбо
Паскаль 3.0. Сегмент глобальных данным называется DATA  или  DSEG
(по этим именам к нему можно обращаться из Турбо Ассемблера).


Стек
-----------------------------------------------------------------

     В Турбо Паскале версии 4.0 и старше сегмент глобальных  дан-
ных находится над стеком. Заметим, что такое расположение отлича-
ется от принятого в Турбо Паскале 3.0. Стек и динамически распре-
деляемая область памяти не растут навстречу друг другу. Для стека
выделяется фиксированный объем памяти. По умолчанию это 16К,  что
вполне  достаточно для большинства программ. Однако вы можете за-
дать минимальный размер стека 1К (для коротких программ) или мак-
симальный  64К  (для программ, интенсивно использующих рекурсию).
Размер стека и динамически распределяемой  области  памяти  можно
выбрать с помощью директивы компилятора $M.

     В большинстве программ для процессоров 80х86 указатель стека
начинается  с вершины стека и изменяет значение в сторону младших
адресов. При вызове процедуры или функции  Турбо  Паскаль  обычно
выполняет  проверку,  чтобы  убедиться, что стек не исчерпан. Эту
проверку можно "выключить" с помощью директивы компилятора {$S-}.




TASM2 #2-5/Док              = 227 =


Динамически распределяемая область памяти
-----------------------------------------------------------------

     В старших адресах памяти Турбо Паскаля находится динамически
распределяемая  область  памяти  (heap). По умолчанию динамически
распределяемая область занимает всю память, не использованную для
сегментов кода,  данных и стека.  Однако для ограничения  размера
динамически  распределяемой области памяти можно использовать ди-
рективу $M (ее можно также использовать для предотвращения выпол-
нения  программы,  если не доступен минимальный объем динамически
распределяемой области памяти).

     Память в  динамически распределяемой области  выделяется ди-
намически  при  обращении к  процедурам  New() и  GetMem(), начи-
ная с нижней ее границы  ("дно").  Когда  используются  процедуры
Dispose  и  FreeMem,  Турбо Паскаль версии 4.0 и выше отслеживает
свободные области, образующиеся в динамически распределяемой  об-
ласти памяти, с помощью специальной структуры данных, которая на-
зывается списком свободных областей (free list). Список свободных
областей, размер которого не может превышать 64К, увеличивается в
сторону младших адресов, начиная с "вершины" динамически  распре-
деляемой области памяти.


Использование регистров в Турбо Паскале
-----------------------------------------------------------------

     Турбо Паскаль налагает на использование регистров  минималь-
ные  ограничения.  При вызове процедуры или функции должны сохра-
няться (и восстанавливаться) значения только трех регистров:  ре-
гистра  сегмента стека (SS), регистра сегмента данных (DS) и ука-
зателя базы (BP). Регистр DS указывает на глобальный сегмент дан-
ных  (с именем DATA), а SS - на сегмент стека. Регистр BP исполь-
зуется в каждой процедуре и функции для ссылки на запись  актива-
ции  (activation record), которая представляет собой пространство
в стеке, используемое для параметров, локальных переменных и вре-
менной  рабочей памяти. Все подпрограммы перед выходом должны вы-
равнивать указатель стека (SP), то есть очищать его  от  парамет-
ров.

                         Ближний или дальний?
-----------------------------------------------------------------

     Поскольку программа Турбо Паскаля содержит несколько сегмен-
тов  кода,  для  обращения к процедурам и функциям она использует

TASM2 #2-5/Док              = 228 =

"смесь" вызовов ближнего (NEAR) и дальнего  (FAR)  типов.  В  чем
разница?  Ближний вызов может использоваться только для обращения
к подпрограмме, которая находится в том же сегменте, что  и  сег-
мент, откуда  делается  вызов. С помощью же дальнего вызова можно
обращаться к подпрограмме, которая находится в любом месте  памя-
ти.  Однако это не проходит даром: дальние вызовы занимают больше
места и выполняются медленнее, чем ближние.

     Каждая подпрограмма программы Турбо Паскаля должна быть  на-
писана (разработана вами или использована компилятором) таким об-
разом, чтобы она вызывалась только одним из этих  двух  способов.
Какой из них следует выбрать? Подпрограммы, описанные в интерфей-
сной части модуля, всегда должны иметь дальний тип, так  как  они
могут  вызываться из других модулей. Однако подпрограммы, описан-
ные в основной программе или объявленные только в разделе  реали-
зации  модуля имеют обычно ближний тип. (Любой подпрограмме можно
принудительно назначить дальний тип с помощью директивы  компиля-
тора {$F+}.)

     При разработке программ на Ассемблере,  взаимодействующих  с
Турбо  Паскалем,  вы  должны  убедиться, что ваша программа имеет
правильный тип вызова.  Турбо Паcкаль не сообщит об ошибке,  если
вы на языке Ассемблера объявите процедуру (PROC),  как ближнюю, а
соответствующее  описание внешней процедуры расположено таким об-
разом, что она должна иметь дальний тип.




TASM2 #2-5/Док              = 229 =

          Совместное использование данных c Турбо Паскалем //

Директива компилятора $L и внешние подпрограммы
-----------------------------------------------------------------

     Два ключевых момента при использовании  Турбо  Ассемблера  с
Турбо Паскалем - это директива компилятора (Турбо Паскаля) {$L} и
описание   внешней   (external)   подпрограммы.   Директива   {$L
MYFILE.OBJ}  приводит к тому, что Турбо Паскаль будет искать файл
объектный MYFILE.OBJ (файл в стандартном пригодном для компоновки
формате MS-DOS) и компоновать его с вашей программой Турбо Паска-
ля.  Если у файла в директиве {$L} расширение не указывается,  то
подразумевается расширение .OBJ.

     Каждая процедура или функция Турбо  Ассемблера,  которую  вы
хотите сделать доступной в программе Турбо Паскаля, должна объяв-
ляться, как идентификатор PUBLIC, и ей должно  соответствовать  в
программе описание external (внешняя). Синтаксис описания внешней
процедуры или функции в  Турбо  Паскале  аналогичен  опережающему
(forward) описанию:

     procedure AsmProc(a : integer; b : real); external;
     function AsmFunc(c : word; d : byte); external;

     Эти описания должны соответствовать  следующим  описаниям  в
программе Турбо Ассемблера:

CODE        SEGMENT BYTE PUBLIC
AsmProc     PROC NEAR
            PUBLIC AsmProc
            .
            .
            .
AsmProc     ENDP
AsmFunc     PROC FAR
            PUBLIC Bar
            .
            .
            .
AsmFunc     ENDP
CODE        ENDS

     Описание внешней процедуры Турбо Паскаля  должно  находиться
на  самом  внешнем  уровне  программы  или модуля, то есть оно не
должно быть вложенным по отношению к другому  описанию  процедуры
или  функции. Попытка описать процедуру или функцию на любом дру-

TASM2 #2-5/Док              = 230 =

гом уровне приведет к ошибке этапа компиляции.

     Турбо Паскаль не делает проверку, чтобы убедиться,  что  все
процедуры,  описанные  с  атрибутами  NEAR или FAR, соответствуют
ближним или дальним подпрограммам в программе Турбо Паскаля. Фак-
тически,  он  даже  не  проверяет,  являются  ли  метки AsmProc и
AsmFunc именами процедур. Поэтому  вы  должны  обеспечить,  чтобы
описания в Ассемблере и Паскале были правильными.




TASM2 #2-5/Док              = 231 =

                           Директива PUBLIC
-----------------------------------------------------------------

     В Турбо Паскале доступны только те метки Ассемблера, которые
объявлены   в  модуле  на  языке  Ассемблера,  как  общедоступные
(PUBLIC). Метки представляют собой единственные объекты,  которые
могут  передаваться  из  языка  Ассемблера в Турбо Паскаль. Более
того, каждой общедоступной метке должно  соответствовать описание
процедуры или функции в программе Турбо Паскаля, иначе компилятор
выдаст сообщение об ошибке. Причем не требуется,  чтобы  общедос-
тупная метка была частью описания PROC. Что касается Турбо Паска-
ля, то для него описания:

AsmLabel     PROC  FAR
             PUBLIC Bar
и

AsmLabel:
             PUBLIC Bar

эквивалентны.




TASM2 #2-5/Док              = 232 =

                            Директива EXTRN
-----------------------------------------------------------------

     Модуль Турбо Ассемблера может обращаться к любой  процедуре,
функции, переменной или типизованной константе Турбо Паскаля, ко-
торая описывается на самом внешнем уровне программы или модуля, с
которым  она компонуется. (Заметим, что это включает в себя пере-
менные, описанные после директивы компилятора {$L} и внешние опи-
сания,  связанные  с  данным  модулем.) Метки и обычные константы
Турбо Паскаля языку Ассемблера недоступны.

     Предположим, в вашем  программе  Турбо  Паскаля  описываются
следующие глобальные переменные:

  var
      a : byte;
      b : word;
      c : shortint;
      d : integer;
      e : real;
      f : single;
      g : double;
      h : extended;
      i : comp;
      j : pointer;

     В программе на языке Ассемблера вы можете получить доступ ко
всем этим переменным с помощью описаний EXTRN:

  EXTRN A : BYTE          ; 1 байт
  EXTRN B : WORD          ; 2 байта
  EXTRN C : BYTE          ; в Ассемблере значения со знаком и
                          ; без знака интерпретируются одинаково
  EXTRN D : WORD          ; то же самое
  EXTRN E : FWORD         ; 6-байтовое действительное значение
                          ; (обрабатывается программно)
  EXTRN F : DWORD         ; 4-байтовое значение с плавающей
                          ; точкой в формате IEEE
  EXTRN G : QWORD         ; 8-байтовое значение с плавающей
                          ; точкой (двойной точности) в
                          ; формате IEEE
  EXTRN H : TBYTE         ; 10-байтовое значение с плавающей
                          ; точкой во временном формате
  EXTRN I : QWORD         ; 8-байтовое целое со знаком в
                          ; формате IEEE (сопроцессор 8087)
  EXTRN J : DWORD         ; указатель Турбо Паскаля

TASM2 #2-5/Док              = 233 =


     Аналогичным образом можно получить  доступ  к  процедурам  и
функциям  Турбо Паскаля, включая библиотечные. Предположим, у вас
имеется модуль Турбо Паскаля, который выглядит следующим образом:

unit Sample;
{ Пример модуля, в котором определяется нескольку процедур
  Паскаля, вызываемых из процедуры на языке Ассемблера }

interface

procedure TestSample;

procedure PublicProc;  { для обращения извне должна
                         быть дальнего типа }
inplementation

var
  A : word;

procedure AsmProc; external;
{$L ASMPROC.OBJ}

procedure PublicProc;
   begin { PublicProc }
     Writeln('В PublicProc');
   end   { PublicProc }

procedure NearProc;   { должна быть ближнего типа }
   begin { NearProc }
     Writeln('B NearProc');
   end;  { NearProc }

{$F+}
procedure FarProc     { должна иметь дальний тип согласно
                        директиве компилятора }
   begin { FarProc }
     Writeln('B FarProc');
   end   { FarProc }

{$F-}

procedure TestSample;
  begin { TestSample }
    Writeln('B TestSample');
    A := 10;

TASM2 #2-5/Док              = 234 =

    Writeln('Значение A перед ASMPROC = ',A);
    AsmProc;
    Writeln('Значение A после ASMPROC = ',A);
  end   { TestSample };

end.

     Процедура AsmProc вызывает  процедуры  PublicProc,  NearProc
или FarProc, используя директиву EXTRN следующим образом:

DATA   SEGMENT WORD PUBLIC
       ASSUME  DS:DATA
       EXTRN   A:WORD                ; переменная из модуля
DATA   ENDS

CODE   SEGMENT BYTE PUBLIC
       ASSUME  CS:CODE
       EXTRN   PublicProc : FAR      ; дальняя процедура
                                     ; (экспортируется модулем)
       EXTRN   NearProc   : NEAR     ; ближняя процедура
                                     ; (локальная для модуля)
       EXTRN   FarProc    : FAR      ; дальняя процедура
                                     ; (локальна, но задана,
                                     ; как дальняя)
AsmProc        PROC NEAR
               PUBLIC AsmProc
               CALL   FAR PTR PublicProc
               CALL   NearProc
               CALL   FAR PTR FarProc
               mov    cx,ds:A        ; взять переменную из
                                     ; модуля
               sub    cx,2           ; изменить ее
               mov    ds:A,cx        ; записать ее обратно
               RET
AsmProc        ENDP
CODE           ENDS
               END

     Основная программа, которая проверяет эту программу  на  Ас-
семблере и модуль Паскаля, выглядит следующим образом:

  program TSample;
  uses Sample;
  begin
      TestSample;
  end.

TASM2 #2-5/Док              = 235 =


     Чтобы сформировать пример программы с  помощью  компилятора,
работающего  в режиме командной строки, и Ассемблера, используйте
следующие команды (или командный файл):

     TASM ASMPROC
     TPC /B SAMPLE
     TSAMPLE

     Так как внешняя подпрограмма должна объявляться в  программе
Турбо  Паскаля на самом внешнем уровне процедур, вы не можете для
доступа к объектам, являющимся локальными по отношению к процеду-
рам  или функциям использовать описания EXTRN. Однако, ваша прог-
рамма на  Турбо  Ассемблере при вызове из программы Турбо Паскаля
может получить эти объекты, как значения параметров-переменных.




TASM2 #2-5/Док              = 236 =

           Ограничения при использовании объектов типа EXTRN
-----------------------------------------------------------------

     Синтаксис составного идентификатора Турбо Паскаля, при кото-
ром  для доступа к объекту в заданном модуле используется имя мо-
дуля и  точка,  несовместим с синтаксическими правилами Турбо Ас-
семблера и будет, таким образом, отвергнут. Описание:

        EXTRN SYSTEM.Assing : FAR

приведет к тому, что Турбо Ассемблер выдаст сообщение об ошибке.

     Имеется также два других ограничения на использование в Тур-
бо  Паскале  объектов  EXTRN.  Первое из них состоит в том, что в
ссылках на процедуру или функцию не могут  выполняться  арифмети-
ческие операции с адресами. Таким образом, если вы объявите:

        EXTRN PublicProc : FAR

то не сможете записать оператор вида:

        call PublicProc + 42

     Второе ограничение относится к тому, что  компоновщик  Турбо
Паскаля  не  будет распознавать операции, которые разделяют слова
на байты, поэтому вы не можете применять такие операции к  объек-
там EXTRN. Например, если вы объявите:

        EXTRN i : WORD

то не сможете использовать в модуле  Турбо  Ассемблера  выражения
LOW i или HIGH i.

                 Использование корректировок сегментов
-----------------------------------------------------------------

     Турбо Паскаль генерирует файлы .EXE,  которые  могут  загру-
жаться  в  память компьютера РС по любому доступному адресу. Пос-
кольку в программе заранее неизвестно, куда будет загружен данный
сегмент  программы, компоновщик указывает загрузчику DOS.EXE, что
нужно при загрузке скорректировать в программе все ссылки на сег-
менты. После выполнения этих корректировок все ссылки на сегменты
(такие, как CODE или DATA) будут содержать корректные значения.

     Ваша программа на Турбо Ассемблере  может  использовать  это

TASM2 #2-5/Док              = 237 =

средство  для  получения  адресов  объектов  во время выполнения.
Предположим, например, что в вашей программе  требуется  изменить
значение  регистра DS, но вы не хотите сохранять в цикле исходное
содержимое стека или перемещать эти  значения  во  временную  об-
ласть.  Вместо этого вы можете использовать операцию Турбо Ассем-
блера SEG:

                 .
                 .
                 .
                 mov   ax,SEG DATA  ; получить действительный
                                    ; адрес глобального значения
                                    ; DS Турбо Паскаля
                 mov   ds,ax        ; поместить его в DS для
                                    ; использования Турбо
                                    ; Паскалем
                 .
                 .
                 .

     Когда ваша программа будет загружаться, DOS поместит коррек-
тное значение SEG DATA прямо в поле промежуточного операнда инст-
рукции MOV. Это наиболее быстрый  путь  перезагрузки  сегментного
регистра.

     Данный метод нужно также использовать, чтобы программы  обс-
луживания  прерываний  сохраняли информацию в глобальном сегменте
данных Турбо Паскаля. Регистр DS не обязательно во время прерыва-
ния содержит значение DS Турбо Паскаля,  но для получения доступа
к переменным и типизованным константам Турбо  Паскаля  можно  ис-
пользовать указанную выше последовательность.

                    Устранение неиспользуемого кода
-----------------------------------------------------------------

     В Турбо Паскале имеются средства, обеспечивающие  устранение
неиспользуемого кода. Это означает, что в полученный в результате
файл .EXE не будет включаться код процедур и функций, который ни-
когда не выполняется. Но поскольку нет полной информации о содер-
жимом модулей Турбо Ассемблера, Турбо Паскаль может выполнять для
них только ограниченную оптимизацию.

     Турбо Паскаль будет устранять код модуля .OBJ в том и только
в  том случае, если к любой доступной процедуре или функции этого
модуля нет обращения. Если же на какую либо процедуру или функцию

TASM2 #2-5/Док              = 238 =

имеется ссылка, то весь этот модуль используется.

     Чтобы добиться большей эффективности использования  средства
Турбо Паскаля по устранению неиспользуемого кода, неплохо было бы
разбить программу на Ассемблере на небольшие модули, которые  со-
держали  бы  только несколько процедур или функций. Это позволило
бы Турбо Паскалю, если он может это сделать, уменьшить объем  ва-
шей конечной программы.




TASM2 #2-5/Док              = 239 =


Соглашения Турбо Паскаля по передаче параметров
-----------------------------------------------------------------

     Турбо Паскаль использует для передачи параметров  стек  цен-
трального  процессора (или, в случае передачи значений параметров
с одинарной, двойной, расширенной точностью  или  сложного  типа,
стек  арифметического сопроцессора). Параметры всегда вычисляются
и заносятся в стек в том порядке, в  котором  они  указываются  в
описании  подпрограммы,  слева направо. В данном разделе мы пояс-
ним, как эти параметры представляются.


Параметры-значения
-----------------------------------------------------------------

     Параметр-значение - это параметр, значение которого не может
изменяться  подпрограммой,  в которую он передается. В отличие от
многих компиляторов, Турбо Паскаль не выполняет слепого копирова-
ния  в стек каждого параметра-значения: как мы далее поясним, ис-
пользуемый метод зависит от типа.

                            Скалярные типы
-----------------------------------------------------------------

     Параметры-значения  всех  скалярных  типов  (boolean,  char,
shortint,  byte, integer, word, longint, отрезки типов и перечис-
лимые типы) передаются как значения через стек  процессора.  Если
размер объекта составляет 1 байт, он заносится в стек, как полное
16-битовое слово, однако более значащий (старший) байт  слова  не
содержит  полезной  информации.  (Нельзя  рассчитывать на то, что
значение этого байта равно 0, как в версии  3.0  Турбо  Паскаля.)
Если  размер  объекта равен двум байтам, то он просто заносится в
стек "как есть". Если объект имеет размер 4  байта  (длинное  це-
лое), он заносится в стек, как два 16-битовых слова. В соответст-
вии со стандартом процессоров серии 8088 наиболее значащее (стар-
шее) слово заносится в стек первым и занимает в стеке старшие ад-
реса.

     Заметим, что сложный тип (comp),  в отличие от целого  типа,
не  считается  скалярным  типом (с точки зрения передачи парамет-
ров).  Таким образом, в Турбо Паскале версии 4.0 параметры-значе-
ния  этого типа передаются в стеке процессора 8087,  а не в стеке
центрального процессора. В Турбо Паскале версии 5.0 значения типа
comp передаются в стеке центрального процессора.


TASM2 #2-5/Док              = 240 =


Вещественные значения
-----------------------------------------------------------------

     Параметры-значения вещественного типа (real) передаются, как
6 байт в стеке (в Турбо Паскале это тип представляет собой 6-бай-
товый программно-эмулируемый тип с плавающей точкой).  Это единс-
твенный тип,  превышающий 4 байта, который может передаваться че-
рез стек.




TASM2 #2-5/Док              = 241 =


Типы сопроцессора 8087
-----------------------------------------------------------------

     В Турбо Паскале версии 4.0 параметры-значения  типов  сопро-
цессора  8087  (с  одиночной,  двойной, расширенной точностью или
сложный тип) передаются через стек сопроцессора, а не через  стек
центрального  процессора.  Так  как  стек сопроцессора 8087 имеет
глубину только 6 уровней, подпрограммы Турбо Паскаля не могут пе-
редавать  более  6  параметров  с типами сопроцессора 8087. Перед
возвратом из подпрограммы она должна извлечь из  стека  арифмети-
ческого сопроцессора все параметры такого типа.

     Турбо Паскаль 5.0 использует при передаче параметров (значе-
ний  процессора 8087) те же соглашения, что и Турбо Си: они пере-
даются в стеке центрального процессора наряду с другими  парамет-
рами. Это могут быть параметры с одинарной,  двойной, расширенной
точностью или сложного типа (сomp).


Указатели
-----------------------------------------------------------------

     Значения параметров для всех типов указателей заносятся  не-
посредственно в стек, как указатели дальнего типа: сначала слово,
содержащее сегмент, затем другое слово, содержащее смещение. Сег-
мент занимает старший адрес,  в соответствии с соглашениями фирмы
Intel.  Для извлечения параметра-указателя в программе Турбо  Ас-
семблера можно использовать инструкции LDS или LES.


Строки
-----------------------------------------------------------------

     Строковые параметры,  независимо от размера,  обычно никогда
не  заносятся  в стек.  Вместо этого Турбо Паскаль заносит в стек
указатель (дальнего типа) на строку.  Вызываемая подпрограмма  не
должна изменять строку,  на которую ссылается указатель. Если это
необходимо,  подпрограмма может создать и работать с копией стро-
ки.

     Единственное исключение из этого правила - это случай, когда
подпрограмма в перекрываемом (оверлейном) модуле A  передает  как
параметр-значение  строковую константу подпрограмме в перекрывае-
мом модуле B.  В этом контексте перекрываемый модуль означает лю-
бой модуль, скомпилированный с директивой {$O+} (допускаются ове-
рлеи). В этом случае перед тем,  как будет сделан вызов  и  адрес

TASM2 #2-5/Док              = 242 =

стека  будет передан программе в модуле B,  в стеке для строковой
константы резервируется временная память. Более  подробная инфор-
мация содержится в Главе 6 ("Оверлеи") и в "Руководстве пользова-
теля по Турбо Паскалю" версии 5.0 или 5.5.


Записи и массивы
-----------------------------------------------------------------

     Записи и массивы,  занимающие ровно 1, 2 или 4 байта, дубли-
руются непосредственно в стек и передаются,  как параметры-значе-
ния. Если массив или запись имеет какой-либо другой размер (вклю-
чая 3 байта),  то в стек заносится указатель на этот  массив  или
запись.  В  этом  случае,  если  подпрограмма  модифицирует такую
структуру, то она должна создать ее локальную копию.


Множества
-----------------------------------------------------------------

     Множества, как и строки,  обычно никогда не заносятся непос-
редственно в стек.  Вместо этого в стек  заносится  указатель  на
множество.  Первый  бит младшего байта множества всегда соответс-
твует элементу базового типа (или порождающего типа) с порядковым
значением 0.

     Единственное исключение из этого правила - это случай, когда
подпрограмма в перекрываемом (оверлейном) модуле A  передает  как
параметр-значение константу-множество подпрограмме  в  оверлейном
модуле B. В этом контексте перекрываемый  модуль  означает  любой
модуль, скомпилированный с директивой {$O+}(допускаются оверлеи).
В этом случае перед тем, как будет сделан вызов и адрес стека бу-
дет передан программе в модуле B, в стеке для множества-константы
резервируется  временная память.  Более  подробная информация со-
держится в Главе 6 ("Оверлеи") и в "Руководстве  пользователя  по
Турбо Паскалю 5.0".


Параметры-переменные
-----------------------------------------------------------------

     Все параметры-переменные  (var) передаются точно также:  как
указатель дальнего типа на их действительные адреса в памяти.

                           Обеспечение стека

TASM2 #2-5/Док              = 243 =

-----------------------------------------------------------------

     Турбо Паскаль  ожидает,  что  перед  возвратом управления из
подпрограммы все параметры в стеке центрального  процессора будут
удалены.

     Есть два способа настройки стека. Вы можете использовать ин-
струкцию RET N (где N - это число байт передаваемых,  то есть за-
несенных в стек, параметров), либо сохранить адрес возврата в ре-
гистрах  (или  в памяти) и извлечь параметры из стека поочередно.
Такую технику извлечения полезно использовать для  оптимизации по
скорости  при  работе с процессором 8086 или 8088 (самые "медлен-
ные" процессоры серии), когда на адресацию типа "база плюс смеще-
ние"  затрачивается минимум 8 циклов за обращение.  Это позволяет
также сэкономить место,  так как инструкция POP  занимает  только
один байт.

           Примечание: Если вы используете директивы .MODEL, PROC
      и ARG, то Ассемблер автоматически добавляет во все инструк-
      ции RET число байт извлекаемых параметров.




TASM2 #2-5/Док              = 244 =

                          Доступ к параметрам
-----------------------------------------------------------------

     Когда получает управление ваша подпрограмма на Турбо Ассемб-
лере, вершина стека будет содержать адрес возврата (два или четы-
ре слова, в зависимости от того, является ли подпрограмма ближней
или дальней),  а далее будут  находится  передаваемые  параметры.
(Примечание: При вычислении адресов параметров нужно принимать во
внимание регистры,  такие как BP,  содержимое которых также может
быть занесено в стек.)

     Существует три основных метода доступа к параметрам, переда-
ваемых Турбо Паскалем вашей подпрограмме на Турбо  Ассемблере. Вы
можете:

     - использовать для адресации к стеку регистр BP;
     - для получения параметров использовать другой  базовый  или
       индексный регистр;
     - извлечь из стека адрес возврата, а затем параметры.

     Первый и второй методы более сложны, и мы расскажем о них  в
следующих  двух разделах. Третий метод предусматривает извлечение
из стека и сохранение адреса возврата, а затем  извлечения  пара-
метров  и  записи их в регистры. Лучше всего этот метод работает,
когда ваша подпрограмма не требует пространства для локальных пе-
ременных.




TASM2 #2-5/Док              = 245 =

            Использование для адресации к стеку регистра BP
-----------------------------------------------------------------

     Первый и наиболее часто используемый метод доступа  к  пара-
метрам, передаваемым из Турбо Паскаля в Турбо Ассемблер, заключа-
ется в том, чтобы использовать для адресации к стеку регистр  BP.
Например:

CODE       SEGMENT
           ASSUME  CS:CODE
MyProc     PROC    FAR         ; procedure MyProc(i,j : integer);
           PUBLIC  MyProc
j          EQU WORD PTR [bp+6] ; j находится над сохраненным BP
                               ; и адресом возврата
i          EQU WORD PTR [bp+8] ; i располагается над j
           push  bp            ; нужно сохранить BP вызывающей
                               ; программы
           mov   bp,sp         ; BP теперь указывает на вершину
                               ; стека
           mov   ax,i          ; адресуемся к i через BP
           .
           .
           .

     При вычислении смешений в стеке параметров, к которым мы об-
ращаемся  таким  образом, нужно помнить, что 2 байта используются
для сохраненного регистра BP.

     Обратите внимание на использование в данном примере  прирав-
ниваний.  Они  позволяют  сделать программу более понятной. У них
есть только один недостаток: поскольку для выполнения такого рода
приравниваний можно использовать только директиву EQU (а не =), в
данной исходном файле Турбо Ассемблера вы не сможете  переопреде-
лить идентификаторы i и j. Один из способов обойти это заключает-
ся в том, чтобы использовать более описательные имена параметров,
чтобы  они  не повторялись, либо можно ассемблировать каждую под-
программу Ассемблера отдельно.




TASM2 #2-5/Док              = 246 =

                             Директива ARG
-----------------------------------------------------------------

     Хотя можно обращаться к параметрам через регистр  BP,  Турбо
Ассемблер предусматривает альтернативу вычислению смещений в сте-
ке и выполнению текстовых присваиваний. Это  директива  ARG.  При
использовании  ее в процедуре директива ARG автоматически опреде-
ляет смещения параметров относительно регистра BP.  Она вычисляет
также размер блока параметров и использует его в  инструкции RET.
Поскольку идентификаторы, создаваемые по директиве ARG, определе-
ны только в соответствующей процедуре,  в  каждой  процедуре  или
функции  вам  не требуется использовать уникальные имена парамет-
ров.

     Покажем, как будет  выглядеть  пример  предыдущего  раздела,
если переписать его, используя директиву ARG:

CODE       SEGMENT
           ASSUME  CS:CODE
MyProc     PROC    FAR         ; procedure MyProc(i,j : integer);
                               ; external;
           PUBLIC  MyProc
           ARG j : WORD, i : WORD = RetBytes
           push  bp            ; нужно сохранить BP вызывающей
                               ; программы
           mov   bp,sp         ; BP теперь указывает на вершину
                               ; стека
           mov   ax,i          ; адресуемся к i через BP
           .
           .
           .

     Директива ARG Турбо Ассемблера создает локальные идентифика-
торы для параметров i и j. На время выполнения процедуры строка:

        ARG j : WORD, i : WORD = RetBytes

автоматически приравнивает идентификатор i  к  [WORD  PTR  BP+6],
идентификатор  j  к [WORD PTR BP+8], а идентификатор RetBytes - к
числу 4 (размеру в байтах блока параметров). В значениях учитыва-
ется  и  занесенное в стек значение BP, и размер адреса возврата:
если бы процедура MyProc имела ближний тип, то i было бы  прирав-
нено  к  значению  [BP+4], j - к [BP+6], а RetBytes также было бы
равно 4 (в любом случае процедура MyProc может завершить выполне-
ние с помощью инструкции RET RetBytes).


TASM2 #2-5/Док              = 247 =

     При использовании директивы ARG нужно помнить, что параметры
должны  перечисляться в обратном порядке. Последний параметр про-
цедуры или функции Турбо Паскаля нужно размещать в директиве  ARG
первым и наоборот.

     Относительно использования директивы ARG  с  Турбо  Паскалем
можно сделать еще одно замечание. В  отличие  от  других  языков,
Турбо Паскаль всегда заносит в стек параметр-значение  размером в
байт,  как 16-битовое слово. При этом сообщить Турбо Ассемблеру о
дополнительном байте должны вы. Предположим, например, что вы на-
писали функцию, описание которой в Паскале выглядит следующим об-
разом:

        function MyProc(i, j : char) : string; external;

     Директива ARG для этой функции должна была бы выглядеть так:

        ARG j:BYTE: 2, i:BYTE: 2 = RetBytes  RETURN  result:DWORD

     Здесь : 2 после каждого аргумента необходимо  указывать  для
того,  чтобы  сообщить Ассемблеру, что каждый идентификатор зано-
сится в стек, как массив из 2 байт (где, в данном случае, младший
байт каждой пары содержит полезную информацию).

     В функции, возвращающей строковое значение (как данная функ-
ция),  параметр  RETURNS в директиве ARG позволяет вам определить
переменную, приравненную к тому месту в стеке, которое  указывает
на временный результат функции. Переменная в RETURNS на размер (в
байтах) блока параметров. См. Главу 3 "Справочного  руководства",
где о директиве ARG рассказывается более подробно.




TASM2 #2-5/Док              = 248 =

                   Турбо Паскаль и директива .MODEL
-----------------------------------------------------------------

     Директива .MODEL с параметром TPASCAL задает упрощенную сег-
ментацию,  модель памяти и языковую поддержку. Ранее мы уже виде-
ли, что нужно сделать в программах Ассемблера, чтобы  можно  было
использовать процедуры и функции Паскаля. Преобразуем пример, ис-
пользуя в нем директивы .MODEL и PROC:

               .MODEL   TPASCAL
               .CODE
MyProc         PROC     FAR i:BYTE,j:BYTE result:DWORD
               PUBLIC   MyProc
               mov      ax,i
               .
               .
               .
               ret

     Заметим, что теперь не нужно задавать параметры  в  обратном
порядке.  Не требуется также масса других операторов. Использова-
ние в директиве .MODEL ключевого слова TPASCAL задает использова-
ние  соглашений  Паскаля,  определяет  имена сегментов, выполняет
инструкции PUSH BP и MOV BP,SP и задает также возврат  с  помощью
инструкций POP BP и RET N (где N - число байт параметров).

        Использование другого базового или индексного регистра
-----------------------------------------------------------------

     Второй способ доступа к параметрам состоит  в  использовании
для получения этих параметров другого базового или индексного ре-
гистра (BX, SI или DI). Нужно однако помнить,  что  по  умолчанию
сегментным  регистром для них является регистр  DS, а не SS. Поэ-
тому для их использования вам придется применять префикс  переоп-
ределения сегмента.

     Приведем пример использования для получения  параметров  ре-
гистра BX:

CODE          SEGMENT
              ASSUME  CS:CODE
MyProc        PROC FAR         ; procedure MyProc(i,j : integer);
              PUBLIC MyProc
j          EQU WORD PTR SS:[BX+4] ; j находится над сохраненным
                               ; BP и адресом возврата

TASM2 #2-5/Док              = 249 =

i          EQU WORD PTR SS:[bp+8] ; i располагается над j
           mov   bx,sp         ; BX теперь указывает на вершину
                               ; стека
           mov   ax,i          ; адресуемся к i через BX
           .
           .
           .

     В тех программах, где нет большого числа ссылок на  парамет-
ры, такой метод позволяет сэкономить время и место. Почему? Пото-
му, что в отличие от BP,  регистр BX не требуется восстанавливать
в конце программы.

                  Результаты функции в Турбо Паскале
-----------------------------------------------------------------

     В зависимости от типа результата функции Турбо Паскаля возв-
ращают свои результаты различными способами.

               Результаты функции скалярного типа

     Результаты функции скалярных типов возвращаются в  регистрах
центрального  процессора  (ЦП).  Байтовые значения возвращаются в
регистре AL, значения  размером  в  2  байта  -  в  регистре  AX,
4-байтовые значения - в паре регистров DX:AX (старшее слово нахо-
дится в регистре DX).

              Результаты функции вещественного типа

     Результаты используемого в Турбо Паскале  6-байтового  прог-
раммно  эмулируемого  вещественного  типа возвращаются в трех ре-
гистрах ЦП. Наиболее значащее (старшее) слово возвращается в  DX,
среднее - в BX, а наименее значащее - в AX.

            Результаты функции типов сопроцессора 8087

     Результаты типов, использующихся сопроцессором 8087, возвра-
щаются в регистре вершины стека ST(0) (или просто ST).

               Результаты функции строкового типа

     Результаты строкового типа возвращаются во временной рабочей
области, выделяемой Турбо Паскалем перед вызовом. Указатель даль-
него типа на эту область заносится в стек перед занесением перво-
го  параметра.  Заметим,  что  этот  указатель не является частью

TASM2 #2-5/Док              = 250 =

списка параметров.

           Примечание: Не удаляйте из стека полученный в  резуль-
      тате  указатель,  так  как Турбо Паскаль ожидает, что после
      вызова он будет доступен.

                Результаты функции типа указатель

     Результаты указатель возвращаются  в  паре  регистров  DX:AX
(сегмент:смещение).


Выделение пространства для локальных данных
-----------------------------------------------------------------

     Ваши программы, написанные на Турбо Ассемблере, могут  выде-
лять  пространство для своих собственных переменных, как постоян-
ных (статических), то есть сохраняющихся в промежутке между вызо-
вами,  так и для временных (которые после вызова будут потеряны).
Оба этих случая обсуждаются в следующих разделах.

              Выделение общедоступной статической памяти
-----------------------------------------------------------------

     Турбо Паскаль позволяет в программах Турбо Ассемблера резер-
вировать пространство для статических переменных в сегментах гло-
бальных данных (DATA или DSEG). Чтобы выделить это  пространство,
можно просто использовать такие директивы, как DB, DW и т.д. Нап-
ример:

   DATA        SEGMENT PUBLIc
   MyInt       DW      ?        ; зарезервировать слово
   MyByte      DB      ?        ; зарезервировать байт
     .
     .
     .
   DATA        ENDS

     Переменных, выделяемых Турбо Ассемблером в сегменте глобаль-
ных данных, касаются два важных ограничения. Во-первых, эти пере-
менными являются "местными", они недоступны программе Турбо  Пас-
каля (хотя вы можете передавать указатели на них). Во-вторых, они
не могут быть предварительно инициализированы,  как  типизованные
константы. Оператор:


TASM2 #2-5/Док              = 251 =

   MyInt       DW      42        ; это не инициализирует
                                 ; MyInt значением 42

не вызовет ошибки при компоновке модуля с программой Турбо Паска-
ля, однако MyInt при выполнении программы не будет иметь значение
42.

     Эти ограничения можно обойти, описав переменные или  типизо-
ванные  константы  Турбо  Паскаля  с помощью директивы EXTRN, что
сделает их доступными Турбо Ассемблеру.




TASM2 #2-5/Док              = 252 =

                      Выделение временной памяти
-----------------------------------------------------------------

     В ваших программах на Турбо  Паскале  можно  выделять  также
временную память (локальные переменные) в стеке на время выполне-
ния каждого вызова. Перед возвратом управления эта память  должна
быть освобождена, а значение регистра BP восстановлено. В следую-
щем примере процедура MyProc резервирует  пространство  для  двух
целых переменных a и b:

CODE          SEGMENT
              ASSUME  CS:CODE   ; procedure MyProc(i : integer);
MyProc        PROC    FAR
              PUBLIC  MyProc
              LOCAL a : WORD, b : WORD = LocalSpace ; a в [bp-2]
                                ; b - в [bp-4]
i             equ   word ptr [bp+6] ; параметр i находится над
                                ; сохраненным BP и адресом
                                ; возврата
              push  bp          ; нужно сохранить BP вызывающей
                                ; программы
              mov   bp,sp       ; теперь BP указывает на
                                ; вершину стека
              sub   sp,LocalSpace ; зарезервировать пространст-
                                ; во для двух слов
              mov   ax,42       ; загрузить в AX начальное
                                ; значение A
              mov   a,ax        ; и в A
              xor   ax,ax       ; очистить регистр AX
              mov   b,ax        ; инициализировать B нулем
              mov   b,ax        ; выполнить нужные действия
              .
              .
              .
              mov   sp,bp       ; восстановить исходное
                                ; значение SP
              mov   bp          ; восстановить исходное
                                ; значение регистра BP
              ret   2
MyProc        ENDP
CODE          ENDS
              END

     Заметим, что директива Турбо Ассемблера  LOCAL  используется
для создания идентификаторов и выделения пространства для локаль-
ных переменных. Оператор:

TASM2 #2-5/Док              = 253 =


        LOCAL a : WORD, b : WORD = LocalSpace

на время выполнения процедуры присваивает идентификатору a значе-
ние  [BP-2], идентификатору b - значение [BP-4], а идентификатору
LocalSpace - число 4 (размер области локальных переменных).  Пос-
кольку  нет соответствующего оператора для создания идентификато-
ров, ссылающихся на параметры, вы должны использовать  присваива-
ние i значения [BP+6].

     Более разумный  способ  инициализации  локальных  переменных
заключается  в  том, чтобы вместо уменьшения SP занести в стек их
значения. Таким образом, вы  должны  заменить  SUB  SP,LocalSpace
инструкциями:

          mov   ax,42           ; получить начальное значение
                                ; для a
          push  ax              ; занести его в a
          xor   ax,ax           ; обнулить AX
          push  ax              ; и занести 0 в b

     Если вы используете этот способ, нужно внимательно  отслежи-
вать  стек!  Не  следует  ссылаться на идентификаторы a и b перед
тем, как они занесены в стек.

     Другой вид оптимизации предусматривает  использование  инст-
рукции PUSH CONST для инициализации локальных переменных (ее мож-
но использовать при наличии процессором 80186,  80286  и  80386),
или  сохранение  BP  в регистре вместо занесения его в стек (если
есть неиспользованные регистры).


Примеры подпрограмм на Ассемблере для Турбо Паскаля
-----------------------------------------------------------------

     В данном разделе вы дадим некоторые примеры  подпрограмм  на
языке  Ассемблера,  которые  вы можете вызывать из программ Турбо
Паскаля.

   Подпрограмма шестнадцатиричного преобразования общего назначения
-----------------------------------------------------------------

     Содержащиеся в параметре num байты  преобразуются  в  строку
шестнадцатиричных  цифр  длины  (byteCount * 2). Поскольку каждый
байт порождает два символа, максимальное значение byteCount равно

TASM2 #2-5/Док              = 254 =

127  (не проверяется). Для преобразования каждой группы (по 4 би-
та) в шестнадцатиричную цифру мы для скорости используем последо-
вательность add-daa-adc-daa.

     Процедура HexStr написана так, что вызываться она  должна  с
помощью вызова дальнего типа. Это означает, что ее следует описы-
вать в интерфейсной части модуля Турбо Паскаля или с помощью  ди-
рективы компилятора {$F+}.

CODE           SEGMENT
               ASSUME  cs:CODE,ds:NOTHING

; Параметры (+2 с учетом push bp)

byteCount      equ byte ptr  ss:[bp+6]
num            equ dword ptr ss:[bp+8]

; Адресация к результату функции (+2 с учетом push bp)

resultPtr      equ dword ptr ss:[bp+12]

HexStr         PROC FAR
               PUBLIC HexStr

               push  bp
               mov   bp,sp            ; получить указатель
                                      ; стека
               les   di,resultPtr     ; получить адрес
                                      ; результата функции
               mov   dx,ds            ; сохранить DS Турбо
                                      ; Паскаля в DX
               lds   si,sum           ; получить адрес числа
               mov   al,byteCount     ; сколько байт?
               xor   ah,ah            ; слово
               mov   cx,ax            ; отслеживать число
                                      ; байт в CX
               add   si,ax            ; начать со старшего
                                      ; байта числа
               dec   si
               shl   ax,1             ; сколько цифр?
                                      ; (2/байт)
               cld                    ; сохранить число цифр
                                      ; (работать в прямом
                                      ; направлении)
               stosb                  ; в приемнике - байт
                                      ; длины строки

TASM2 #2-5/Док              = 255 =

NextLoop:
               std                    ; сканировать число от
                                      ; старшего байта к
                                      ; младшему
               lodsb                  ; получить следующий
                                      ; байт
               mov   ah,al            ; сохранить его
               shr   al,1             ; выделить старшую
                                      ; группу бит
               shr   al,1
               shr   al,1
               shr   al,1
               add   al,90h           ; специальная после-
                                      ; довательность шестнадца-
                                      ; тиричного преобразования
               daa                    ; использование инструкций
                                      ; ADD и DAA
               adc   al,40h
               daa                    ; группа преобразована
                                      ; в код ASCII
               cld                    ; сохраним ASCII и следуем
                                      ; далее
               stosb
               mov   al,ah            ; повторить преобразование
                                      ; для младшей группы
               and   al,0Fh
               add   al,90h
               daa
               adc   al,40h
               daa
               stosb
               loop  HexLoop          ; продолжать, пока не
                                      ; будет выполнено
               mov   ds,dx
               pop   bp
               ret   6                ; параметры занимают
                                      ; 6 байт
HexStr         ENDP
CODE           ENDS
               END

     Пример  программы  на  Паскале,  где  используется   функция
HexStr, имеет следующий вид:

Program HexTest;
var

TASM2 #2-5/Док              = 256 =

  num : word;
{$F+}

function HexStr (var num; byteCount : byte) : string; external;

{$L HEXSTR.OBJ}

{$F-}
begin
  num := word;
  Writeln('Преобразованная строка имеет шестнадцатиричное
представление: ', HexStr(num,Sizeof(num)),'*');
end.

     Для построения и запуска  примеров  программы на  Паскале  и
программы  Ассемблера  используйте  следующие  команды командного
файла:

     TASM HEXSTR
     TPC HEXTEST
     HEXTEST

     Если вы используете директиву .MODEL,  то  программу  HexStr
можно записать следующим образом:

               .MODEL   TPASCAL
               .CODE
HexStr PROC FAR num:DWORD,byteCount:BYTE RETURNS resultPtr:DWORD
               PUBLIC HexStr
               les   di,resultPtr     ; получить адрес
                                      ; результата функции
               mov   dx,ds            ; сохранить DS Турбо
                                      ; Паскаля в DX
               lds   si,sum           ; получить адрес числа
               mov   al,byteCount     ; сколько байт?
               xor   ah,ah            ; слово
               mov   cx,ax            ; отслеживать число
                                      ; байт в CX
               add   si,ax            ; начать со старшего
                                      ; байта числа
               dec   si
               shl   ax,1             ; сколько цифр?
                                      ; (2/байт)
               cld                    ; сохранить число цифр
                                      ; (работать в прямом
                                      ; направлении)

TASM2 #2-5/Док              = 257 =

               stosb                  ; в приемнике - байт
                                      ; длины строки
NextLoop:
               std                    ; сканировать число от
                                      ; старшего байта к
                                      ; младшему
               lodsb                  ; получить следующий
                                      ; байт
               mov   ah,al            ; сохранить его
               shr   al,1             ; выделить старшую
                                      ; группу бит
               shr   al,1
               shr   al,1
               shr   al,1
               add   al,90h           ; специальная после-
                                      ; довательность шестнадца-
                                      ; тиричного преобразования
               daa                    ; использование инструкций
                                      ; ADD и DAA
               adc   al,40h
               daa                    ; группа преобразована
                                      ; в код ASCII
               cld                    ; сохраним ASCII и следуем
                                      ; далее
               stosb
               mov   al,ah            ; повторить преобразование
                                      ; для младшей группы
               and   al,0Fh
               add   al,90h
               daa
               adc   al,40h
               daa
               stosb
               loop  HexLoop          ; продолжать, пока не
                                      ; будет выполнено
               mov   ds,dx            ; восстановить DS
                                      ; Турбо Паскаля
               ret
HexStr         ENDP
CODE           ENDS
               END

     При этом вы можете использовать ту же программу на Паскале и
просто  ассемблировать альтернативный вариант HexStr и перекомпи-
лировать программу с помощью того же командного файла.


TASM2 #2-5/Док              = 258 =

               Пример обмена содержимого двух переменных
-----------------------------------------------------------------

     С помощью данной процедуры вы можете выполнить обмен  содер-
жимого  двух  переменных размера count. Если count имеет значение
0, то то процессор попытается перекопировать 64К.

CODE        SEGMENT
            ASSUME cs:CODE,ds:NOTHING

; Параметры (заметим, что из-за push bp смещение
; увеличивается на 2)

var1        equ    DWORD PTR ss:[bp+12]
var2        equ    DWORD PTR ss:[bp+8]
count       equ    WORD  PTR ss:[bp+6]

Exchange    PROC FAR
            PUBLIC Exchange
            cld                   ; обмен в прямом направлении
            mov    dx,ds          ; сохранить регистр DS
            push   bp
            mov    bp,sp          ; получить базу стека
            lds    si,var1        ; получить первый адрес
            les    di,var2        ; получить второй адрес
            mov    cx,count       ; получить число перемещаемых
                                  ; байт
            shr    cx,1           ; получить счетчик слов
                                  ; (младший бит -> перенос)
            jnc    ExchangeWord   ; если не нечетный байт,
                                  ; войти в цикл
            mov    al,es:[di]     ; считать нечетный байт
                                  ; из var2
            movsb                 ; переместить байт из var1
                                  ; в var2
            mov    [si-1],al      ; записать var2 в var1
            jz     Finis          ; выполнено, если нужно
                                  ; выполнить обмен только
                                  ; одного байта
ExchangeWords:
            mov    bx,-2          ; BX - это удобное место
                                  ; для хранения -2
ExchangeLoop:
            mov    ax,es:[di]     ; считать слово из var2
            movsw                 ; переместить из var1
                                  ; в var2

TASM2 #2-5/Док              = 259 =

            mov    [bx][si,ax     ; записать слово var2 в
                                  ; var1
            loop   ExchangeLoop   ; повторить count/2 раз
Finis:
            mov    ds,dx          ; получить обратно DS
                                  ; Турбо Паскаля
            pop    bp
            ret    10
Exchange    ENDP
CODE        ENDS
            END

     Программа  Турбо   Паскаля,   которая   использует   функцию
Exchange, имеет вид:

program TextExchange;

type
  EmployeeRecord = record
                     Name     :    string[30];
                     Address  :    string[30];
                     City     :    string[15];
                     State    :    string[2];
                     Zip      :    string[10];
                    end;

var
  OldEmployee, NewEmployee : EmployeeRecord;

{$F+}

procedure Exchange(var var1,var2; count : word); external;
{$L XCHANGE.OBJ}
{$F-}
begin
  with OldEmployee do
  begin
    Name := 'John Smith';
    Address := ' 123 F Street';
    City := 'Scotts Valley';
    State := 'CA';
    Zip := ' 90000-0000';
  end;
  with NewEmployee do
  begin
    Name := 'Mary Jones';

TASM2 #2-5/Док              = 260 =

    Address := ' 9471 41st Avenue';
    City := 'New York';
    State := 'NY';
    Zip := ' 10000-1111';
  end;
  Writeln('Before: ',OldEmployee.Name,' ',NewEmployee.Name);
  Exchange(OldEmployee,NewEmployee,sizeof(OldEmployee));
  Writeln('After: ',OldEmployeeName,' ',NewEmployee.Name);
  Exchange(OldEmployee,NewEmployee,sizeof(OldEmployee));
  Writeln('After: ',OldEmployeeName,' ',NewEmployee.Name);
end.

     Чтобы сформировать и запустить данные программы на Паскале и
Ассемблере, используйте следующие команды командного файла:

        TASM XCHANGE
        TPC  XCHANGE
        XCHANGE

     Если использовать директиву .MODEL, то программа Exchange на
Ассемблере будет выглядеть следующим образом:

            .MODEL  TPASCAL
            .CODE
Exchange    PROC FAR var1:DWORD,var2:DWORD,count:WORD
            PUBLIC Exchange
            cld                  ; обмен в прямом направлении
            mov    dx,ds         ; сохранить DS
            push   bp
            mov    bp,sp         ; получить базу стека
            lds    si,var1       ; получить первый адрес
            les    di,var2       ; получить второй адрес
            mov    cx,count      ; получить число перемещаемых
                                 ; байт
            shr    cx,1          ; получить счетчик слов
                                 ; (младший бит -> перенос)
            jnc    ExchangeWord  ; если не нечетный байт,
                                 ; войти в цикл
            mov    al,es:[di]    ; считать нечетный байт
                                 ; из var2
            movsb                ; переместить байт из var1
                                 ; в var2
            mov    [si-1],al     ; записать var2 в var1
            jz     Finis         ; выполнено, если нужно
                                 ; выполнить обмен только
                                 ; одного байта

TASM2 #2-5/Док              = 261 =

ExchangeWords:
            mov    bx,-2         ; BX - это удобное место
                                 ; для хранения -2
ExchangeLoop:
            mov    ax,es:[di]    ; считать слово из var2
            movsw                ; переместить из var1
                                 ; в var2
            mov    [bx][si,ax    ; записать слово var2 в
                                 ; var1
            loop   ExchangeLoop  ; повторить count/2 раз
Finis:
            mov    ds,dx         ; получить обратно DS
                                 ; Турбо Паскаля
            ret
Exchage     ENDP
CODE        ENDS
            END

     Вы можете использовать ту же программу на Паскале  и  просто
ассемблировать  альтернативный вариант процедуры Exchаnge и пере-
компилировать программу с помощью того же командного файла.




TASM2 #2-5/Док              = 262 =

                 Пример анализа операционной среды DOS
-----------------------------------------------------------------

     С помощью функции EnvString вы сможете просмотреть  операци-
онную среду DOS и найти строку вида "s=НЕЧТО" и возвратить НЕЧТО,
если это найдено.

DATA           SEGMENT PUBLIC
               EXTRN prefixSeg : Word  ; дает адрес PSP
DATA           ENDS
               SEGMENT PUBLIC
               ASSUME  cs:CODE,ds:DATA

EnvString      PROC FAR
               PUBLIC  EnvString
               push    bp
               cld                     ; работать в прямом
                                       ; направлении
               mov     es,[prefixSeg]  ; посмотреть PSP
               mov     es,es:[2Ch]     ; ES:DI указывают на
                                       ; операционную среду,
               xor     di,di           ; которая выровнена на
                                       ; границу параграфа
               mov     bp,sp           ; найти строку параметров,
               lds     si,ss:[bp+6]    ; которая следует за
                                       ; адресом возврата
               ASSUME  ds:NOTHING
               lodsb                   ; посмотреть длину
               or      al,al           ; она равна 0?
               jz      RetNul          ; да, возврат
               mov     ah,al           ; в противном случае
                                       ; сохранить ее в AH
               mov     dx,si           ; DS:SI содержат указатель
                                       ; на первый параметр
                                       ; char
               xor     al,al           ; сделать его равным 0
Compare:
               mov     ch,al           ; мы хотим, чтобы для
                                       ; следующего отсчета ch=0
               mov     si,dx           ; возвратить указатель на
                                       ; просмотренную строку
               mov     cl,ah           ; получить длину
               mov     si,dx           ; возвратить указатель на
                                       ; строку
               repe    cmpsb           ; сравнить байты
               jne     Skip            ; если сравнение неудач-

TASM2 #2-5/Док              = 263 =

                                       ; ное попробовать следу-
                                       ; ющую строку
               cmp     byte ptr es:[di],'=' ; сравнение
                                       ; завершилось успешно
                                       ; следующий символ '='?
               jne     NoEqual         ; если нет, все еще нет
                                       ; совпадения
Found:
               mov     ax,es           ; DI:SI будет указывать
                                       ; на найденную нами строку
               mov     ds,ax
               mov     si,di
               inc     si              ; "пройти" символ '='
               les     bx,ss:[bp+10]   ; получить адрес
                                       ; результата
                                       ; функции
               mov     di,bx           ; занести его в ES:DI
               inc     di              ; байт длины
               mov     cl,255          ; задать максимальную
                                       ; длину
CopyLoop:
               lodsb                   ; получить байт
               or      al,al           ; проверить на 0
               jz      Done            ; если 0, выполнено
               stosb                   ; занести его в результат
               loop    CopyLoop        ; переместить до 255
                                       ; байт
Done:          not     cl              ; при сохранении мы
                                       ; уменьшали от CL до 255
               mov     es:[bx],cl      ; сохранить длину
               mov     ax,SEG DATE
               mov     ds,ax           ; восстановить DS
               ASSUME  ds:DATA
               pop     bp
               ret     4
               ASSUME  ds:NOTHING
Skip:
               dec     di              ; проверить на 0
NoEqual:
               mov     cx,7FFFh        ; длинный поиск, если
                                       ; нужно
               sub     cx,di           ; операционная среда
                                       ; никогда не превышает
                                       ; 32К
               jbe     RetNul          ; если конец, выйти
               repne   scasb           ; посмотреть следующий

TASM2 #2-5/Док              = 264 =

                                       ; 0
               jcxz    RetNul          ; выйти, если не найден
               cmp     byte ptr es:[di],al ; второй 0 в строке?
               jne     Compare         ; если нет, попытаться
                                       ; снова
RetNul:
               les     di,ss:[bp+10]   ; получить адрес
                                       ; результата
               stosb                   ; сохранить там 0
               mov     ax,SEG DATA
               mov     ds,ax           ; восстановить DS
               ASSUME  ds:DATA
               pop     bp
               ret     4
EnvString      ENDP
CODE           ENDS
               END

     Программа на Паскале, которая использует функцию  EnvString,
выглядит следующим образом:

program EnvTest;
{ программа ищет строки операционной среды }

var
  EnvVariable : string;
  EnvValue    : string;

{$F+}

function EnvString(s:string) : string; external;
{$L ENVSTRING.OBJ}
{$F-}
begin
  EnvVariable := 'PROMPT';
  EnvValue := EnvString(EnvVariable);
  if EnvValue = '' then EnvValue := '*** не найдена ***';
  Writeln('Переменная операционной среды: ',
           EnvVariable,' Значение: ',EnvValue);
end.

     Чтобы сформировать и запустить данные программы на Паскале и
Ассемблере, используйте следующие команды командного файла:

     TASM ENVSTR
     TPC ENVTEST

TASM2 #2-5/Док              = 265 =

     ENVTEST

     Если использовать директиву .MODEL, то функцию EnvString  на
Ассемблере будет выглядеть следующим образом:

               .MODEL   TPASCAL
               .DATA
               EXTRN prefixSeg : Word  ; дает адрес PSP
               .CODE
EnvString      PROC FAR  EnvVar:DWORD  RETURNS EnvVal:DWORD
               PUBLIC  EnvString
               push    bp
               cld                     ; работать в прямом
                                       ; направлении
               mov     es,[prefixSeg]  ; посмотреть PSP
               mov     es,es:[2Ch]     ; ES:DI указывают на
                                       ; операционную среду,
               xor     di,di           ; которая выровнена на
                                       ; границу параграфа
               mov     bp,sp           ; найти строку параметров,
               lds     si,ss:[bp+6]    ; которая следует за
                                       ; адресом возврата
               ASSUME  ds:NOTHING
               lodsb                   ; посмотреть длину
               or      al,al           ; она равна 0?
               jz      RetNul          ; да, возврат
               mov     ah,al           ; в противном случае
                                       ; сохранить ее в AH
               mov     dx,si           ; DS:SI содержат указатель
                                       ; на первый параметр
                                       ; char
               xor     al,al           ; сделать его равным 0
Compare:
               mov     ch,al           ; мы хотим, чтобы для
                                       ; следующего отсчета ch=0
               mov     si,dx           ; возвратить указатель на
                                       ; просмотренную строку
               mov     cl,ah           ; получить длину
               mov     si,dx           ; возвратить указатель на
                                       ; строку
               repe    cmpsb           ; сравнить байты
               jne     Skip            ; если сравнение неудач-
                                       ; ное, попробовать следу-
                                       ; ющую строку
               cmp     byte ptr es:[di],'=' ; сравнение
                                       ; завершилось успешно

TASM2 #2-5/Док              = 266 =

                                       ; следующий символ '='?
               jne     NoEqual         ; если нет, все еще нет
                                       ; совпадения
Found:
               mov     ax,es           ; DI:SI будет указывать
                                       ; на найденную нами строку
               mov     ds,ax
               mov     si,di
               inc     si              ; "пройти" символ '='
               les     bx,ss:[bp+10]   ; получить адрес
                                       ; результата функции
               mov     di,bx           ; занести его в ES:DI
               inc     di              ; байт длины
               mov     cl,255          ; задать максимальную
                                       ; длину
CopyLoop:
               lodsb                   ; получить байт
               or      al,al           ; проверить на 0
               jz      Done            ; если 0, выполнено
               stosb                   ; занести его в результат
               loop    CopyLoop        ; переместить до 255
                                       ; байт
Done:          not     cl              ; при сохранении мы
                                       ; уменьшали от CL до 255
               mov     es:[bx],cl      ; сохранить длину
               mov     ax,SEG DATE
               mov     ds,ax           ; восстановить DS
               ASSUME  ds:DATA
               pop     bp
               ret     4
               ASSUME  ds:NOTHING
Skip:
               dec     di              ; проверять на 0
NoEqual:
               mov     cx,7FFFh        ; длинный поиск, если
                                       ; нужно
               sub     cx,di           ; операционная среда
                                       ; никогда не превышает
                                       ; 32К
               jbe     RetNul          ; если конец, выйти
               repne   scasb           ; посмотреть следующий
                                       ; 0
               jcxz    RetNul          ; выйти, если не найден
               cmp     byte ptr es:[di],al ; второй 0 в строке?
               jne     Compare         ; если нет, попытаться
                                       ; снова

TASM2 #2-5/Док              = 267 =

RetNul:
               les     di,ss:[bp+10]   ; получить адрес
                                       ; результата
               stosb                   ; сохранить там 0
               mov     ax,SEG DATA
               mov     ds,ax           ; восстановить DS
               ASSUME  ds:DATA
               ret     4
EnvString      ENDP
CODE           ENDS
               END

     Вы можете использовать ту же программу на Паскале  и  просто
ассемблировать  альтернативный  вариант функции EnvString и пере-
компилировать программу с помощью того же командного файла.



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