46019 (665326), страница 37
Текст из файла (страница 37)
Для того, чтобы идентификаторы были видимыми извне ассемблерного модуля, вы должны объявить их как PUBLIC.
Например, если высобираетесь написать модуль с целочисленными функциями max и min, а также целочисленными переменными MAXINT, lastmaxи lastmin, вам следует поместить в кодовый сегмент оператор
PUBLIC _max, _min
и операторы
PUBLIC _MAXINT, _lastmax, _lastmin
_MAXINT DW 32767
_lastmin DW 0
_lastmax DW 0
в сегмент данных.
TASM 2.0
Turbo Assemblrt 2.0 расширяет синтаксис многих директив, позволяя задавать опциональный спецификатор языка. Например, если вы укажете С в вашем модуля в директиве .MODEL, то все имена идентификаторов будут записываться в объектный модуль с ведущим символом подчеркивания. Это средство такжеможет работать на уровне директив. При помощи спецификатора языкаC в Turbo Assembler 2.0 вышеприведенные объявления можно переписать в виде:
PUBLIC C max, min
PUBLIC C MAXINT, lastmax, lastmin
MAXINT DW 32767
lastmin DW 0
lastmax DW 0
Подготовка к вызову Turbo C++ из .ASM
Для того, чтобы модуль на языке ассемблера мог обращаться к функциям и переменным программы на Turbo C++, следует использовать оператор EXTRN.
Ссылки к функциям
Для того, чтобы иметьвозможность вызвать функцию С из подпрограммы на языке ассемблера, вы должны объявить ее в ассемблерном модуле в операторе
EXTRN fname : fdist
где fname - это имя функции, а fdist - это либо near, либо far, в зависимости от того, является ли функция С near или far. Поэтому в кодовом сегменте может находиться оператор
EXTRN _myCfunc1:near, _myCfunc2:far
что позволяет вызывать myCfunc1 и myCfunc2 из подпрограмм на языке ассемблера.
TASM 2.0
Используя спецификатор языкаС в Turbo Assembler2.0 последний оператор можно переписать как:
EXTRN C mCfunc1:near, myCfunc2:far
Ссылки к данным
Для обращения к переменным следует поместить в сегмент данных соответствующий оператор(ы) EXTRN в формате:
EXTRN vname : size
где vname - это имя переменной, а size указывает размер переменной.
Размер переменной может быть следующим:
BYTE(1 байт) QWORD(8 байтов)
WORD(2 байта) TBYTE(10 байтов)
DWORD(4 байта)
Поэтому, если вС-программе имеются следующие глобальные переменные:
int i,jarray[10];
char ch;
long result;
то можно сделать их видимыми из вашего модуля при помощи следующего оператора:
EXTRN _i:WORD,_jarray:WORD,_ch:BYTE,_result:DWORD
либо при помощи спецификатора языка С в Turbo Assembler 2.0 (TASM 2.0):
EXTRN C i:WORD,jarray:WORD,ch:BYTE,result:DWORD
Важное замечание !
При использовании модели памяти huge операторы EXTRN должны находиться вне любых сегментов. Это относится как к функциям,так и к переменным.
Определение подпрограмм на языке ассемблера
Теперь, когда вы знаете, каквыполнить подготовительные установки, рассмотрим, как практически пишется функция на языке ассемблера. Здесь имеется несколько важных вопросов: передача параметров, возврат значений, и также использование нужных соглашений о регистрах.
Предположим, что вы хотите написать функцию min, для которой предполагается наличие соответствующего прототипа С:
extern int min(int v1, int v2);
Вы хотите, чтобы min возвращала минимальное из двух переданных ей значений. Общий формат min будет следующий:
PUBLIC _min
_min PROC NEAR
...
_min ENDP
Разумеется, это предполагает,что min является ближней функцией; если бы эта функция была дальней, вы бы подставилиFAR вместо NEAR. Отметим, что мы добавили передmin символ подчеркивания, благодаря чему компоновщик Turbo C++ может правильно разрешить ссылки. Если бы мы использовали в операторе PUBLIC спецификатор языка С Turbo Assembler 2.0, ассемблер позаботился бы об этом сам.
Передача параметров
Прежде всего, вы должны решить, какое соглашениео передаче параметров использовать; при отсутствииадекватной причины избегайте соглашения о передаче параметров Паскаля, соглашение С является предпочтительным. Это означает, что когда min вызвана, стек будет выглядеть следующим образом:
sp + 04: v2
sp + 02: v1
sp: адрес возврата
Вам требуется получить доступ к параметрам, не выполняя снятие со стека, поэтому вам следует сохранить указатель базы (BP), переслать указатель стека (SP) в указатель базы, а затем использовать последний для прямой индексации стека, что позволит получить необходимые значения. Отметим, что при помещении BP в стек относительные смещения параметров увеличатся на 2, поскольку стек теперь увеличится на два.
TASM 2.0
Turbo Assembler 2.0 обеспечивает простой способ обращения к параметрам функции и работы со стеком. Прочтите следующее; для вас важно понять, как работает адресация стека.
Обработка значений возврата
Ваша функция возвращает целочисленное значение; куда же оно помещается? Для 16-битовых (2-байтовых) значений (char, short, int, enum иближних указателей) используется регистр AX; для 32-битовых (4-байтовых) значений (включая указатели far и huge) используется также регистр DX, причем старшее слово (в случае указателей это адрес сегмента) помещается в DX, а младшее слово помещается в AX.
Значениятипа float, double и long double возвращаются через регистр "вершины стека" (TOS), ST(0); если используется эмулятор 80x87, то значение возвращается через регистр TOS эмулятора. Вызывающая функция должна скопировать это значениетуда, куда требуется.
Структуры длиной в 1 байт возвращаются через AL. Структуры длиной в 2 байта возвращаются через AX. Структуры длиной 4 байта возвращаются через DX:AX. Для возврата структур, имеющих размер 3 байта или более 5 байтов, онипомещаются в областьстатических данных и затем возвращается указатель на их адрес (через AX для моделей данных small и через DX:AX для моделей данных large). вызываемая подпрограмма должна скопировать значение возврата по адресу, задаваемому указателем.
В примере с функцией min выимеетедело с 16-битовым значением, поэтому ответ можно поместить непосредственно в AX.
Так будет выглядеть этот код теперь:
PUBLIC _min
_min PROC NEAR
push bp;записать bp в стек
mov bp,sp;скопировать sp в bp
mov ax,[bp+4];переслать v1 в ax
cmp ax,[bp+6];сравнить с v2
jle exit ;если v1 > v2
mov ax,[bp+6];то загрузить v2 в ax
exit: pop bp;восстановить bp
ret;и выполнить возврат в С
_min ENDP
Что, если вы объявитеmin как дальнюю (far) функцию - что изменится в результате этого? Главноеотличие будетсостоять в том, что стек на входе в подпрограмму будет выглядеть следующим образом:
sp + 06: v2
sp + 04: v1
sp + 02: сегмент возврата
sp: смещение возврата
Это означает, что смещения встек увеличились на два, поскольку теперь в стекпомещается дополнительно 2 байта (содержащие сегмент возврата). Версия min в случае far выглядит следующим образом:
PUBLIC _min
_min PROC FAR
push bp;записать bp в стек
mov bp,sp;скопировать sp в bp
mov ax,[bp+6];переслать v1 в ax
cmp ax,[bp+8];сравнить с v2
jle exit ;если v1 > v2
mov ax,[bp+6];то загрузить v2 в ax
exit: pop bp;восстановить bp
ret;и выполнить возврат в С
_min ENDP
Отметим,что все смещения для v1 и v2 увеличились на 2, что отражает дополнительно помещенные в стек два байта.
Что будет, если вы решите использовать последовательность передачи параметров Паскаля?
При входе ваш стек будет выглядеть теперь следующим образом (предполагая снова, что min является NEAR функцией):
SP + 04: v1
SP + 02: v2
SP: адрес возврата
Кроме того, вам придетсясоблюдать соглашения Паскалядля идентификатора min: он должен быть записан заглавными буквами и не иметь символа подчеркивания в начале.
Помимо того, что должны поменяться местами v1 и v2, это соглашение также подразумевает, что min должна очищать стекпри выходе, задавая в команде RET число байтов, которые должны сниматься со стека. В данном случае требуетсяснять со стека 4 дополнительных байтадля v1 и v2 (адрес возврата снимается со стека автоматически командой RET).
Вот как будет выглядеть модифицированная подпрограмма:
PUBLIC MIN
MIN PROC NEAR ;версия с соглашениями Паскаля
push bp;записать bp в стек
mov bp,sp;скопировать sp в bp
mov ax,[bp+6];переслать v1 в ax
cmp ax,[bp+4];сравнить с v2
jle exit ;если v1 > v2
mov ax,[bp+4];то загрузить v2 в ax
exit: pop bp;восстановить bp
ret 4;очистить стек и выполнить
;возврат в С
MIN ENDP
Приведем последний пример того,почемуможет понадобиться использование последовательностьпередачи параметров С. Предположим, вы переопределили min следующим образом:
int min (int count,...);
Теперь min может принимать любое число целочисленных параметров, возвращая минимальный из них. Однако, поскольку min не можетавтоматически определить число передаваемых ей параметров, можно сделать первый передаваемый параметр счетчиком , который будетуказывать число следующих за ним параметров. Например, вы можете использовать функцию следующим образом:
i = min(5, j, limit, indx, lcount, 0);
предполагая, что i, j, limit, indx и lcount имеют тип int (или любой совместимый с ним тип). Стек после входа в подпрограмму будет иметь вид:
sp + 08: (и т.д.)
sp + 06: v2
sp + 04: v1
sp + 02: count
sp: адрес возврата
Модифицированная версия min будет иметь теперь вид:
PUBLIC MIN
_min PROC NEAR
push bp;записать bp в стек
mov bp,sp;скопировать sp в bp
mov cx,[bp+4];переслать count в cx
cmp cx,0 ;сравнить с 0
jle exit ;если <= 0 то выход из подпрограммы
lea bx,[bp+6];установить bx
mov ax,[bx];переслать первое значение
imp ltest;проверить цикл
compare: cmp ax,[bx];сравнение
jle ltest;если след. значение
mov ax,[bx];то загрузить в ax...
ltest: add bx,2 ;переход к новому значению
loop compare;продолжение цикла
exit: pop bp;восстановить bp
ret;возврат в С
_min ENDP
Данная версии подпрограммы будет правильно обрабатыватьвсе возможные значения счетчика count:
- Если count <= 0, то min возвращает 0.
- Если count = 1, то min возвращает первое значение в списке.
- Если count >= 2, то min выполняет последовательность сравнений до последнего переданного ей в списке значения.
Теперь, когда вы понимаете, как нужно манипулировать стеком и умеете писать свои собственные функции,вы можете оценить некоторые новые расширения версии Turbo Assembler
2.0. Некоторые из них позволяют вам автоматически создавать имена переменных, устанавливать и очищать стек из PROC, а также легко выполнять доступ к параметрам, используя при этом соглашения того языка, на котором написана вызывающая процедура.
С учетомэтих расширений первая версия min (на стр.257 оригинала) может быть переписана следующим образом:
PUBLIC C MIN
min PROC C NEAR v1: WORD, v2: WORD
mov ax,v1
cmp ax,v2
jle exit
mov ax,v2
exit: ret
min ENDP
Версия с соглашениями Паскаля (стр.259 оригинала) может быть переписана в виде:
PUBLIC PASCAL MIN
min PROC PASCAL NEAR v1: WORD, v2: WORD
mov ax,v1
cmp ax,v2
jle exit
mov ax,v2
exit: ret
min ENDP
Отметим, что код в обоих случаях отличается только ключевым словом PASCAL вместо С, а в остальных он идентичен. Однако, код, фактически генерируемый ассемблером, соответствует исходным примерам. Полное описание этих новых средств, учитывающих конкретные языки при смешанномпрограммировании, см. в руководствах по Turbo Assembler.
Как и обычные процедуры и функции С, подпрограммы на языке ассемблера типа external должны соблюдать определенные правила программирования, чтобы с ними могла правильно работать программа управления оверлеями.
Если подпрограмма на языке ассемблера выполняет вызов любой оверлейной процедуры или функции, то эта подпрограмма должна быть дальней (far) и устанавливать стековый фрейм, используя для этого регистр BP. Более подробную информацию см. на стр.217 оригинала.
Соглашения о регистрах
В min было использовано несколько регистров (BP, SP, AX,BX, CX); было ли это использование безопасным? Как обстоит дело с регистрами, которые может использовать ваша программа на Turbo C++?
Оказывается, данная функция была написана верно. Изо всех используемых в ней регистров единственный регистр, окотором вы должны были специально позаботиться, это BP, и при входе в функцию вы сохраняли его в стеке, восстанавливая затем при выходе.
Два остальных регистра, на которые также следует обращать внимание, это SI и DI; Turbo C++ использует эти два регистрадля любыхрегистровых переменных. Есливы используете их в вашей ассемблерной подпрограмме, то при входе в нее следует сохранить эти регистры(возможно, в стеке),и затем восстановить их при выходе. Однако, при компиляции программы Turbo C++ сопцией-r(или при выключенной опцииRegister Variables диалогового поля Code Generation) вы можете не беспокоиться о сохранении SI и DI.
Примечание
При использовании опции -r- следует принимать меры предосторожности. См. Главу4, "Компилятор командной строки" в Руководстве пользователя, где данная опция описана подробно.
Регистры CS, DS, SS и ESпринимают конкретные значения, в зависимости от используемоймоделипамяти. Ниже приводится эта взаимозависимость: