Глава 10. Описание процедур
Турбо Ассемблер позволяет вам описывать процедуры нескольки-
ми способами. В данной главе описываются процедуры NEAR и FAR,
объявление языка процедур, использование в процедурах аргументов
и переменных, сохранение регистров, вложенные процедуры и описа-
ние процедур методов для объектов.
Синтаксис определения процедур
Для описания процедур вы можете использовать директиву PROC.
В режиме Ideal она имеет следующий синтаксис:
PROC [[модификатор_языка] язык] имя [расстояние]
[ARG список_аргументов] [RETURN список_элементов];
[LOCAL список_аргументов]
[USES список_элементов]
.
.
.
ENDP [имя]
В режиме MASM используется следующий синтаксис:
имя PROC [[модификатор_языка] язык] [расстояние]
[ARG список_аргументов] [RETURN список_элементов];
[LOCAL список_аргументов]
[USES список_элементов]
.
.
.
[имя] ENDP
Турбо Ассемблер также воспринимает для определения процедур
синтаксис MASM. Подробнее о синтаксисе MASM рассказывается в Гла-
ве 3.
Описание процедур NEAR или FAR
Процедуры NEAR вызываются с помощью вызова ближнего типа и
содержат ближний возврат управления. Вы должны вызывать их только
в том же сегменте, в котором они определены. Вызов ближнего типа
заносит адрес возврата в стек и устанавливает указатель инструк-
тор (IP) в значение смешения процедуры. Поскольку сегмент кода
(CS) не изменяется, процедура должна находиться в том же сегмен-
те, что и вызывающая программа. Когда процессор обнаруживает
возврат ближнего типа, он извлекает из стека адрес возврата и
снова устанавливает в него IP. Сегмент кода не изменяется.
Процедура FAR вызывается с помощью вызова дальнего типа и
содержит возврат дальнего типа. Процедуры FAR вы можете вызывать
вне сегмента, в котором они определяются. Вызов FAR заносит в
стек адрес в виде сегмента и смещения, а затем устанавливает
CS:IP в адрес процедуры. Когда процессор обнаруживает возврат
дальнего типа, он извлекает из стека сегмент и смещение адреса
возврата и устанавливает в него CS:IP.
Расстояние (NEAR или FAR), используемое в процедуре по умол-
чанию, определяется текущей выбранной моделью. Для моделей TINY,
SMALL и COMPACT по умолчанию процедура будет ближней (NEAR). Для
всех других моделей по умолчанию выбирается расстояние FAR. Если
вы не используете упрощенные директивы определения сегментов, то
по умолчанию процедура всегда будет ближней (NEAR).
Примечание: FAR или NEAR можно задать в качестве аргу-
мента оператора MODEL. Более подробно об этом рассказывает-
ся в Главе 7.
Вы можете переопределить используемое по умолчанию расстоя-
ние, задав нужное расстояние в определении процедуры. Для этого
вы можете использовать ключевые слова NEAR или FAR. Эти ключевые
слова переопределяют расстояние, используемое в процедуре по
умолчанию, но только для текущей процедуры. Например:
.
.
.
MODEL TINY ; по умолчанию расстояния NEAR
.
.
. ; test1 - это дальняя процедура
test1 PROC FAR
; тело процедуры
RET ; это будет дальним возвратом:
ENDP
; test2 по умолчанию является
; ближней процедурой
test2 PROC
; тело процедуры
RET ; это будет ближним возвратом
ENDP
.
.
.
В процедурах NEAR и FAR используется одна и та же инструкция
RET. Турбо Ассемблер использует расстояние процедуры для опреде-
ления того, требуется возврат ближнего или дальнего типа. Анало-
гично, Турбо Ассемблер использует расстояние процедуры для опре-
деления того, требуется для ссылки на процедуру возврат ближнего
или дальнего типа.
.
.
.
CALL test1 ; это дальний возврат
CALL test2 ; это ближний возврат
.
.
.
При выполнении вызова процедуры с опережающей ссылкой Турбо
Ассемблеру может потребоваться для определения расстояния проце-
дуры выполнить несколько проходов. Например:
.
.
.
test1 PROC NEAR
MOV ax,10
CALL test2
RET
test1 ENDP
test1 PROC FAR
ADD ax,ax
RET
test2 ENDP
.
.
.
Когда Турбо Ассемблер при первом проходе достигает инструк-
ции call test2, он еще не обнаруживает test2, и следовательно не
знает расстояния. Он предполагает, что это расстояние NEAR, и что
можно сделать ближний вызов.
Когда Турбо Ассемблер обнаруживает, что test2 является на
самом деле дальней процедурой, он определяет, что для корректной
генерации вызова требуется второй проход. Если вы разрешаете нес-
колько проходов (с помощью параметра-переключателя командной
строки /m), то можно сделать второй проход. Если вы не разрешаете
несколько проходов, то Турбо Ассемблер будет выводить ошибку
'forward reference needs override' ('опережающая ссылка требует
переопределения').
Чтобы избежать такой ситуации (и уменьшить число проходов),
вы можете задать в вызове расстояние процедур с опережающей ссыл-
кой, как NEAR PTR и FAR PTR.
.
.
.
test1 PROC NEAR
mov AX,10
CALL FAR PTR test2
RET
test1 ENDP
.
.
.
В предыдущем примере Турбо Ассемблеру сообщается, что нужно
использовать дальний вызов, поэтому не возникает необходимость в
нескольких проходах.
Описание языка процедуры
Вы можете легко определить процедуры, которые используют в
Турбо Ассемблере соглашения по интерфейсу языков высокого уровня.
Соглашения по интерфейсу поддерживаются для языков NOLANGUAGE
(Ассемблер), BASIC, PROLOG, FORTRAN, C, CPP (C++) и PASCAL.
Турбо Ассемблер выполняет всю работу по генерации корректно-
го кода начала (вход в процедуру) и завершения (выход из процеду-
ры), необходимых для соблюдения соглашений заданного языка.
С помощью директивы MODEL вы можете задать язык, используе-
мый по умолчанию. Подробности можно найти в Главе 7. Если
используемый по умолчанию язык задан, все процедуры, для которых
не задается другой язык, используют соглашения назначенного по
умолчанию языка.
Чтобы переопределить используемый по умолчанию язык для
конкретной процедуры, включите имя языка в определение процедуры.
Вы можете задать язык процедуры, включив описывающее язык ключе-
вое слово в описание процедуры. Например, определение в режиме
MASM процедуры PASCAL может иметь вид:
.
.
.
pascalproc PROC PASCAL FAR
; тело процедуры
pascalproc ENDP
.
.
.
Турбо Ассемблер использует заданный в процедуре язык для оп-
ределения того, какой вход начала и завершения нужно автоматичес-
ки включить в тело процедуры. Начальный код устанавливает кадр
стека для передаваемых аргументов и локальных переменных процеду-
ры, а код завершения перед возвратом из процедуры восстанавливает
кадр стека.
Турбо Ассемблер автоматически вставляет в процедуру началь-
ный код перед первой инструкцией процедуры или перед первой мет-
кой ("метка: цель").
В коде завершения делается следующее:
- в стеке сохраняется текущий регистр BP;
- BP настраивается на текущий указатель стека;
- настраивается указатель стека для выделения локальных пе-
ременных;
- в стеке сохраняются регистры, заданные спецификатором
USES.
По каждой инструкции RET процедуры Турбо Ассемблер автома-
тически вставляет в процедуру код завершения (в случае нескольких
инструкций RET код завершения будет вставляться несколько раз).
Турбо Ассемблер также вставляет код завершения перед переходом на
любой объектно-ориентированный метод (см. Главу 4).
Завершающий код изменяет действие кода инициализации на об-
ратное. Это делается следующий образом:
- из стека извлекаются регистры, заданные оператором USES;
- настраивается указатель стека, чтобы отбросить локальные
аргументы;
- из стека извлекается сохраненный регистр BP;
- стек настраивается, чтобы отбросить переданные аргументы
(если этого требует язык) и выполнить возврат.
Последний шаг кода завершения (отбрасывание аргументов) вы-
полняется только для тех языков, которые требуют удаления аргу-
ментов процедуры (например, Бейсик, Фортран, Паскаль). По согла-
шениям, принятым в других языках (Си, С++, Пролог), аргументы ос-
таются в стеке, и удалять их должна вызывающая программа.
Турбо Ассемблер всегда реализует код начала и завершения
процедуры с помощью наиболее эффективных инструкций текущего выб-
ранного языка и процессора.
Для процедур NOLANGUAGE Турбо Ассемблер не генерирует код
начала и завершения. Если в таких процедурах ожидается передача
аргументов в стеке, вы должны сами включать код начала и заверше-
ния.
В общем случае язык процедуры влияет на процедуру следующим
образом:
Как язык влияет на процедуру Рис. 10.1
----------T------T------T--------T--------T-------T------T------¬
¦ Язык ¦ Нет ¦Бейсик¦ Фортран¦ Паскаль¦ Си ¦ С++ ¦Пролог¦
+---------+------+------+--------+--------+-------+------+------+
¦ Порядок ¦ L-R ¦ L-R ¦ L-R ¦ L-R ¦ R-L ¦ R-L ¦ R-L ¦
¦ аргу- ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
¦ ментов ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
¦ (слева ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
¦ -напра- ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
¦ во, ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
¦ справа- ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
¦ налево) ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
+---------+------+------+--------+--------+-------+------+------+
¦Кто очи- ¦проце-¦проце-¦процеду-¦процеду-¦вызыва-¦вызы- ¦вызы- ¦
¦щает стек¦дура ¦дура ¦ра ¦дура ¦ющая ¦вающая¦вающая¦
¦(вызыва- ¦ ¦ ¦ ¦ ¦прог- ¦прог- ¦прог- ¦
¦ющая ¦ ¦ ¦ ¦ ¦рамма ¦рамма ¦рамма ¦
¦программа¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
¦или про- ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
¦цедура) ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
L---------+------+------+--------+--------+-------+------+-------
L-R - слева-направо, R-L - справа-налево.
Для включения в файл листинга кода начала и завершения вы
можете использовать параметр командной строки /la. Это позволит
вам увидеть разницу между языками.
Примечание: Более подробную информацию можно найти в
Главе 13.
Задание модификатора языка
Модификаторы языка указывают сообщают Турбо Ассемблеру, что
в процедуры нужно включать специальный код начала и завершения,
организующий интерфейс с 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.
Определения аргументов и локальных переменных
Турбо Ассемблер передает аргументы процедурам языка высокого
уровня в кадре стека, занося аргументы в стек перед вызовом про-
цедуры. Когда в процедуре языка требуются аргументы, она считыва-
ет их из стека. Когда процедура возвращает управление, она либо
удаляет аргументы из стека (соглашения по вызову Паскаля), либо
предполагает, что аргументы удаляются из стека вызывающей прог-
раммы (соглашения по вызову языка Си).
В описании процедуры передаваемые в процедуру через кадр
стека аргументы задаются директивой ARG. Аргументы имеют внутрен-
нее представление в виде положительных смещений от регистров BP
или EBP.
Языковые соглашения процедуры определяют, будут аргументы
заносится в стек в прямом или обратном порядке. В списке аргумен-
тов директивы ARG аргументы нужно указывать в том порядке, в ко-
тором они указываются в описании процедуры на языке высокого
уровня.
Директива LOCAL в описании процедуры задает в кадре стека
переменные, локальные для процедуры. Аргументы имеют внутреннее
представление в виде отрицательных смещений от регистра BP или
EBP.
Выделить пространство для локальных переменных кадра стека
можно с помощью включения в процедуру кода инициализации, смещаю-
щего вниз указатель стека на нужную величину. Код завершения про-
цедуры должен отбрасывать это лишнее пространство, восстанавливая
указатель стека. (Когда процедура подчиняется любым языковым сог-
лашениям, отличным от NOLANGUAGE, Турбо Ассемблер автоматически
генерирует этот код завершения.)
Нужно помнить о том, что Турбо Ассемблер предполагает, что
процедура, использующая аргументы кадра стека, содержит соответс-
твующий код инициализации, устанавливающий регистр BP или EBP.
(Когда процедура подчиняется любым языковым соглашениям, отличным
от NOLANGUAGE, Турбо Ассемблер автоматически генерирует этот код
завершения.) Даже если процедура использует языковые соглашения
NOLANGUAGE, задавайте аргументы и локальные переменные процедуры
с помощью директив ARG и LOCAL. Однако в этом случае код начала
(код инициализации) и завершения автоматически не генерируется.
Синтаксис директив ARG и LOCAL
Приведем синтаксис определения передаваемых процедуре аргу-
ментов:
ARG аргумент [,аргумент] . [=идентификатор]
[RETURNS аргумент] [,аргумент]]
При определении локальных переменных процедуры используется
следующий синтаксис:
LOCAL аргумент [,аргумент] . [=идентификатор]
Отдельные аргументы имеют следующий синтаксис:
имя_аргумента [[выражение_счетчик_1]]
[: сложный_тип [:выражение_счетчик_2]]
где "сложный_тип" - это тип данных аргумента. Он может быть либо
простым типом, либо сложным выражением-указателем. Подробнее о
синтаксисе сложных типов рассказывается в Главе 5.
Если вы не задаете поле "сложный_тип", Турбо Ассемблер пред-
полагает WORD. При выборе 32-разрядной модели он предполагает
DWORD.
"Выражение_счетчик_2" задает, сколько элементов данного типа
определяет аргумент. Например, в определении аргумента:
ARG tmp:DWORD:4
определяется аргумент с именем "tmp", состоящий из 4 двойных
слов.
По умолчанию "выражение_счетчик_2" имеет значение 1 (кроме
аргументов типа BYTE. Так как вы не можете занести в стек байто-
вое значение, для аргументов типа BYTE значение счетчика по умол-
чанию равно 2, что обеспечивает для них в стеке размер в слово.
Это согласуется с языками высокого уровня, которые интерпретируют
передаваемые в качестве параметров символьные переменные. Если вы
действительно хотите задать аргумент, как один байт в стеке, нуж-
но явным образом определить значение поля "выражение_счетчик_2",
равное 1. Например:
ARG realbyte:BYTE:1
"Выражение_счетчик_1" представляет собой число элементов
массива. Общее пространство, резервируемое для аргумента в стеке,
равно произведению "выражения_счетчик_2" на длину, заданную полем
"тип_аргумента" и на "выражение_счетчик_1". Если поле "выражение_
счетчик_1" не задано, то по умолчанию оно равно 1. Общее число
аргументов задает произведение "выражения"_счетчик_1" на "выраже-
ние_счетчик_2".
Если вы завершаете список аргументов символом равенства (=)
и идентификатором, то Турбо Ассемблер будет приравнивать этот
идентификатор к общему размеру блока аргументов (в байтах). Если
вы не используете автоматическое использование соглашений языков
высокого уровня в Турбо Ассемблере, то можете использовать данное
значение в конце процедуры в качестве аргумента инструкции RET.
Заметим, что это вызывает очистку стека от всех занесенных туда
перед возвратом аргументов (это соглашения по вызову, принятые в
Паскале).
Аргументы и переменные определяются в процедуре как операнды
в памяти относительно BP. Передаваемые аргументы, определенные с
помощью директивы ARG, имеют положительное смещение относительно
BP. Локальные переменные, определенные с помощью директивы LOCAL,
имеют отрицательное смещение от BP. Приведем пример:
.
.
.
func1 PROC NEAR
ARG a:WORD,b:WORD:4,c:BYTE=d
LOCAL x:DWORD,y=WORD:2=z
.
.
.
Здесь a определяется, как [bp+4], b определяется, как
[bp+6], c определяется, как [bp+14], а d - как 20. x - это
[bp-2], y - [bp-6], а z - 8.
Область действия аргументов и имен локальных переменных
Если вы не задаете для них имена с предшествующий префиксом
локального идентификатора, все аргументы, заданные в заголовке
процедуры, определены ли они с помощью директивы ARG (передавае-
мые аргументы), RETURN (возвращаемые аргументы) или LOCAL (ло-
кальные переменные) имеют глобальную область действия.
Идентификаторы с локальной областью действия разрешает дирек-
тива LOCALS. Например:
.
.
.
LOCALS
test1 PROC PASCAL FAR
ARG @a:WORD,@d:WORD,@c:BYTE
LOCAL @x:WORD,@y:DWORD
MOV ax,@a
MOV @x,ax
LES di,@b
MOV WORD ptr @y,di
MOV WORD ptr @y+2,es
MOV @c,'a'
RET
ENDP
test2 PROC PASCAL FAR
ARG @a:DWORD,@b:BYTE
LOCAL @x:WORD
LES di,@a
MOV ax,es:[di]
MOV @x,ax
CMP a1,@b
jz @dn
MVO @x,0
@dn: MOV ax,@x
RET
ENDP
.
.
.
Примечание: Об управлении областью действия идентифи-
каторов подробнее рассказывается в Главе 11.
Поскольку в данном примере используются переменные локальной
области действия, их имена существуют только в теле процедуры.
Таким образом, в test2 можно снова использовать имена @a, @b и
@x.
Сохранение регистров
Большинство языков высокого уровня требуют, чтобы вызываемые
процедуры сохраняли определенные регистры. Это можно сделать, за-
нося их в стек в начале процедуры и извлекая из стека в конце
процедуры.
Турбо Ассемблер может автоматически генерировать код для
сохранения и восстановления этих регистров, который включается в
код начала и завершения. Эти регистры можно указать в операторе
USES, который имеет следующий синтаксис:
USES элемент [,элемент] .
где "элемент" может быть регистром или состоящим из одной лексемы
элементом данных, который можно заносить и извлекать из стека. В
одной процедуре допускается использовать не более 8 элементов.
Например:
.
.
.
myproc PROC PASCAL NEAR
ARG @source:DWORD,@dest:DWORD,@count:WORD
USES cx,si,di,foo
MOV cx,@count
MOV foo,@count
LES di,@dest
LDS si,@source
REP MOVSB
ENDP
.
.
.
О сохранении регистров в языках Си и Паскаль можно подробнее
узнать в Главе 18 и 19.
Оператор USES можно использовать только в тех процедурах,
которые используют языковые соглашения, отличные от NOLANGUAGE.
Вложенные процедуры и правила области действия
Хотя вы можете вкладывать одну процедуру в другую, все про-
цедуры имеют глобальную область действия, Например:
.
.
.
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, а таких идентификаторов не существует.
Описание процедур методов для объектов
При создании процедур методов для объектов применяются неко-
торые специальные соглашения. Процедуры методов объектов должны
иметь возможность должны иметь возможность доступа к объекту, с
которым они работают. Таким образом, в качестве параметра проце-
дуры должен использоваться указатель на объект.
Турбо Ассемблер интерпретирует объекты достаточно гибко и
позволяет использовать для передачи аргументов процедурам методов
разнообразные соглашения. Эти соглашения ограничиваются только
необходимостью взаимодействовать с объектами, создаваемыми на
языке высокого уровня.
Если вы пишете процедуру метода объекта на самом языке Ас-
семблера, то может оказаться полезным использование соглашения,
по которому аргументы передаются через регистры. В этом случае
вам следует написать процедуру метода, воспринимающую указатель
на объект в регистре или паре регистров ES:DI).
Если вы пишете процедуру метода, которая использует соглаше-
ния по интерфейсу языка высокого уровня, то процедура должна
воспринимать указатель объекта в одном из аргументов. Указатель
объекта, передаваемый из объектно-ориентированных языков высокого
уровня (таких как С++), представляет собой неявный аргумент, по-
мещаемый в начало списка аргументов. Процедура метода, написанная
на языке Ассемблера, должна явно включать в список аргументов
указатель объекта. В противном случае вы можете получить непред-
виденные результаты. Нужно помнить, что в зависимости от того,
является ли объект ближним (NEAR) или дальним (FAR), указатель
может быть величиной WORD или DWORD.
Когда вы пишете на языке Ассемблера конструктор или деструк-
тор, могут возникнуть другие сложности. Чтобы указать, что конс-
труктор или деструктор должен выполнять определенные действия,
С++ использует (при некоторых обстоятельствах) использует другие
неявные аргументы.
Примечание: Об используемых в С++ соглашениях по вызо-
ву рассказывается в Главе 18.
Конструкторам, написанным на языке Ассемблера, не обязатель-
но требуется передавать указатель на объект. Если объект никогда
не распределяется статически, то конструктор объекта всегда будет
выделять для объекта память из динамически распределяемой облас-
ти.
Назад | Содержание | Вперед