 |
mov [bx],al ; заполнить следующий байт
inc bx ; ссылка на следующий байт
loop FillLoop ; обработать следующий байт,
; если он имеется
mov sp,bp ; освободить память,
; выделенную для динамичес-
; ких локальных переменных
; (можно также использовать
; add sp,AUTO_SIZE)
pop bp ; восстановить указатель
; стека вызывающей программы
Турбо Ассемблер 3.0/tasm/#2-2 = 99 =
ret
_TestSub ENDP
.
.
.
В данном примере следует обратить внимание не то, что первое
поле после определения данной динамической локальной переменной
представляет собой тип данных для этой переменной: BYTE, WORD,
DWORD, NEAR и т.д. Второе поле после определения данной динами-
ческой локальной переменной - это число элементов указанного ти-
па, резервируемых для данной переменной. Это поле является необя-
зательным и определяет используемый динамический локальный массив
(если он используется). Если данное поле пропущено, то резервиру-
ется один элемент указанного типа. В итоге LocalArray состоит из
100 элементов размером в 1 байт, а LocalCount - из одного элемен-
та размером в слово (см. пример).
Отметим также, что строка с директивой LOCAL в данном приме-
ре завершается полем =AUTO_SIZE. Это поле, начинающееся со знака
равенства, необязательно. Если оно присутствует, то метка, следу-
ющая за знаком равенства, устанавливается в значение числа байт
требуемой динамической локальной памяти. Вы должны затем исполь-
зовать данную метку для выделения и освобождения памяти для дина-
мических локальных переменных, так как директива LABEL только ге-
нерирует метки и не генерирует никакого кода или памяти для
данных. Иначе говоря, директива LOCAL не выделяет память для ди-
намических локальных переменных, а просто генерирует метки, кото-
рые вы можете использовать как для выделения памяти, так и для
доступа к динамическим локальным переменным.
Очень удобное свойство директивы LOCAL заключается в том,
что область действия меток динамических локальных переменных и
общего размера динамических локальных переменных ограничена той
процедурой, в которой они используются, поэтому вы можете свобод-
но использовать имя динамической локальной переменной в другой
процедуре.
Как можно заметить, с помощью директивы LOCAL определять и
использовать автоматические переменные намного легче. Отметим,
что при использовании в макрокомандах директива LOCAL имеет со-
вершенно другое значение.
Кстати, Borland C++ работает с границами стека так же, как
мы здесь описали. Вы можете скомпилировать несколько модулей
Borland C++ с параметром -S и посмотреть, какой код Ассемблера
генерирует Borland C++ и как там создаются и используются границы
стека.
Все это прекрасно, но здесь есть некоторые трудности.
Во-первых, такой способ доступа к параметрам, при котором исполь-
зуется постоянное смещение относительно BP достаточно неприятен:
при этом не только легко ошибиться, но если вы добавите другой
Турбо Ассемблер 3.0/tasm/#2-2 = 100 =
параметр, все другие смещения указателя стека в функции должны
измениться. Предположим, например, что функция Test воспринимает
три параметра:
Test(Flag, i, j, 1);
Тогда i находится по смещению 6, а не по смещению 4, j - по
смещению 8, а не 6 и т.д. Для смещений параметров можно использо-
вать директиву EQU:
.
.
.
Flag EQU 4
AddParm1 EQU 6
AddParm2 EQU 8
SubParm1 EQU 10
mov ax[bp+AddParm1]
add ax,[bp+AddParm1]
sub ax,[bp+SubParm1]
.
.
.
но вычислять смещения и работать с ними довольно сложно. Однако
здесь могут возникнуть и более серьезные проблемы: в моделях па-
мяти с дальним кодом размер занесенного в стек адреса возврата
увеличивается на два байта, как и размеры передаваемых указателей
на код и данные в моделях памяти с дальним кодом и дальними дан-
ными, соответственно. Разработка функции, которая с равным успе-
хом будет ассемблироваться и правильно работать с указателем сте-
ка при использовании любой модели памяти было бы весьма непростой
задачей.
Однако в Турбо Ассемблере предусмотрена директива ARG, с по-
мощью которой можно легко выполнять передачу параметров в прог-
раммах на Ассемблере.
Директива ARG автоматически генерирует правильные смещения в
стеке для заданных вами переменных. Например:
ARG FillArray:WORD, Count:WORD, FillValue:BYTE
Здесь задается три параметра: FillArray, параметр размером в
слово, Count, также параметр размером в слово и FillValue - пара-
метр размером в байт. Директива ARG устанавливает метку
FillArray в значение [BP+4] (подразумевается, что код находится
в процедуре ближнего типа), метку Count - в значение [BP+6], а
метку FillValue - в значение [BP+8]. Однако особенно ценна дирек-
тива ARG тем, что вы можете использовать определенные с ее по-
мощью метки не заботясь о тех значениях, в которые они установле-
ны.
Турбо Ассемблер 3.0/tasm/#2-2 = 101 =
Например, предположим, что у вас есть функция FillSub кото-
рая вызывается из С++ следующим образом:
extern "C" {
void FillSub(
char *FillArray,
int Count,
char FillValue);
}
main()
{
#define ARRAY_LENGTH 100
char TestArray[ARRAY_LENGTH];
FillSub(TestArray,ARRAY_LENGTH,'*');
}
В FillSub директиву ARG для работы с параметрами можно ис-
пользовать следующим образом:
_FillSub PROC NEAR
ARG FillArray:WORD, Count:WORD, FillValue:BYTE
push bp ; сохранить указатель стека
; вызывающей программы
mov bp,sp ; установить свой собственный
; указатель стека
mov bx,[FillArray] ; получить указатель на
; заполняемый массив
mov cx,[Count] ; получить заполняемую длину
mov al,[FillValue] ; получить значение-заполнитель
FillLoop:
mov [bx],al ; заполнить символ
inc bx ; ссылка на следующий символ
loop FillLoop ; обработать следующий символ
pop bp ; восстановить указатель стека
; вызывающей программы
ret
_FillSub ENDP
Не правда ли, удобно работать с параметрами с помощью дирек-
тивы ARG? Кроме того, директива ARG автоматически учитывает раз-
личные размеры возвратов ближнего и дальнего типа.
Сохранение регистров
ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
При взаимодействии Турбо Ассемблера и Borland C++ вызываемые
из программы на языке С++ функции Ассемблера могут делать все что
угодно, но при этом они должны сохранять регистры BP, SP, CS, DS
и SS. Хотя при выполнении функции Ассемблера эти регистры можно
изменять, при возврате из вызываемой подпрограммы они должны
иметь в точности такие значения, какие они имели при ее вызове.
Турбо Ассемблер 3.0/tasm/#2-2 = 102 =
Регистры AX, BX, CX, DX и ES, а также флаги могут произвольно из-
меняться.
Регистры DI и SI представляют собой особый случай, так как в
Borland C++ они используются для регистровых переменных. Если в
модуле С++, из которого вызывается ваша функция на Ассемблере,
использование регистровых переменных разрешено, то вы должны сох-
ранить регистры SI и DI, если же нет, то сохранять их не нужно.
Однако неплохо всегда сохранять эти регистры, независимо от
того, разрешено или запрещено использование регистровых перемен-
ных. Трудно заранее гарантировать, что вам не придется компоно-
вать данный модуль Ассемблера с другим модулем на языке С++, или
перекомпилировать модуль С++ с разрешением использования регист-
ровых переменных. При этом вы можете забыть, что изменения нужно
также внести и в код Ассемблера.
Возврат значений
|