Turbo Assembler 3.0. Руководство пользователя

         

Включение одной именованной структуры в другую


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

Приведем синтаксис режима Ideal:

STRUC имя_структуры параметры_заполнения

В режиме MASM вы можете использовать следующий синтаксис:

имя_структуры STRUC параметры_заполнение

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

{ [имя_элемента [=выражение] [,имя_элемента [=выражение].]] }

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


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

Обычно, когда вы создаете экземпляр структуры, нужно обеспе- чить, что инициализированные данные содержал только один элемент объединения (подробности см. в Главе 12). Поскольку при встраива- нии структуры создание экземпляра не предусматривается, это огра- ничение здесь не применяется. Инициализированные данные могут со- держать несколько элементов включаемого объединения, например:

FOO STRUC ABC DW 1 DEF DW 2 UNION A1 DW '123' A2 DW ? ENDS ENDS

FOO2 STRUC FOO STRUC {A1=2} ; встроить структуру FOO в структуру ; FOO2 с переопределением ; заметим, что по умолчанию в FOO2 ; инициализируется и A1, и A2 GHI DB 3 ENDS

Определение структуры FOO2 в предыдущем примере эквивалентно следующей вложенной структуре/объединению:

FOO STRUC STRUC ; начало вложенной структуры. ABC DW 1 DEF DW 2 UNION ; начало вложенного объединения. A1 DW '123' A2 DW ? ENDS ; конец вложенного объединения. ENDS ; конец вложенной структуры. GHI DB 3 ENDS

Заметим, что при создании экземпляра FOO2 нужно убедиться, что инициализируется только одно значение в объединении.


Включение расширения макрокоманды в файл листинга


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



- .LALL

- .SALL

- .XALL

- %MACS

- %NOMACS

которые позволяют управлять листингом макрорасширений.

Примечание: Подробнее об этих директивах рассказывает- ся в Главе 17.

Назад | Содержание | Вперед



Включение условных директив в файл листинга


Обычно код условного ассемблирования с ложным условием (false) в листинг не включается. Используя директивы Турбо Ас- семблера и параметры командной строки, вы можете это переопреде- лить.

Примечание: Более подробная информация содержится в Главе 2 и Главе 17.

Назад | Содержание | Вперед



Вложенные процедуры и правила области действия


Хотя вы можете вкладывать одну процедуру в другую, все про- цедуры имеют глобальную область действия, Например:

. . . test1 PROC FAR ; код процедуры CALL test2 ; код процедуры RET test2 PROC NEAR ; код процедуры RET ; ближний возврат test2 ENDP test1 ENDP . . .

В данном примере вне охватывающей процедуры можно вызывать test1 и test2.

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

. . . LOCALS test1 PROC FAR ; код процедуры RET @test2 PROC NEAR ; код процедуры RET @test2 ENDP test1 ENDP . . .

Примечание: Директива LOCALS разрешает идентификаторы с локальной областью действия. Подробнее об этом рассказы- вается в Главе 11.

В данном коде в процедуре test1 вы можете обратиться только к процедуре @test2. Фактически, если они не находятся в одной и той же процедуре может существовать несколько процедур с именем @test2. Например, допустимо следующее:

. . . LOCALS test1 PROC FAR MOV si, OFFSET Buffer CALL @test2 RET @test2 PROC NEAR ; некоторый код RET @test2 ENDP

test2 PROC FAR MOV si,OFFSET Buffer2 CALL @test2 RET @test2 PROC NEAR ; некоторый код RET @test2 ENDP test2 ENDP . . .

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

. . . lOCALS test1 PROC FAR MOV si,OFFSET Buffer CALL @test2 RET test1 ENDP

@test2 PROC NEAR ; код процедуры RET @test2 ENDP . . .

так как вызов @test2 задает локальный идентификатор для процедуры test1, а таких идентификаторов не существует.



Вложенные структуры и объединения


Турбо Ассемблер позволяется для управления смещениями, прис- ваевыми элементам структуры, использовать вложенные директивы STRUC, UNION и ENDS внутри открытого определения данных типа структуры или объединения.

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

Директивы STRUC, UNION и ENDS Таблица 8.1 -----------------T----------------------------------------------¬ ¦ Директива ¦ Смысл ¦ +----------------+----------------------------------------------+ ¦ STRUC ¦ При использовании внутри открытой структуры ¦ ¦ ¦ или объединения данная директива начинает ¦ ¦ ¦ блок элементов, который внешняя (охватываю- ¦ ¦ ¦ щая) структура или объединение рассматрива- ¦ ¦ ¦ ет, как один элемент. Смещения элементам в ¦ ¦ ¦ блоке присваиваются в возрастающем порядке. ¦ ¦ ¦ Размер блока представляет собой сумму разме- ¦ ¦ ¦ ров всех элементов в нем. ¦ ¦ ¦ ¦ +----------------+----------------------------------------------+ ¦ UNION ¦ При использовании внутри открытой структуры ¦ ¦ ¦ или объединения данная директива начинает ¦ ¦ ¦ блок элементов, который охватывающая струк- ¦ ¦ ¦ тура или объединение рассматривает как один ¦ ¦ ¦ элемент. Элементам блока присваивается одно ¦ ¦ ¦ и то же смещение. Размер блока равен размеру ¦ ¦ ¦ наибольшего элемента в нем. ¦ ¦ ¦ ¦ +----------------+----------------------------------------------+ ¦ ENDS ¦ Завершает блок элементов, начинающихся пре- ¦ ¦ ¦ дыдущей директивой STRUC или UNION. ¦ ¦ ¦ ¦ L----------------+-----------------------------------------------

Например, в следующем определении данных типа структуры/объ- единения содержится в общей сложности 5 элементов.

CUNION STRUC CTYPE DB ? UNION ; начало объединения ; если CTYPE = 0, использовать следующее. STRUC CT0PAR1 DW 1 CT0PAR2 DW 1 ENDS ; если CTYPE = 1, использовать следующее. STRUC CT1PAR1 DW 3 CT1PAR2 DW 4 ENDS ENDS ; конец объединения ENDS ; конец структурного типа

Список этих элементов приведен в следующей таблице:

Элементы блока Таблица 8.1 ------------T---------T------------T----------------------------¬ ¦Элемент ¦ Тип ¦ Смещение ¦ Значение по умолчанию ¦ +-----------+---------+------------+----------------------------+ ¦ CTYPE ¦ Byte ¦ 0 ¦ ? (не инициализирован) ¦ ¦ CT0PAR1 ¦ Word ¦ 1 ¦ 1 ¦ ¦ CT0PAR2 ¦ Byte ¦ 3 ¦ 2 ¦ ¦ CT1PAR1 ¦ Byte ¦ 1 ¦ 3 ¦ ¦ CT1PAR2 ¦ Dword ¦ 2 ¦ 4 ¦ L-----------+---------+------------+-----------------------------

Длина данной структуры/объединения равна 6 байтам.



Внешние дальние идентификаторы


Если вы используете упрощенные директивы определения сегмен- тов, то описания идентификаторов 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 . . .

SEG FileVariable не будет возвращать корректного значения сегмента. Директива EXTRN размещена в области действия директивы файла FILE2.ASM DATA, поэтому Турбо Ассемблер считает, что пере- менная FileVariable должна находиться в ближнем сегменте DATA файла FILE2.ASM, а не в дальнем сегмента DATA.

В следующем коде FILE2.ASM SEG FileVariable будет возвращать корректное значение сегмента:

. . . .DATA @CurSeg ENDS EXTRN FileVariable:BYTE .CODE Start PROC mov ax,SEG FileVariable mov ds,ax . . .

"Фокус" здесь состоит в том, что директива @CurSeg ENDS за- вершает сегмент .DATA, поэтому, когда переменная FileVariable описывается, как внешняя, никакая сегментная директива не дейс- твует.



Возможности фирмы Borland


Службы технического сопровождения фирмы Borland публикуют техническую информацию по множеству тем. Там вы можете получить ответы на возникшие вопросы.

TechFax (8000-822-4269) - это 24-часовая автоматизированная служба, позволяющая свободно получить техническую информацию по факсу. Вы можете запросить до трех документов за один вызов.

408-439-9096 (модем) - пересылка файлов (File DownLoad BBS, 2500 бод). Через модем вы можете получать образцы файлов, прик- ладных программ и техническую информацию. Специальной установки не требуется.

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

Оперативные информационные услуги ----------------------------------------- Служба Команда ----------------------------------------- CompuServe Go BORLAND BIX JOIN BORLAND GEnie BORLAND -----------------------------------------

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

408-438-5300 - по этому телефону можно обратиться в наш от- дел технического обслуживания по рабочим дням с 6 утра до 5 вече- ра. Звоните пожалуйста по телефону, который находится недалеко от компьютера с работающей программой. Прежде чем позвонить, подго- товьте следующую информацию:

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

2. Марку и модель компьютера, а также любого используемого вами дополнительного аппаратного обеспечения.

3. Название и номер версии операционной системы. (Номер вер- сии можно определить, введя по приглашению DOS команду VER).

4. Содержимое вашего файла AUTOEXEC.BAT и содержимое файла CONFIG.SYS (в корневом каталоге (\) на загрузочном диске компьютера.

5. Телефон, по которому с вами можно связаться в рабочее время.

6. Если звонок связан с какой-то проблемой, какие шаги нужно выполнить, чтобы возникла эта проблема.

По телефону 408-438-5300 с 7 до 17 часов вы можете получить ответы на нетехнические вопросы, включая цены, изменения и т.д. (служба покупателя).

Содержание | Вперед



Возврат значений


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

---------------------------T------------------------------------¬ ¦Тип возвращаемого значения¦ Где находится возвращаемое значение¦ +--------------------------+------------------------------------+ ¦ 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)) ¦ ¦ double ¦ регистр вершины стека сопроцессора¦ ¦ ¦ 8087 (ST(0)) ¦ ¦ long double ¦ регистр вершины стека сопроцессора¦ ¦ ¦ 8087 (ST(0)) ¦ ¦ near* ¦ AX ¦ ¦ far* ¦ DX:AX ¦ L--------------------------+-------------------------------------

В общем случае 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 имеет следующий вид:

.MODEL SMALL .CODE PUBLIC _FindLastChar _FindLastChar PROC push bp mov bp,sp cld ; в строковой инструкции нужно ; выполнять отсчет в прямом ; направлении 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.


Турбо Ассемблер фирмы Borland представляет


Турбо Ассемблер фирмы Borland представляет собой многопро- ходный ассемблер с разрешением опережающих ссылок, скоростью ас- семблирования до 48000 строк в минуту (на компьютере IBM PS/2, модель 60), совместимый с макроассемблером фирмы Microsoft MASM и дополнительной возможностью использования режима расширенного синтаксиса Ideal. Независимо от вашего опыта в программировании вы, несомненно, оцените эти особенности, а также ряд других средств, которые значительно облегчают программирование на Ас- семблере. Среди таких средств можно кратко упомянуть следующие (подробно они будут описаны позднее):
- возможности объектно-ориентированного программирования;
- 32-разрядная модель и поддержка кадра стека;
- поддержка интерфейса защищенного режима DOS (DPMI) для ра- боты Турбо Ассемблера в защищенном режиме под управлением Microsoft Windows;
- полная поддержка процессора 80386 и i486;
- упрощенные директивы определения сегментов;
- улучшенное управление листингом;
- поддержка таблиц;
- перечислимые типы;
- развитые инструкции работы с флагами;
- новые средства поддержки структур;
- быстрая операция умножения с непосредственным операндом;
- поддержка множественных определений;
- директива спецификации VERSION;
- режим Quirks для эмуляции MASM;
- полная отладка на уровне исходного текста с помощью Турбо отладчика;
- встроенная утилита генерации перекрестных ссылок (TCREF);
- файлы конфигурации и командные файлы;
- утилита-преобразователь файлов .h в .ash (включаемые фай- лы).
Турбо Ассемблер является мощным ассемблером, работающим с командной строкой, который воспринимает ваши исходные файлы (фай- лы с расширением .ASM) и создает из них объектные модули (файлы с расширением .OBJ). После этого вы можете использовать програм- му-компоновщик фирмы Borland TLINK.EXE, отличающуюся высокой ско- ростью компоновки, для компоновки полученных объектных модулей и создания выполняемых файлов (файлов с расширением .EXE).

Выделение пространства для локальных данных


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



Выделение статической памяти


Турбо Паскаль позволяет в программах Турбо Ассемблера резер- вировать пространство для статических переменных в сегментах гло- бальных данных (DATA или DSEG). Чтобы выделить это пространство, можно просто использовать такие директивы, как DB, DW и т.д. Нап- ример:

DATA SEGMENT PUBLIC MyInt DW ? ; зарезервировать слово MyByte DB ? ; зарезервировать байт . . . DATA ENDS

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

MyInt DW 42 ; это не инициализирует ; MyInt значением 42

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

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



Выделение временной памяти


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

CODE SEGMENT ASSUME CS:CODE MyProc PROC FAR ; procedure MyProc(i : integer); 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 использу- ется для создания идентификаторов и выделения пространства для локальных переменных.

Оператор:

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 в регистре вместо занесения его в стек (если есть неиспользованные регистры).



Выполнение вызова


Все, что требуется от вас для передачи параметров в функцию C++, это занесение в стек самого правого параметра первым, затем следующего по порядку параметра и так далее, пока в стеке не ока- жется самый левый параметр. После этого нужно просто вызвать функцию. Например, при программировании на Borland C++ для вызова библиотечной функции Borland C++ strcpy для копирования строки SourceString в строку DestString можно ввести:

strcpy(DestString, SourceString);

Для выполнения того же вызова на Ассемблере нужно использо- вать инструкции:

lea ax,SourceString ; правый параметр push ax lea ax,DestString ; левый параметр push ax call _strcpy ; скопировать строку add sp,4 ; отбросить параметры

При настройке SP после вызова не забывайте очищать стек от параметров.

Можно упростить ваш код и сделать его независимым от языка, воспользовавшись расширением команды Турбо Ассемблера CALL:

call назначение [язык [,аргумент_1] .]

где "язык" - это C, PASCAL, BASIC, FORTRAN, PROLOG или NOLANGUAGE, а "аргумент_n" это любой допустимый аргумент програм- мы, который может быть прямо помещен в стек процессора.

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

lea ax,SourceString lea bx,DestString call strcpy c,bx,ax

Турбо Ассемблер автоматически вставит команды помещения ар- гументов в стек в последовательности, принятой в С++ (сначала AX, затем BX), выполнит вызов _strcopy (перед именами С++ Турбо Ас- семблер автоматически вставляет символ подчеркивания), и очищает стек после вызова.

Если вы вызываете функцию С++, которая использует соглашения Паскаля, заносите в стек параметры слева направо. После вызова настраивать указатель стека SP не требуется.

lea ax,DestString ; левый параметр push ax lea ax,SourceString ; правый параметр push ax call CTRCPY ; скопировать строку

Можно опять упростить ваш код, воспользовавшись расширением команды Турбо Ассемблера CALL:

lea bx,DestString ; самый левый параметр lea ax,SourceString ; самый правый параметр call strcpy pascal,bx,ax

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

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

Функции С++ сохраняют следующие регистры (и только их): SI, DI, BP, DS, SS, SP и CS. Регистры AX, BX, CX, DX, ES и флаги мо- гут произвольно изменяться.



Выравнивание элементов структуры


В определения структуры для выравнивания элементов структур на соответствующие границы можно использовать директиву ALIGN. Например:

ALIGN 4 ; выравнивание на границу двойного слова member dd ? ; member будет выравниваться на DWORD



Выражения


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

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

Простые выражения Таблица 5.8 ---------------------T------------------------------------------¬ ¦ Выражение ¦ Что получается при вычислении ¦ +--------------------+------------------------------------------+ ¦ 5 ¦ константа 5 ¦ ¦ -5 ¦ константа -5 ¦ ¦ 4+3 ¦ константа 7 ¦ ¦ 4*3 ¦ константа 12 ¦ ¦ 4*3+2*1 ¦ константа 14 ¦ ¦ 4*(3+2)*1 ¦ константа 21 ¦ L--------------------+-------------------------------------------

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



Выражения и операнды


Основное различие между выражениями режима Ideal и режима MASM состоит в функции, которую выполняют квадратные скобки. В режиме Ideal квадратные скобки всегда относятся к содержимому заключаемой в них величины. Квадратные скобки никогда не приводят в подразумеваемому сложению. Однако многие стандартные конструк- ции MASM в режиме Idealе не допускаются.

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

mov ax,wordptr

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

mov ax,[wordptr]

Здесь ясно, что вы загружаете содержимое ячейки, адресуемой с помощью wordptr (в текущем сегменте данных DS) в регистр AX.

Если вы хотите сослаться на смещение идентификатора в сег- менте, то вы должны явно использовать операцию OFFSET, например:

mov ax,OFFSET wordptr



Вывод информации на устройство печати


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

.MODEL SMALL .STACK 100h .DATA Message DB 'Привет!',13,10,'$' Message_Length EQO $ - Message .CODE mov ax,@Data mov ds,ax ; установить регистр DS таким ; образом, чтобы он указывал mov ah,40h ; функция DOS вывода строки ; на устройство mov bx,4 ; описатель принтера mov cx,Message_Length ; число печатаемых символов mov dx,OFFSET Message ; ссылка на "Привет!" int 21h ; вывести "Привет!" принтер mov ah,4ch ; функция DOS завершения ; программы int 21h ; завершить программу END

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

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

mov bx,4 ; описатель принтера

на строку:

mov bx,1 ; описатель стандартного вывода

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



Вывод нескольких сообщений об ошибках


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

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

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

Приведем пример директивы MULTERRS:

MULTERRS mov ax,[bp+abc ; порождает две ошибки: ; 1) неопределенный идентификатор ; abc ; 2) нужна правая квадратная ; скобка

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

Приведем пример использования директивы NOMULTERRS:

NOMULTERRS mov ax,[bp+abc ; порождает одну ошибку: ; 1) неопределенный идентификатор ; abc

Назад | Содержание | Вперед



Вывод предупреждающих сообщений


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

Синтаксис директивы WARN следующий:

WARN [класс_предупреждений]

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

ALN Выравнивание сегмента BRK Требуются скобки ICG Неэффективная генерация кода LCO Переполнение счетчика инструкций OPI Открытие условного IF OPP Открыть процедуру OPS Открытие сегмента OVF Арифметическое переполнение PDC Конструкция, зависящая от прохода PRO Запись в память в защищенном режиме с исполь- зованием регистра CS PQK Предположение об использовании констант для [константа] предупреждение. RES Предупреждение о зарезервированном слове. TPI Недопустимое предупреждение Турбо Паскаля.

Заметим, что те же идентификаторы используются в параметре командной строки /W.

Приведем пример использования директивы WARN:

WARN OVF ; разрешается предупреждение о переполнении DW 1000-h * 123h ; выводится предупреждение о переполнении

Примечание: Директива WARN без класса предупреждений разрешает все предупреждения. Директива WARN с идентифика- тором разрешает только указанные предупреждения.

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

NOWARN OVF ; запрещается предупреждение о переполнении DW 1000-h * 123h ; не выводится предупреждение о ; переполнении



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


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

Эти две директивы практически представляют собой одно и то же, но директива DISPLAY выводит на экран строку, заключенную в кавычки, а %OUT - без кавычек.

В режимах Ideal и MASM директива DISPLAY имеет следующий синтаксис:

DISPLAY "текст"

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

Директива %OUT в режимах Ideal и MASM имеет следующий син- таксис:

%OUT текст

где "текст" также представляет то, что вы хотите вывести.



Вызов Borland C++ из Турбо Ассемблера


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



Вызов функции Турбо Ассемблера из Borland C++


Теперь мы рассмотрим пример программы на Borland C++, вызы- вающей функцию Турбо Ассемблера. Модуль Турбо Ассемблера COUNT.ASM содержит функцию LineCount, которая возвращает значение счетчика числа строк и символов в передаваемой строке:

; Вызываемая из С++ функция на Ассемблере с малой моделью памяти ; для подсчета числа строк и символов в завершающейся нулем ; "строке". ; ; Прототип функции: ; extern unsigned int LineCount(char * near StringToCount, ; unsigned int near * CharacterCountPtr); ; ; Ввод: ; char near * StringToCount: указатель на "строку", в ; которой нужно выполнить подсчет строк. ; ; unsigned int near * CharacterCountPtr: указатель на ; целую переменную, в которую нужно записать значение ; счетчика NEWLINE EQU 0ah ; символ перевода строки в Си .MODEL SMALL .CODE PUBLIC _LineCount __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 ; это новая строка? jnz LineCountLoop ; нет, проверить ; следующий символ inc dx ; да, подсчитать еще одну ; строку jmp LineCountLoop EndLineCount: inc dx ; подсчитать строку, которая ; завершается нулевым символом mov [bx],cx ; задать значение переменной- ; счетчика mov ax,dx ; возвратить счетчик строк в ; качестве значения счетчика pop si ; восстановить регистровую ; переменную вызывающей ; программы pop bp ret _LineCount ENDP END

Следующий модуль на языке С++ с именем CALLCT.CPP представ- ляет собой пример вызова функции LineCount:

char * TestString="Line 1\nline 2\nline 3"; extern "C" { unsigned int LineCount(char * StringToCount, unsigned int near * CharacterCountPtr); } main() { unsigned int LCount; unsigned int CCount;


Lcount = LineCount(TestString, &CCount); printf("Lines: %d\nCharacters: %d\n", LCount, CCount); }

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

bcc -ms callct.cpp count.asm

Как здесь показано, функция LineCount будет работать только при компоновке с программами на языке С++, в которых используется малая модель памяти, так как в других моделях размеры указателей и адресов в стеке изменятся. Приведем пример версии функции LineCount (COUNTLG.ASM), которая будет работать с программами на С++, использующим большую модель памяти (но не малую модель: пос- кольку передаются дальние указатель, функция LineCount также опи- сана, как функция дальнего типа):

; Вызываемая из С++ функция на Ассемблере для подсчета числа ; строк и символов в завершающейся нулем "строке". ; ; Прототип функции: ; extern unsigned int LineCount(char * far StringToCount, ; unsigned int far * CharacterCountPtr); ; ; Ввод: ; char far * StringToCount: указатель на "строку", в ; которой нужно выполнить подсчет строк. ; ; unsigned int far * CharacterCountPtr: указатель на ; целочисленную переменную, в которую нужно записать ; значение счетчика NEWLINE EQU 0ah ; символ перевода строки в Си .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 ; да, подсчитать еще одну ; строку 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 можно скомпоновать с CALLCT.CPP с по- мощью следующей командной строки:

bcc -ml callct.cpp countlg.asm


Вызов из Borland C++ функций Турбо Ассемблера


Обычно C++ и Ассемблер совместно используют путем написания отдельных модулей целиком на C++ или Ассемблере, компиляции моду- лей С++ и ассемблирования модулей Ассемблера с последующей сов- местно компоновкой этих раздельно написанных модулей. Это пока- зано на Рис. 1.18.

-------------------------------¬ -------------------------------¬ ¦ Исходный файл на языке С++ ¦ ¦ Исходный файл на Ассемблере ¦ ¦ имя_файла.СPP ¦ ¦ имя_файла.ASM ¦ L--------------T---------------- L------------T------------------ ¦ ¦ Компиляция Ассемблирование -=============¬ -==================¬ ¦ Borland C++ ¦ ¦ Турбо Ассемблер ¦ L=============- L==================- ¦ ¦ -------------------------------¬ -------------------------------¬ ¦ Объектный файл языка С++ ¦ ¦ Объектный файл языка С++ ¦ ¦ имя_файла.OBJ ¦ ¦ имя_файла.OBJ ¦ L--------------T---------------- L-------------T----------------- ¦ ¦ ¦ ¦ L-------------¬ --------------- ¦ ¦ -=============¬ ¦ TLINK ¦ Компоновка L=============- ¦ ---------------------------------¬ ¦ Выполняемый файл ¦ ¦ имя_файла.EXE ¦ L---------------------------------

Рис. 1.18 Цикл компиляции, ассемблирования и компоновки Borland C++, Турбо Ассемблера и компоновщика TLINK

Выполняемый файл получается из "смеси" модулей С++ и Ассемб- лера. Этот цикл можно запустить командой:

bcc имя_файла_1.cpp имя_файла_2.asm

которая указывает Borland C++, что нужно сначала компилировать файл имя_файла_1.СPP в файл имя_файла_1.OBJ, а затем вызвать Тур- бо Ассемблер для ассемблирования файла имя_файла_2.asm в имя_фай- ла_2.obj, и, наконец, вызвать компоновщик TLINK для компоновки файла имя_файл_1.OBJ и имя_файл_2.OBJ в файл имя_файла.EXE.

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

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

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

Давайте теперь приступим к изучению правил компоновки прог- рамм Турбо Ассемблера и Borland C++.


Вызов из Турбо Ассемблера функции Borland C++


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

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

Часть программы CALCAVG.CPP, реализованная на С++ (CALCAVG.CPP), выглядит следующим образом:

#include <stdio.h> 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(). ; ; Прототип функции: ; extern float Average(int far * ValuePtr, ; int NumberOfValues); ; ; Ввод: ; int far * ValuePtr: ; массив значений для ; ; вычисления среднего ; int NumberOfValues: ; число значений для ; ; вычисления среднего .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 END

Основная функция (main) на языке С++ передает указатель на массив целых чисел TestValues и длину массива в функцию на Ас- семблере Average. Эта функция вычисляет сумму целых чисел, а за- тем передает эту сумму и число значений в функцию С++ IntDivide. Функция IntDivide приводит сумму и число значений к типу с плава- ющей точкой и вычисляет среднее значение (делая это с помощью од- ной строки на С++, в то время как на Ассемблере для этого потре- бовалось бы несколько строк). Функция IntDivide возвращает сред- нее значение (Average) в регистре вершины стека сопроцессора 8087 и передает управление обратно основной функции.

Программы CALCAVG.CPP и AVERAGE.ASM можно скомпилировать и скомпоновать в выполняемую программу CALCAVG.EXE с помощью коман- ды:

bcc calcavg.cpp average.asm

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

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

.MODEL small,C EXTRN C IntDivide:PROC .CODE PUBLIC C Average Average PROC C ValuePtr:DWORD, NumberOfValues:WORD les bx,ValuePtr mov cx,NumberOfValues mov ax,0 AverageLoop: add ax,es:[bx] add bx,2 ;установить указатель ;на следующее значение loop AverageLoop call _IntDivide C,ax,NumberOfValues ret Average ENDP END

Вызов метода объекта


Для вызова метода объекта используйте инструкцию CALL. Для вызова процедур методов Турбо Ассемблер обеспечивает расширение стандартной инструкции CALL - CALL.METHOD.

Примечание: Синтаксис инструкции CALL для вызова ста- тических и виртуальных методов совпадает.



Вызов общей макрокоманды, состоящей из нескольких строк


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

имя_макрокоманды [аргумент [[,]аргументе].]

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

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

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

- как группу символов, выделенную угловыми скобками (<>), которая может содержать пробелы, запятые и двоеточия;

- как одиночный символ с предшествующим знаком !, что экви- валентно заключению символа в угловые скобки;

- как выражение с предшествующим символом %, которое предс- тавляет текстовое значение выражения, соответствующее те- кущему выбранному основанию.



Вызов процедур методов для объектов: CALL.METHOD


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

Поскольку вы можете использовать косвенный вызов, выполняю- щие вызов инструкции могут нарушить содержимое некоторых регист- ров. В этом случае, если вы используете вызов виртуального мето- да, Турбо Ассемблер позволяет вам выбрать соответствующие регист- ры. Приведем синтаксис расширения CALL.METHOD: CALL указатель_экземпляра METHOD [имя_объекта:]имя_метода [USES [сегм_регистр:]регистр_смещения] [язык_и_аргументы]

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

Поле "имя_метода" содержит имя метода, который должен вызы- ваться для конкретного экземпляра объекта.

Примечание: О том, как задавать виртуальных или стати- ческий метод, подробнее рассказывается в Главе 8.

Если метод является виртуальным,и требуется косвенный вызов, инструкция CALL.METHOD выполняет косвенный вызов через регистры ES:BX (ES:EBX для модели USE32 процессора 80386). Если вы хотите использовать другие регистры, можно переопределить их с помощью оператора USES. Поле "сегм_регистр" это необязательный используе- мый сегментный регистр, а "регистр_смещения" - это регистр смеще- ния, используемый для вызова.

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

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

Поле "язык_и_аргументы" инструкции CALL.METHOD содержит необязательные спецификации языка и аргументов, которые идентичны по форме описанным в предыдущем разделе.

Вызов процедур методов для С++ и Паскаля требует обычно, чтобы экземпляр объекта передавался в качестве аргумента в стеке. Подробнее об этом рассказывается в Главе 18 и Главе 19.



Вызов процедур с кадрами стека


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

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

Используемые процедурой интерфейсные соглашения определяют порядок, в котором аргументы должны заноситься в стек. Для проце- дур Бейсика, Фортрана и Паскаля аргументы заносятся в стек в том порядке, в котором они указываются. Для языков Си и С++ аргументы заносятся в стек в обратном порядке.

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

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

CALL выражение [язык] [,список_аргументов]

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

Примечание: О директиве MODEL более подробнее расска- зывается в Главе 7.

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

CALL test PASCAL,ax,es OFFSET buffer,blen

Поскольку в данном примере указан Паскаль, Турбо Ассемблер будет заносить аргументы в стек в том порядке, в котором они ука- заны. Этот пример эквивалентен следующим инструкциям:


PUSH ax PUSH es OFFSET buffer PUSH word PTR blen CALL test

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

CALL test C,ax,es OFFSET buffer, word PTR blen

даст в результате следующий код:

PUSH word PTR blen PUSH es OFFSET buffer PUSH ax CALL test SUB sp,8

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

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

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


Вызов статического метода


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

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

В следующей таблице показан пример вызова статического мето- да init для объекта связанного списка:

CALL foolist METHOD list:init pascal,ds offset foolist CALL es:di METHOD list:init pascal,es di

Сам адрес вызова является адресом экземпляра объекта. Этот адрес используется только по синтаксическим причинам. Фактически генерируемым адресом является непосредственный вызов процедуры метода.

В данном примере первым вызовом является вызов метода init объекта list. Так как это статический метод, вы выполняете непос- редственный вызов процедуры метода list_init. Турбо Ассемблер иг- норирует экземпляр объекта foolist (он только передается в ка- честве аргумента процедуре метода).

За именем вызова следует обычный расширенный параметр языка и список параметров. Язык и параметры зависят от вызываемого вами метода. Один из параметров обычно является указателем на экземп- ляр объекта. В данном примере метод воспринимает один параметр, являющийся указателем на экземпляр объекта.



Вызов виртуального метода


Любой вызов виртуального метода требует косвенного вызова процедуры метода. Для этого используйте расширенную инструкцию CALL.METHOD. Для выполнения вызова Турбо Ассемблер генерирует следующие инструкции:

1. Загружает промежуточные регистры указателем на ТВМ из эк- земпляра объекта.

2. Выполняет косвенный вызов соответствующего элемента таб- лицы.

Таким образом, когда вы задаете:

CALL <экземпляр> METHOD <объект>:<метод> USES <seg>:<reg> <вызов_проц>

то генерируются следующие инструкции:

MOV <рег>, [<экземпляр>.<указатель_ТВМ>] CALL [(<сегм><рег>).<метод>] <вызыв_проц>

Первая инструкция загружает выбранный регистр <рег> адресом таблицы виртуальных методов из поля указателя ТВМ структуры объ- екта. Вторая инструкция выполняет косвенный вызов соответствующе- го метода в таблице.

Например, вызов в виде:

CALL es:di method list:insert uses ds:bx pascal,es di,es dx,es cx

генерирует последовательность вида:

mov bx,[es:di.@Mptr_list] CALL [ds:bx.insert] pascal,es di,es dx,es cx

Заметим, для объектов, описанных с таблицами NEAR, инструк- цией CALL.METHOD будет загружаться только регистр смещения. Сегментный регистр всегда должен содержать корректное значение. В следующем примере показано, как обеспечить правильную установку сегментного регистра:

; Добавить узел к концу объекта связанного списка. ; Это виртуальный метод "list_append". list_append PROC PASCAL NEAR ARG @@list:dword,\ @@new:dword USES dx,bx, es,di mov ax,@Data mov ds,ax les di,@@list sub ax,ax CALL es:di method list:insert uses DS:bx pascal, es di,@@new,ax ax ret ENDP

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


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

node STRUC GLOBAL METHOD { construct:dword = node_construct ; подпрограмма ; конструктора узла destroy:dword = node_destroy ; подпрограмма ; деструктора узла init:dword = node_init ; подпрограмма ; инициализации узла deinit:dword = node_deinit ; подпрограмма ; деинициализации узла routine virtual next:word = node_adv ; подпрограмма ; следующего узла virtual prev:word = node_back ; подпрограмма ; предыдущего узла virtual print:word = node_print ; подпрограмма ; содержимого узла } node_next dd ? ; указатель следующего ; узла node_prev dd ? ; указатель ; предыдущего узла ends

Чтобы можно было использовать связанный список или очередь, вы можете определить любое число объектов, наследующих объект node. Приведем два примера:

mlabel STRUC GLOBAL node METHOD { virtual print:word = label_print } label_name db 80 dup (?) label_addr db 80*2 dup (?) label_city db 80 dup (?) label_state db 2 dup (?) label_zip db 10 dup (?) ENDS

book STRUC GLOBAL node METHOD { virtual print:word = book_print } book_title db 80 dup (?) book_author db 80 dup (?) ENDS

В следующем примере вы для объектов label и book вызываем методы путем вызова printit. Если "предком" является node, не важно, какой объект передается printit. Так как метод печати - это виртуальный метод, вызов выполняется косвенно через ТВМ объ- екта. При первом вызове printit, так как мы передаем экземпляр объекта label, вызывается процедура метода label_print. При вто- ром вызове printit вызывается процедура метода book_print, пос- кольку мы передаем экземпляр объекта book. Заметим, что если бы метод print был статическим, то при вызове node_print всегда вы- зывалась бы процедура node_print (что нежелательно).

call printit pascal,<<адрес экземпляра объекта label>> call printit pascal,<<адрес экземпляра объекта book>> . . . printit proc pascal near arg @@obj:dword uses ds,si,es,bx mov ax,@data mov es,ax lds si@@obj call ds:si method node:print uses es:bx pascal,ds si ret endp


Вызов виртуальных методов "предков"


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

virtual show:word = list_show

а в классе очереди:

virtual show:word = queue_show

Подпрограмма list_show может печатать LIST SHOW: с последую- щим выводом отдельных элементов списка. Однако в порожденном классе, если queue_show использует подпрограмму печати, она долж- на печатать собственный заголовок QUEUE SHOW: и использовать list _show только как механизм последовательного прохода по списку и печати отдельных элементов. list_show может определить передавае- мый ей тип структуры и в зависимости от этого печатать заголовок списка. Если подпрограмма для list_show посмотрит на указатель таблицы виртуальных методов передаваемой ей структуры, она сможет определить, совпадает ли указатель с указателем, установленным в подпрограмме list_init для списков (или они различны). Если ука- затель ТВМ в структуре не указывает на таблицу виртуальных мето- дов для списков, то вероятно структура является порожденным ти- пом. list_show может выполнить эту проверку с помощью следующих операторов:

cmp [([es:di]).@mptr_list],offset @TableAddr_LIST jne @@not_a_list ; пропустить печать заголовка списка

; Если мы попали сюда, то это список, и следует ; распечатать его заголовок. . @@not_a_list: ; Теперь вывести отдельные элементы списка.

Как можно вызвать класс списка и метод вывода из подпрограм- мы queue_show? Если бы вы вызвали list_show непосредственно, то в подпрограмме могла бы возникнуть проблема, если имя используемого для вывода метода изменилось. (Вы можете не помнить об изменениях в вызове queue_show.) Если в queue_show вы поместите следующий оператор:

call(es:di) method list:show

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


Наилучшим способом вызова метода класса является следующий:

call +@table_list | show

Поскольку при описании класса list_show было задано как зна- чение элемента вывода @table_list, Турбо Ассемблер автоматически транслирует данный оператор в непосредственный вызов list_show. Заметим, что хотя в списке метод вывода описывается как виртуаль- ный, задание вызова приводит к тому, что Турбо Ассемблер выполня- ет непосредственный вызов без просмотра ТВМ.

Примечание: Виртуальные подпрограммы обычно вызываются косвенным образом через просмотр ТВМ.

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

mov bx,offset @TABLEADDR_LIST call [(@table_list ptr es:bx).SHOW]

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


Вызываемые процедуры, содержащие RETURNS


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



Взаимодействие между Турбо Ассемблером и Borland C++


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



Если текущим выбранным процессором является


Если текущим выбранным процессором является процессор 80386 или старше, то Турбо Ассемблер предоставляет две операции, кото- рые определяют, будет выражение интерпретироваться как 16- или 32 -разрядное значение. Это операции SMALL и LARGE. Они имеют сле- дующий синтаксис:
SMALL выражение LARGE выражение
Операция SMALL помечает выражение, как представляющее 16-разрядное значение. Операция LARGE помечает его как 32-разряд- ное значение. Эти операции особенно важны в программах, при рабо- те которой в операционной среде некоторые сегменты являются 32-битовыми, а другие - 16-битовыми. Например, инструкция:
JMP [DWORD PTR ABC]
представляет собой косвенный переход на содержимое переменной в памяти ABC. Если у вас разрешено использование процессора 80386, то данная инструкция может интерпретироваться либо как дальний переход с использованием сегмента и 16-разрядного смещения, либо как ближний переход по 32-разрядному смещению. Для устранения неоднозначности вы можете использовать операции LARGE и SMALL:
JMP SMALL [DWORD PTR ABC]
Данная инструкция приводит к ассемблированию Турбо Ассембле- ром инструкции перехода таким образом, что значение, считываемое из ABC, интерпретируется как 16-разрядный сегмент и 16-разрядное смещение. При этом Турбо Ассемблер выполняет косвенный переход типа FAR (дальний переход).
При использовании операций SMALL или LARGE в адресной части смещения, эти операции указывают, что адрес является 32-разряд- ным, например:
JMP SMALL [LARGE DWORD PTR ABC]
указывает, что переменную в памяти ABC описывает 32-разрядный ад- рес, но ее содержимое интерпретируется как 16-разрядный сегмент и 16-разрядное смещение.
Назад | Содержание | Вперед

Задание адресного подтипа выражения


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

Операции переопределения типа Таблица 5.19 ----------------------------T-----------------------------------¬ ¦ Выражение ¦Значение ¦ +---------------------------+-----------------------------------+ ¦выражение_1 PTR выражение_2¦Преобразует выражение_2 к типу, оп-¦ ¦ ¦ределяемому выражением_1. При этом¦ ¦ ¦0=UNKNOWN, 1=BYTE, 2=WORD, 4=DWORD,¦ ¦ ¦6=PWORD, 8=QWORD, 10=TBYTE, 0ffffh=¦ ¦ ¦NEAR, 0fffeh=FAR, все про-¦ ¦ ¦чие=UNKNOWN (только для режима¦ ¦ ¦MASM). ¦ ¦ ¦ ¦ ¦тип PTR выражение_2 ¦Преобразует выражение к заданному¦ ¦ или тип выражение ¦адресному подтипу (только для режи-¦ ¦ ¦ма Ideal). ¦ ¦ ¦ ¦ ¦тип LOW выражение ¦Преобразует выражение к заданному¦ ¦ ¦адресному подтипу. Описанный тип¦ ¦ ¦должен быть меньше по размеру, чем¦ ¦ ¦тип выражения ( только для режима¦ ¦ ¦Ideal). ¦ ¦ ¦ ¦ ¦тип HIGH выражение ¦Преобразует выражение к заданному¦ ¦ ¦адресному подтипу. Описанный тип¦ ¦ ¦должен быть меньше по размеру, чем¦ ¦ ¦тип выражения. Возвращаемый адрес¦ ¦ ¦настраивается на старшую часть объ-¦ ¦ ¦екта, описанного адресным выражени-¦ ¦ ¦ем ( только для режима Ideal). ¦ L---------------------------+------------------------------------

Приведем некоторые примеры:

IDEAL big DD 12345678h MOV ax,[WORD big] ; ax = 5678h MOV al,[BYTE PTR big] ; al = 78h MOV ax,[WORD HIGH big] ; ax = 1234h MOV ax,[WORD LOW big] ; ax = 5678h MOV al,[BYTE LOW WORD HIGH big] ; al = третьему байту big ; = 34h MASM: MOV ax,2 PTR big ; ax = 5678h MOV ax,WORD PTR big ; ax = 5678h (WORD имеет ; значение 2)



Задание элементов структуры и объединения


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

member1 DW 1

одинаково допустима в сегменте и в определении структуры. В сег- менте данный оператор означает "зарезервировать слово со значени- ем 1 и именем member1". В определении структуры или объединения это означает "зарезервировать слово с начальным значением 1 и именем элемента member1".

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

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

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



Задание модификатора языка


Модификаторы языка указывают сообщают Турбо Ассемблеру, что в процедуры нужно включать специальный код начала и завершения, организующий интерфейс с Windows и менеджером оверлеев VROOM. Чтобы эти модификаторы использовать, укажите их перед языком про- цедуры в директиве модели или в заголовке процедуры. Допустимыми модификаторами являются модификаторы NORMAL, WINDOWS, ODDNEAR и ODDFAR.

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

Примечание: Подробнее об этом рассказывается в Главе 7.

Чтобы задать модификатор языка для конкретной процедуры, включите модификатор языка в конкретную процедуру. Например:

. . . sample PROC WINDOWS PASCAL FAR ENDP . . . Если вы не задаете модификатор языка, Турбо Ассемблер ис- пользует модификатор языка, заданный в операторе MODEL. Если ди- ректива MODEL отсутствует, или если задан модификатор NORMAL, то Турбо Ассемблер будет использовать стандартный код начала и за- вершения.

Если вы выбрали модификатор языка WINDOWS, Турбо Ассемблер генерирует код начала и завершения, позволяющий вам выбирать про- цедуру из Windows. Турбо Ассемблер генерирует специальный код на- чала и завершения только для процедура FAR WINDOWS. Из Windows вы не можете вызвать процедуры NEAR, поэтому специальный код начала и завершения для них не требуется. Процедуры, вызываемые Windows, обычно используют соглашения по вызову, принятые в Паскале (PASCAL). Например:

. . . Winoproc PROC WINDOWS PASCAL FAR ARG __hwnd:WORD, __mess:WORD, __wparam:WORD, __lparam:DWORD ; тело процедуры ENDP . . .

Примечание: Подробности о процедурах Windows можно уз- нать в документации по Windows.

Модификаторы языка ODDNEAR и ODDFAR используются для менед- жера оверлеев VROOM. VROOM имеет два режима операций: oddnear и oddfar. Чтобы увидеть код начала и завершения, который порождают эти модификаторы, вы можете использовать параметр командной стро- ки /la.



Задание сегмента


Как мы уже говорили ранее, необходимо обеспечивать, чтобы Borland C++ и Турбо Ассемблер использовали одну и ту же модель памяти, и чтобы сегменты, которые вы используете в Турбо Ассемб- лере, совпадали с теми сегментами, которые использует Borland C++. В Турбо Ассемблере имеется модель памяти tchuge,которая под- держивает модель huge Borland C++. Перечень моделей памяти и сег- ментов можно найти в предыдущем разделе. Нужно не забывать также помещать директиву EXTRN для внешних идентификаторов вне всех сегментов или внутри правильного сегмента.



Закрытие определения структуры или объединения


После того, как вы определите все элементы структуры или объединения, нужно закрыть определение структуры или объединения. Для этого используется директива ENDS.

В режиме Ideal директива ENDS имеет следующий синтаксис:

ENDS [имя]

В режиме MASM вы можете использовать синтаксис:

имя ENDS

где "имя" (если оно присутствует) соответствует имени открытого определения структуры или объединения. Если имя не указано, то закрывается открытая в данный момент структура или объединение.

Вы можете также использовать директиву ENDS для закрытия сегментов. Это не приводит к конфликту, поскольку открытие сег- мента внутри структуры или объединения не допускается.



Замечания по динамически компонуемым библиотекам Windows


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

Библиотеки DLL выполняют множество функций. Например, вы мо- жете в DLL неинтерактивные программы DOS. С помощью DLL можно добавить новые средства работы с экраном.

На дистрибутивном диске Турбо Ассемблера можно найти пример программы с именем DLLPROG.ASM, который иллюстрирует DLL.

Для построения DLL можно использовать утилиту MAKE. Формиру- ющий файл должен включать в себя все модули, которые должны ком- поноваться с DLL, например:

dllprog.dll: dllprog.obj dllprog.def TLINK dllprog,,,,dllprog RC dllprog.dll

dllprog.obj: dllprog.asm TASM dllprog

Данный процесс построения требует наличия следующего файла определений компоновщика DLLPROG.DEF:

LIBRARY DLLPROG EXETYPE WINDOWS CODE PRELOAD MOVEABLE DISCARDABLE ; CODE применяется к ; сегментам _TEXT или ; в классе CODE DATA PRELOAD MOVEABLE SINGLE ; DATE применяется ко ; всем сегментам в ; группе DGROUP и в ; классе DATA ; (должен быть ; SINGLE для всех DLL HEAPSIZE 0



Замечания по прикладным программам Windows


Прикладная программа Windows во многом аналогична DLL с единственной процедурой с именем WinMain. Windows вызывает WinMain для запуска процедуры. Прикладная программа имеет обычно стандартную структуру, которая позволяет ей взаимодействовать с графической операционной средой Windows.

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

Для построения прикладной программы Wiondows можно использо- вать утилиту MAKE. При этом в формирующем файле следует указать все модули, компонуемые с данной прикладной программой:

winproc.exe: winprog.obj winprog.def winprogg.res TLINK winprog,,,,winprog RC winprog.res

winproc.res:winproc.rc RC -r winproc.rc

winproc.obj: winprog.asm winprog.inc TASM winprog

Этот процесс построения требует использования следующего файла определений компоновщика WINPROG.DEF:

NAME WINPROG EXETYPE WINDOWS CODE MOVEABLE DISCARDABLE DATA MOVEABLE MULTIPLE DISCARDABLE STACKSIZE 5120 ; минимум для прикладных ; программ Windows

;----------------------------------------------------------- ; Определить импортируемые функции. (Это не обязательно, ес- ; ли вы выполняете компоновку с библиотекой импорта типа ; IMPORT.LIB или LIBW.LIB.) ;----------------------------------------------------------- IMPORTS DLLPROG.SetHello

Назад | Содержание | Вперед



Замечания по программам формата COM


Программы COM представляют собой ограниченные версии прог- рамм EXE. Каждую программу формата COM можно представить как программу EXE, но не каждую программу EXE можно представить как программу формата COM. Здесь действуют следующие ограничения:

- Программы COM следует писать с использованием модели TINY.

- В программах COM нельзя использовать предопределенный сег- мент стека.

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

- Выполнение должно начинаться со смещения 100h в сегменте кода. Чтобы это произошло, укажите в качестве первой инс- трукции сегмента кода директиву STURTUPCODE.

Турбо Ассемблер загружает программы COM, начиная со смещения 100h в префиксе программного сегмента программы (PSP). Для этого директива STARTUPCODE для модели TINY автоматически помещает в программу ORG 100h.

При загрузке программы COM устанавливаются следующие регист- ры:

--------------------T-------------------------------------------¬ ¦ Регистр ¦ Значение ¦ +-------------------+-------------------------------------------+ ¦ CS,DS,ES,SS ¦ Содержит адрес параграфа в PSP программы. ¦ ¦ ¦ ¦ ¦ IP ¦ Устанавливается в значение 100h. ¦ ¦ ¦ ¦ ¦ SP ¦ Устанавливается в 0FFFEh (последнее слово ¦ ¦ ¦ в сегменте программы. ¦ L-------------------+--------------------------------------------

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

Хотя программы COM должны определяться с моделью памяти TINY, с помощью директив CODESEG, DATASEG и UDATASEG можно разде- лить код данные и неинициализированные данные.

Как и в случае программ EXE, когда вы загружаете программу COM, Турбо Ассемблер выделяет для ее завершения всю оставшуюся память. При возврате памяти в DOS убедитесь, что вы не освободи- ли непреднамеренно неинициализированные данные.

Данные вопросы иллюстрируются файлом-примером COMPROPG.ASM, который можно найти на дистрибутивных дисках Турбо Ассемблера.

Чтобы сформировать программу COM, используйте утилиту MAKE. В формирующем файле MAEKFILE следует указать все модули, с кото- рыми компонуется программа, например:

COMPROG.COM: COMPROG.OBJ TLINK COMPROG; COMPROG.OBJ: COMPROG.ASM TASM COMPROG



Замечания по программам формата EXE


При загрузке программы EXE операционная система устанавлива- ет регистры следующим образом:

------------------T---------------------------------------------¬ ¦ Регистр ¦ Значение ¦ +-----------------+---------------------------------------------+ ¦ DS, ES ¦ Содержит адрес параграфа для префикса прог- ¦ ¦ ¦ рамного сегмента программы (PSP). PSP со- ¦ ¦ ¦ держит передаваемые программе в командной ¦ ¦ ¦ строке аргументы и указатель на строку опе- ¦ ¦ ¦ рационной среды для программы. ¦ ¦ ¦ ¦ ¦ CS:IP ¦ Содержит начальный адрес, заданный в опера- ¦ ¦ ¦ торе END в одном из модулей программы, или ¦ ¦ ¦ адрес директивы STARTUPCODE. ¦ ¦ ¦ ¦ ¦ SS:SP ¦ Содержит адрес последнего слова, которое ¦ ¦ ¦ задает в программе сегмент стека. ¦ L-----------------+----------------------------------------------

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

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

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

Эти вопросы иллюстрируются примером программы EXEPROG.ASM на дистрибутивном диске. Чтобы сформировать программу EXE, исполь- зуйте утилиту MAKE. В формирующем файле MAKEFILE следует указать все модули, с которыми компонуется программа, например:

EXECPROG.EXE: EXECPROG.OBJ

TLINK EXECPROG; EXECPROG.OBJ: EXECPROG.ASM TASM EXECPROG



Занесение в стек константа (процессор 8086)


Хотя процессоры 80186б 80286 и 80386 среди основного набора инструкций содержат инструкцию PUSH, которая заносит в стек не- посредственное значение, в процессоре 8086 такой инструкции нет.

Однако Турбо Ассемблер позволяет использовать в инструкции PUSH константы и в процессоре 8086. При этом генерируется после- довательность инструкций, имеющая тот же результат, что и инс- трукция PUSH c операндом-константой для процессора 80186 и выше.

Примечание: Это средство вы можете использовать толь- ко при разрешении генерации эффективного кода.

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

MOV ax, constant PUSH ax

Данная последовательность имеет длину только 6 байт, но в процессе этого теряется содержимое регистра AX.



Записи и массивы


Записи и массивы, занимающие ровно 1, 2 или 4 байта, дубли- руются непосредственно в стек и передаются, как параметры-значе- ния. Если массив или запись имеет какой-либо другой размер (вклю- чая 3 байта), то в стек заносится указатель на этот массив или запись. В этом случае, если подпрограмма модифицирует такую структуру, то она должна создать ее локальную копию.



Запуск программы REVERSE.ASM


Для запуска программы REVERSE.ASM вы должны сначала ассем- блировать ее:

TASM reverse

а затем ввести:

TLINK reverse

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

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

ABCDEFG

а затем нажмите клавишу ENTER. Программа выведет на экран:

GFEDCBA

и завершит работу.

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

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

Если вы не хотите создавать объектный файл, но хотите полу- чить файл листинга, или если вы хотите получить файл перекрестных ссылок, но не хотите создавать файл листинга или объектный файл, задайте в качестве имени файла пустое (нулевое) устройство (NULL). Например, команда:

TASM FILE1,,NUL,

ассемблирует файл FILE1.ASM в объектный файл FILE1.OBJ, не созда- вая файла листинга, и создает файл перекрестных ссылок FILE1.XRF.



Запуск Турбо Ассемблера из DOS


На дистрибутивных дисках находятся две версии Турбо Ассемб- лера - TASM.EXE и TASMX.EXE. Используемая версия зависит от того, что вам нужно сделать.

Примечание: TASMX следует выбрать для ассемблирования очень больших модулей.

Турбо Ассемблер TASMX можно запустить в защищенном режиме Microsoft Windows 3.0 при помощи окна диалога Windows DOS, если вы запустили Windows в улучшенном режиме 386. Используйте TASMX, если у вас имеется большой объем расширенной памяти, и вам требу- ется ассемблировать очень большие модули. TASMX требует задания в маршруте поиска еще одного файла, DPMILOAD.EXE, который также поставляется на дистрибутивных дисках. (При попытке выполнения TASMX в другой среде, нежели MS-Windows в улучшенном режиме 386, TASMX пытается использовать поставляемый с Турбо Ассемблером сер- вер DPMI - поддержку интерфейса защищенного режима DOS. Это поз- воляет вам использовать TASMX на машинах с процессорами 286 или 386 при наличии расширенной памяти.)

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

Если вам не нужно ассемблировать очень большие модули, сле- дует выбрать TASM. Эта версия Турбо Ассемблера работает сравни- тельно быстрее, чем TASMX. Вы также можете запустить TASM, нахо- дясь в диалоговом окне Windows DOS.

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

TASM

или

TASMX

то будет выведен экран со вспомогательным описанием параметров командной строки и синтаксиса для задания ассемблируемых файлов. На Рис. 2.1 показан этот экран. Параметры и их синтаксис для TASM такие же.

----------------------------------------------------------------- Turbo Assembler Version 3.0 Copyright (C) 1988,1991 by Borland International, Inc Usage:

TASM [параметры] исх_файл [,объект_файл] [,листинг] [,пер_ссылки]

/a,/s Упорядочивание сегментов по алфавитному порядку или порядку исходного кода

/c Генерация в листинге перекрестных ссылок


/dSYM[=VAL] Определяется SYM = 0 или SYM = VAL

/e,/ r Эмулируемые или действительные инструкции с плаваю- щей точкой

/h,/? Выводится данная справочная информация

/iPATH Включаемые файлы ищутся по маршруту, определяемому PATH

/jCMD Определяет начальную директиву Ассемблера CMD (на- пример, jIDEAL)

/kh# Мощность хеш-таблицы #

/l,/la Генерация листинга: l=обычный листинг, la=расширен- ный

/ml,/mx,/mu Различимость в регистре букв идентификаторов: ml=все, mx=глобальные, mu=не различаются

/mv# Установка максимально допустимой длины имен иденти- фикаторов

/m# Позволяет выполнять # проходов для разрешения ссы- лок вперед

/n Подавление в листингах таблицы идентификаторов

/o,/op Генерация оверлейного кода, 32-разрядные фиксиро- ванные адреса для компоновщика Phar Lap.

/q Подавление записей файла .OBJ, не нужных для ком- поновки

/p Проверка перекрытия сегмента кода в защищенном ре- жиме

/t Подавление сообщений при успешном ассемблировании

/w0,/w1,/w2 Задание уровня предупреждение: w0 - нет предуп- реждений, w1 или w2 - есть предупреждения

/w-xxx,/w+xxx Запрещение или разрешение предупреждения xxx

/x Включение в листинги блоков условного ассемблирова- ния

/z Вывести на дисплей строку исходного текста с сооб- щением об ошибке.

/zi,/zd,/zn Информация об идентификаторах для отладки: zi=пол- ная, zd=только о номерах строк, zn=отсутствует. -----------------------------------------------------------------

Рис. 2.1 Командная строка Турбо Ассемблера

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

Общий вид командной строки выглядит следующим образом:

TASM файлы [; файлы].

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



TASM /e FILE1; /a FILE2

Здесь файл FILE1.ASM ассемблируется с параметром командной строки /e, а файл FILE2.ASM - с параметром командной строки /a.

В общем случае группа файлов в командной строке может иметь вид:

[параметр].исх_файл [[+] исходный_файл]. [,[объектный_файл] [, [файл_листинга], [, [файл_перекрестных_ссылок]]

Этот синтаксис показывает, что группа файлов может начинать- ся с любого параметра, который вы хотите применить к этим файлам, а затем могут следовать файлы, которые вы хотите ассемблировать. Именем файла может быть отдельное имя файла, либо вы можете ис- пользовать обычные трафаретные символы DOS * и ? для задания группы ассемблируемых файлов. Если расширение имени файла не ука- зано, Турбо Ассемблер добавляет расширение .ASM. Например, для ассемблирования всех файлов с расширением .ASM в текущем каталоге введите команду:

TASM *

Если вы хотите ассемблировать несколько файлов, их имена можно разделить знаком плюс (+):

TASM MYFILE1 + MYFILE2

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

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

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



Если вы желаете принять имя объектного файла по умолчанию и при этом запросить файл листинга, вы должны поместить запятую, которая обычно отделяет имя объектного файла от имени файла лис- тинга:

TASM FILE1,,TEST

Эта строка вызовет ассемблирование файла FILE1.ASM в объект- ный файл FILE1.OBJ и создаст файл листинга TEST.LST.

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

TASM MYFILE,,,MYXREF

По этой команде файл MYFILE.ASM ассемблируется в файл MYFILE.OBJ, листинг выводится в файл с именем MYFILE.LST, а пе- рекрестные ссылки - в файл MYXREF.XRF.

Если при спецификации ассемблируемых исходных файлов вы ис- пользуете трафаретные символы, их можно использовать также для задания имен файла листинга и объектного файла. Например, если в текущем каталоге содержатся файлы XX1.ASM и XX2.ASM, то командная строка:

TASM XX*,YY*

ассемблирует все файлы, начинающиеся с букв XX, генерирует объек- тные файлы, имена которых будут начинаться с YY, а остальную часть имени формирует в соответствии с именем исходного файла. Результирующие объектные файлы получат, таким образом, имена YY1, OBJ и YY2.OBJ.

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

TASM FILE1,,NUL,

Эта команда ассемблирует файл FILE1.ASM в объектный файл FILE1.OBJ. При этом файл листинга не создается, а создается файл перекрестных ссылок FILE1.XRF.


Запуск вашей первой программы


Теперь программу можно запустить на выполнение. Для этого в ответ на подсказку операционной системы DOS введите hello. На эк- ран выведется сообщение:

Привет!

Пока это все. Вы только что создали и выполнили свою первую программу на Ассемблере!.



Значения по умолчанию: когда необходимо загружать сегменты?


В некоторых случаях вызываемые из языка С++ функции Ассемб- лера могут использовать (загружать) для обращения к данным ре- гистры DS и/или ES. Полезно знать соотношение между значениями сегментных регистров при вызове из Borland C++, так как иногда Ассемблер использует преимущества эквивалентности двух сегментных регистров. Давайте рассмотрим значения сегментных регистров в тот момент, когда функция Ассемблера вызывается из Borland C++, а также соотношения между сегментными регистрами, и случаи, когда в функции Ассемблера требуется загружать один или более сегментных регистров.

При входе в функцию Ассемблера из Borland C++ регистры CS и DS имеют следующие значения, которые зависят от используемой мо- дели памяти (регистр SS всегда используется для сегмента стека, а ES всегда используется, как начальный сегментный регистр):

Значения регистров при входе в Ассемблер из Borland C++ Таблица 18.2 ------------------------------------------------------------¬ ¦ Модель CS DS ¦ +-----------------------------------------------------------+ ¦ Крохотная _TEXT DGROUP ¦ ¦ Малая _TEXT DGROUP ¦ ¦ Компактная _TEXT DGROUP ¦ ¦ Средняя имя_файла_TEXT DGROUP ¦ ¦ Большая имя_файла_TEXT DGROUP ¦ ¦ Громадная имя_файла_TEXT имя_вызывающего_файла_DATA¦ L------------------------------------------------------------

Здесь "имя_файла" - это имя модуля на Ассемблере, а "имя_вы- зывающего_файла" - это имя модуля Borland C++, вызывающего модуль на Ассемблере.

В крохотной модели памяти _TEXT и DGROUP совпадают, поэтому при входе в функцию содержимое регистра CS равно содержимому DS. При использовании крохотной, малой и компактной модели памяти при входе в функцию содержимое SS равно содержимому регистра DS.

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


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

С регистром DS дело обстоит иначе. Во всех моделях памяти Borland C++, кроме сверхбольшой, регистр DS при входе в функцию указывает на статический сегмент данных (DGROUP), и изменять его не следует. Для доступа к данным с дальним типом обращения всегда можно использовать регистр ES, хотя вы можете посчитать, что для этого временно нужно использовать регистр DS (если вы собираетесь осуществлять интенсивный доступ к данным), что исключит необходи- мость использования в вашей программе множества инструкций с пре- фиксом переопределения сегмента. Например, вы можете обратиться к дальнему сегменту одним из следующих способов:

. . . .FARDATA Counter DW 0 . . . .CODE PUBLIC _AsmFunction _AsmFunction PROC . . . mov ax,@FarData mov es,ax ; ES указывает на ; сегмент данных с ; дальним типом ; обращения inc es:[Counter] ; увеличить значение ; счетчика . . . _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 вы его восстанавливаете (как это делается в приведенном примере). Даже если в данной функции на Ассемблере вы не обращае- тесь к DGROUP, перед выходом из нее все равно обязательно нужно восстановить содержимое DS, так как в Borland C++ подразумевает- ся, что регистр DS не изменялся.



При использовании в функциях, вызываемых из С++, сверхболь- шой модели памяти работать с регистром DS нужно несколько по-дру- гому. В сверхбольшой модели памяти Borland C++ совсем не исполь- зует 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. Даже в сверх- большой модели памяти Borland C++ требует, чтобы все функции сох- раняли регистр DS.


Значения простых идентификаторов


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

Значения идентификаторов, используемых непосредственно

Таблица 5.10 --------------------------T-------------------------------------¬ ¦ Выражение ¦ Значение ¦ +-------------------------+-------------------------------------+ ¦ имя_адреса ¦ Возвращает адрес. ¦ ¦ ¦ ¦ ¦ имя_числового_выражения ¦ Возвращает значение числового выра- ¦ ¦ ¦ жения. ¦ ¦ ¦ ¦ ¦ имя_таблицы | ¦ Возвращает значение по умолчанию ¦ ¦ имя_элемента_таблицы ¦ элемента таблицы, заданное в опре- ¦ ¦ ¦ делении таблицы. ¦ ¦ ¦ ¦ ¦ структура/ ¦ Возвращает смещение элемента в таб- ¦ ¦ имя_элемента_таблицы ¦ лице или структуре (только в режи- ¦ ¦ ¦ ме MASM). ¦ ¦ ¦ ¦ ¦ имя_записи ¦ Возвращает маску, в которой биты, ¦ ¦ ¦ зарезервированные для представления ¦ ¦ ¦ битовых полей в определении записи, ¦ ¦ ¦ равны 1, а остальные равны 0. ¦ ¦ ¦ ¦ ¦ имя_записи <.> ¦ Возвращает начальное значение эк- ¦ ¦ ¦ земпляра записи, которое имела бы ¦ ¦ ¦ эта запись, если бы она описывалась ¦ ¦ ¦ с текстом, заключенном в угловые ¦ ¦ ¦ скобки (подробности содержатся в ¦ ¦ ¦ Главе 12). ¦ ¦ ¦ ¦ ¦ имя_записи [.] ¦ Аналогично предыдущему. ¦ ¦ ¦ ¦ ¦ имя_поля_записи ¦ Возвращает число бит, на которое ¦ ¦ ¦ смещена запись от младшего бита за- ¦ ¦ ¦ писи (значение сдвига). ¦ ¦ ¦ ¦ ¦ имя_перечисления ¦ Возвращает маску, в которой биты, ¦ ¦ ¦ требуемые для представления макси- ¦ ¦ ¦ мального значения в определении пе- ¦ ¦ ¦ речисления равны 1, а остальные ¦ ¦ ¦ равны 0. ¦ ¦ ¦ ¦ ¦ имя_сегмента ¦ Возвращает значение сегмента. ¦ ¦ ¦ ¦ ¦ имя_группы ¦ Возвращает значение группы. ¦ ¦ ¦ ¦ ¦ имя_структуры/ ¦ Возвращает размер в байтах структу- ¦ ¦ объединения ¦ ры или объединения, но только в том ¦ ¦ ¦ случае, если этот размер равен 1, 2 ¦ ¦ ¦ или 4; в противном случае возвраща- ¦ ¦ ¦ ется 0. ¦ ¦ ¦ ¦ ¦ имя_типа ¦ Если тип определяется как синоним ¦ ¦ ¦ структуры или объединения, то возв- ¦ ¦ ¦ ращаемое значение то же, что и у ¦ ¦ ¦ структуры или объединения. В про- ¦ ¦ ¦ тивном случае возвращается размер ¦ ¦ ¦ типа (с 0ffffh для меток short и ¦ ¦ ¦ near и 0fffeh для меток far). ¦ L-------------------------+--------------------------------------

Все другие типы идентификаторов возвращают значение 0.

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