Распределение памяти под локальные данные
Подпрограммы на ассемблере, вызываемые из Borland Pascal, могут выделять память для собственных переменных - как постоянных, которые используются в нескольких подпрограммах, так и временных, которые создаются только в какой-то одной процедуре.
19.4.1. Распределение памяти для постоянных локальных переменных
Borland Pascal позволяет ассемблерным программам резервировать пространство для данных в сегменте глобальных данных(DATA илиDSEG). В тексте ассемблерной программы такое выделение данных происходит с помощью директив типаDB, DW и т. п.), например:
DATA SEGMENT PUBLIC AInt DW ? AByte DB ?
DATA ENDS
Переменные, размещенные в глобальном сегменте данных, имеют два важных ограничения. Первое из этих ограничений заключается в том, что такие переменные являются внутренними (private) по отношению к ассемблерному модулю, поэтому из модуля Borland Pascal они недоступны (однако в Pascal-программу можно передать
указатель на эти данные). Второе ограничение — эти переменные нельзя инициализировать при объявлении. Например, оператор
AInt DW 42h
не будет порождать ошибки при трансляции и компоновке, однако во время работы программы заначение AInt при первом обращении будет неопределенным.
Эти ограничения можно обойти, применяя типизированные константы Borland Pascal с последующим их объявлением в ассемблерном модуле директивойEXTRN.
19.4.2. Распределение памяти для временных локальных переменных
Каждая ассемблерная процедура может размещать в стеке собственные локальные переменные, которые должны уничтожаться после передачи управления вызывающей процедуре.
В следующем примере процедура AsmProc резервирует место для двух перемен-н-ых целого типа а и Ь:
CODE SEGMENT
ASSUME cs:CODE AsmProc PROC FAR ,'procedure AsmProc(i:integer);
PUBLIC AsmProc
LOCAL a:word, b:word == LocalSpace ; а находится по адресу [bp-2],
; b - [bp-4] i EQU WORD PTR [BP+6]
push bp
mov bp.sp
sub sp, LocalSpace .•уменьшение SP для размещения локальных переменных
mov ax,42
mov a,ax
xor ax,ax
mov b,ax .•инициализация локальных переменных
mov sp.bp
pop bp
ret 2 AsmProc ENDP CODE ENDS
END
Оператор
LOCAL a:word, b:word = LocalSpace
ставит в соответствие^ идентификатору д значение [BP-2J, идентификатору b — значение [ВР-4|, идентификатору LocalSpace - число 4 (общий размер области локальных переменных в байтах) для дальнейшего использования в процедуре.
Наиболее эффективным методом инициализации локальных переменных является помещение в стек их значения вместо уменьшения SP. При таком подходе фрагмент инициализа1ц<и локальных переменных процедуры, приведенной в предыдущем примере, будет иметь следующий вид:
; sub sp, LocalSpace — оператор не нужен mov ах,42 push ax xor ax,ax push ax
При использовании этого метода необходимо проявлять осторожность, чтобы не испортить содержимое стека.
Если разрабатываемая программа будет работать только на моделях процессоров не-ниже 8018Г), то is ней можно использовать еще один метод инициализации локальных переменных, заключающийся в применении командыPUSH с непосредственным операндом. Кроме того, регистр BP можно сохранять не в стеке, а в каком-либо неиспользуемом в процедуре роистре, если таковой имеется.
19.5. Примеры применения ассемблерных подпрограмм в Borland Pascal
19.5.1. Многоцелевая подпрограмма представления чисел в виде шестнадцатеричных цифр
Эта подпрограмма выполняет преобразование параметра num в строку шестнадцатеричных цифр длиной (ByteCount*2). Для повышения скорости работы процедуры при преобразовании каждой тетрады (4 бита) байта в ней использовалась последовательность команд ADD-DAA-ADC-DAA.
HexStr написана на ассемблере как FAR-процедура, поэтому, если в тексте программы на Borland Pascal явно не указан тип FAR, то либо ее объявление должно находиться в интерфейсной секции модуля, либо при компиляции необходимо указывать директиву компилятора $F+.
Ассемблерный код процедуры (файл HEX.ASM):
CODE SEGMENT
ASSUME cs:CODE,ds:NOTHING
; Параметры (+2 для учета помещения в стек Ьр)
byteCount EQU BYTE PTR ss:[bp+6]
num EQU DWORD PTR ss:[bp+8]
; Адрес результата функции (+2 для учета помещения в стек Ьр)
resultPtr EQU DWORD PTR ss:[bp+12]
HexStr PROC FAR
PUBLIC HexStr
push bp
mov bp,sp ;попучить указатель на стек
;считывает по указанному адресу из памяти двойное слово,
;содержащие полный адрес, и загружает младшую половину в указанный регистр,
; а старшую в ES
les di,resultPtr ;получить адрес области результата функции
mov dx,ds ;сохранить DS Borland Pascal в DX
;считывает по указанному адресу из памяти двойное слово,
;содержащие полный адрес, и загружает младшую половину в указанный регистр,
; а старшую в DS
lds si,num ;получить адрес числа
mov al,byteCount ; количество байтов
xor ah,ah
mov cx,ax
add si,ax ; начать со старшего байта числа
dec si
; сдвиг влево всех битов операнда на 1 бит
shl ax,1 ;сколько цифр ? (2/байт)
; сброс флага направления DF в регистре флагов
cld ;запомнить номер цифры
hexloop:
; запись байта из АХ в строку данных
stosb ;запись длины строки результата HexLoop:
; установка флага направления DF в регистре флагов, определяя обратное ;направление выполнение строковых операций (по убыванию адресов элементов ;строки)
std ; просмотр числа от старшего ; байта к младшему
;загрузка байта из строки
lodsb ; получить следующий байт
mov ah,al ;сохранить его
;логический сдвиг вправо битов операнда на 1 бит
shr al,1 ; выделить старшую тетраду
shr al,1
shr al,1
shr al,1
add al,90h ;алгоритм преобразования
; корректировка операции слодения , если результат сложения превышает 99,
; то возникает перенос и устанавливается флаг CF
daa
; Сложение оперантов, прибавляя к результату значения флага CF
adc al,40h
daa ;тетрада преобразована в ASCII
; сброс флага направления DF в регистре флагов
cld
stosb
mov al,ah ;Преобразование младшей тетрады
and al,0Fh
add al,90h
daa
adc al,40h
daa
stosb
loop HexLoop
mov ds,dx ;восстановление DS
pop bp
ret 6 ;длина параметров 6 байт
HexStr ENDP
CODE ENDS
END
Программа на Borland Pascal, использующая данную функцию, которая нaxодится в объектном файле HEX.OBJ, может иметь следующий вид:
program HexTest;
var num: Word;
function HexStr (var num; ByteCount:byte):string; far; external;
{$L HEX}
begin
num:=12345;
Writeln(' Шестнадцатеричный вид числа ',num, '=',HexStr(num,SizeOf(num)));
end.
18.1.9. Соглашения о вызовах, принятых в Pascal
Как уже упоминалось раньше, при вызове функции в C++ выполняется помещение передаваемых параметров в стек в направлении слева направо, а по возвращению из нее — очистка стека от этих параметров. Помимо этих соглашений, Borland C++ поддерживает соглашения языка Pascal, согласно которым параметры передаются в процедуру слева направо, а стек очищает вызываемая функция. Включение соглашений языка Pascal достигается применением переключателя командной строки -р или ключевого слова PASCAL.
Пример использования соглашений языка Pascal в ассемблерных функциях:
'; Вызов: TEST (i,|, k);
i equ 8 j equ 6 k equ 4
.MODELL small .CODE PUBLIC TEST TEST PROC push bp mov bp.sp mov ax,[bp+i] add ax,[bp+j] sub ax,[bp+k] pop bp
ret 6 [возврат и очистка стека TEST ENDP END
На рис. 18.4 приведен вид стека после выполнения команды MOV BP,SP.
Кроме особенностей передачи параметров, соглашения языка Pascal подразумевают использование в идентификаторах всех внешних и общедоступных переменных прописных букв без предшествующего символа подчеркивания.
Причиной, по которой могут использоваться соглашения языка Pascal, является уменьшение размера кода и увеличение скорости выполнения, так как обычный код C++ генерирует дополнительные команды вида ADD SP, n для очистки стека после выполнения процедуры.
Рис. 18.4. Вид стека при использовании соглашений языка Pascal.
|