Реферат: Turbo C++ Programer`s guide
Как и обычные процедуры и функции С, подпрограммы на языке
ассемблера типа 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принимают конкретные значения, в
зависимости от используемоймоделипамяти. Ниже приводится эта взаимозависимость:
Tiny CS
= DS = SS
ES = рабочий
Small, Medium CS
!= DS, DS = SS
ES = рабочий
Compact,
Large CS != DS != SS
ES = рабочий
(один CS на модуль)
Huge CS
!= DS != SS
ES = рабочий
(один CS и один DS на модуль)
Вы можете установить DS не равным SS для моделей
tiny, small и medium, задавая опции компилятора командной строки - mtl, -msl и
-mml. См. Главу 4, "Компилятор командной строки" в Руководстве
пользователя, где эти опции описаны подробно.
TASM 2.0
Turbo Assembler2.0 позволяетзадавать это (DS != SS)
при использовании упрощенных сегментных директив и модификатора модели в
директиве .MODEL.
Вызов функций С из модулей .ASM
Вы можете поступитьи следующимобразом: вызывать
подпрограммы на С из модулей на языке ассемблера. Прежде всего, для этого вы
должны сделать функцию С видимой для модуля на языке ассемблера. Мы уже кратко рассматривали,
как это делается: функция должна быть объявлена как EXTRN и иметь модификатор
либо near, либо far. Например, вы написали следующую функцию С:
long docalc(int *fact1, int
fact2, int fact3);
Для простоты предположим, что docalc является
функцией С (а не Паскаля).Предполагая, что данная функция использует модель
памяти tiny, small или compact, следует сделать соответствующее объявление в
вашем ассемблерном модуле:
EXTRN _docalc:near
Аналогичным образом, если функция использует модели памяти medium, large
или huge, то онадолжна иметь объявление
_docalc:far.
TASM 2.0
Используя в Turbo Assembler 2.0 спецификатор языка
С, эти объявления можно переписать в виде
EXTRN C docalc:near
и
EXTRN C docalc:far
docalc должна вызываться с тремя
параметрами:
- адресом памяти с именем xval
- значением, хранимым в адресе
памяти с именем imax
- третьим значением - константой
421 (десятичной)
Предположим также, что вы собираетесь сохранить
результат в 32-битовом адресе памяти сименем ans. Эквивалентный вызов в С имеет
вид:
ans =
docalc(&xval,imax,421);
Сначала вы должны поместить в стек константу 421, затем imax и наконец,
адрес xval, после чего вызвать docalc. После возврата вы должны очистить стек,
в котором будет находиться лишних шесть байтов, а потом переслать ответ по
адресу ans и ans+2.
Код будет иметь следующий вид:
mov ax,421
;взять 421 и поместить в стек
push ax
push imax
;взять imax и поместить в стек
lea ax,xval
;взять &xval и поместить в стек
push ax
call _docalc
;вызвать docalc
add sp,6
;очистить стек
mov ans,ax
;переслать в ans 32-битовый результат
mov ans+2,dx ;включая
старшее слово
TASM 2.0
Turbo Assembler версии 2.0 включает в себя
несколько расширений, которые упрощают интерфейс между модулями на С и на языке
ассемблера. Некоторые изэтих расширений позволяют автоматически создавать имена
в стиле, свойственном С, помещать параметры в стек втой последовательности, что
принята в С, и очищать стек после вызова функции наС. Например, подпрограмму
docalc можно переписать в виде:
EXTRN C docalc:near
mov bx,421
lea ax,xval
calc docalc
C ax,imax,bx
mov ans,ax
mov ans+2,dx
Полное описаниеэтих новых средств см. в руководствах по Turbo Assembler
2.0.
Как быть, еслиdocalcиспользует соглашениео передаче
параметров Паскаля? В этом случае вамнужно изменить на противоположный порядок
передачи параметров и не выполнять очистку стека после возврата, поскольку
подпрограмма сделает это завас сама.Кроме того, имя docalc должно быть записано
в исходном ассемблерном коде по правилам Паскаля (т.е. заглавными буквами и без
ведущего символа подчеркивания).
Оператор EXTRN будет иметь
следующий вид:
EXTRN DOCALC:near
а сам код, вызывающий docalc:
lea ax,xval
;взять &xval и поместить в стек
push ax
push imax
;взять imax и поместить в стек
mov ax,421
;взять 421 и поместить в стек
push ax
call DOCALC
;вызвать docalc
mov ans,ax
;переслать в ans 32-битовый результат
mov ans+2,dx ;включая
старшее слово
Turbo Assembler версии 2.0 включает в себя
несколько расширений, которыеупрощают интерфейс между модулями с соглашениями
Паскаля и на языке ассемблера, включая автоматическое создание имен в стиле,
свойственном Паскалю, и помещение параметров в стек в той последовательности,
что принята в Паскале. Например, подпрограмму docalc можно переписать в виде:
EXTRN PASCAL docalc:near
lea ax,xval
mov bx,421
calc docalc
PASCAL ax,imax,bx
mov ans,ax
mov ans+2,dx
Это все, что вам необходимо знать для организации
интерфейса между ассемблерными модулями и модулями Turbo C++.
- 239 -
Псевдопеременные, встраиваемые ассемблерные коды и
функции прерывания
Как быть в том случае, если вам требуется выполнить
какие-либо операции нижнего уровня, но при этом вы не хотите связываться с
созданием отдельногомодуля на языку ассемблера? Turbo C++ дает вам ответ на
данный вопрос - даже три ответа, а именно: псевдопеременные, встраиваемые
ассемблерные коды и функции прерывания. Оставшаяся часть главыпосвящена
рассмотрению этих способов работы.
Псевдопеременные
Блок центрального процессора вашей системы (8088или
80х86) имеет несколько регистров,или специальных областей памяти, используемых
для манипулирования значениями. Каждый регистр имеет длину16 битов (2 байта);
большинство из них имеет специальное назначение, а некоторые также могут быть
использованыв качестве регистров общего назначения. См. раздел "Модели
памяти" на стр.187 оригинала Главы 4, где регистры центрального процессора
описаны более подробно.
Иногда при программировании на нижнем уровне вам может
понадобиться доступ из программы на С непосредственно к этим регистрам.
- Вам может потребоваться загрузить туда какие-либо
значения перед вызовом системных подпрограмм.
- Вам может понадобиться узнать, какие значения содержатся
там в текущий момент.
Например, вы можете вызвать конкретные подпрограммы из-
ПЗУ вашего компьютера, выполнив для этого команду INT (прерывания), но сначала
вам требуется поместить в конкретные регистры определенную информацию:
void reaches(unsigned char page, unsigned char *ch,
unsigned char *attr);
(*
_AH = 8; /*
Служебный код: читает символ, атрибут*/
_BH = page;
/* Задает страницу дисплея */
geninterrupt(0x10);
/* Вызов прерывания INT 10h */
*ch = _AL; /*
Прием ASCII-кода считанного символа */
*attr = _AH /* Прием атрибута считанного символа */ *)
Как выможетевидеть, подпрограмме INT 10h передается
служебный код и номер страницы; возвращаемые значения копируются в ch и attr.
Turbo C++ обеспечиваеточень простойспособдоступа к
регистрам через псевдопеременные. Псевдопеременная - это простой идентификатор,
соответствующий данному регистру. Использовать ее можнотаким же образом, как
если бы это была обычная переменная типа unsigned int или unsigned char.
Ниже приводятся рекомендации по безопасному использованию
псевдопеременных:
- Присвоение между псевдопеременными и обычными
переменными не вызывает изменения прочих регистров, если не выполняются
преобразования типа.
- Присвоение псевдопеременным констант также не ведет к разрушению данных
в прочих регистрах, за исключением присвоений сегментным регистрам
(_CS,_DS,_SS,_ES), которые используют регистр _AX.
- Простое обращение по ссылке через переменную типа
указателя обычно влечет разрушение данных в одном из следующих регистров: _BX,
_SI или _DI, а также, возможно, _ES.
- Если вам требуется выполнить установку нескольких
регистров (например, при обращении к ПЗУ-резидентным подпрограммам), безопаснее
использовать _AX последним, поскольку другие операторы могут привести к
случайному его изменению.
В следующей таблице приведен полный список
псевдопеременных, доступных для использования, их типы, регистры, которымони
соответствуют и обычное назначение их использования.
Псевдопеременные Таблица
6.3
Псевдопеременная Тип |
Регистр |
Назначение |
_AX
unsigned
_AL unsigned
_AH
unsigned
|
int AX
char AL
char AH
|
Общего
назначения/сумматор
Младший
байт AX
Старший байт AX
|
_BX
unsigned
_BL unsigned
_BH
unsigned
|
int BX
char BL
char BH
|
Общего
назначения/индексный
Младший
байт BX
Старший байт BX
|
_CX
unsigned
_CL unsigned
_CH
unsigned
|
int CX
char CL
char CH
|
Общего
назн./счетчик циклов
Младший
байт CX
Старший байт CX
|
_DX
unsigned
_DL unsigned
_DH
unsigned
|
int DX
char DL
char DH
|
Общего
назн./хранение данных
Младший
байт DX
Старший байт DX
|
_CS
unsigned
_DS unsigned
_SS unsigned
_ES
unsigned
|
int CS
int DS
int SS
int ES
|
Адрес
кодового сегмента
Адрес
сегмента данных
Адрес
стекового сегмента
Адрес вспомогат. сегмента
|
_SP unsigned
int SP Указатель стека (смещение в SS)
_BP unsigned
int BP Указатель базы (смещение в SS)
_DI unsigned
int DI Используется для регистровых
переменных
_SI unsigned
int SI Используется для регистровых
переменных
_FLAGS unsigned int флагов
Состояние процессора
Псевдопеременныеможно
рассматривать как обычные
глобальные переменные
соответствующего типа (unsigned int,
unsigned char). Однако, поскольку они относятся не к какомулибо
произвольному адресу памяти, а к конкретным регистрам центральногопроцессора,
для них существуют некоторые ограничения и особенности, которые вы должны
учитывать.
- С псевдопеременными нельзя использовать операцию
адресации (&), поскольку псевдопеременные не имеют адреса.
- Так как компилятор все время генерирует коды,
использующие регистры (практически все команды 8086 работают с регистрами), нет
никаких гарантий того, что помещенное в псевдопеременную значение продержится
там сколько-нибудь продолжительный отрезок времени.
Это означает, что присваивать значения псевдопеременным
нужно непосредственно перед тем, как эти значения будут использованы, а
считывать значения - сразу же после их получения, как в предыдущем примере. Это
особеннокасается регистров общего назначения (AX, AH, AL и т.д.), так как
компилятор свободно использует эти регистры для хранения промежуточных
значений. Таким образом, процессор может изменять значения этих регистров
неожиданно для вас; например, CX может использоваться в циклах и операциях
сдвига,а в DX может помещаться старшее слово 16-битового умножения.
- Нельзя ожидать, что значения псевдопеременных останутся
неизменными после вызова функции.Для примера рассмотрим следующий фрагмент
кода:
_CX = 18;
myFunc();
i = _CX;
Привызовефункции сохраняются не все значения регистров, тем самым нет
никаких гарантий, что i будет присвоено значение18. Единственными
регистрами,которые наверняка сохраняют свое значение после вызова функции, являются
_DS,_BP,_SI и _ DI.
- Следует быть очень осторожным при модификации некоторых
регистров, поскольку это может иметь весьма неожиданный и нежелательный эффект.
Например, прямое присвоение значений псевдопеременным CS,_DS,_SS,_SP или _BP
может (и наверное, так и произойдет) привести к ошибочному поведению вашей
программы, так как машинный код, создаваемый компилятором Turbo C++, использует
эти регистры самыми различными способами.
Встраиваемые ассемблерные коды
Вы уже знаете, как писать отдельные подпрограммы на
языке ассемблера икомпоновать их с программой на Turbo C++. Turbo C++ позволяет
также встраивать ассемблерные коды в С-программу.Это средство называется
встроенным ассемблированием.
Для использования в С-программе встроенных ассемблерных
кодов может служить опция компилятора -B. Если эта опция не была задана, а в
программе встретился встроенный ассемблерный код, то компилятор выдает
соответствующее предупреждение и перезапускается с опцией -B.Этого можно
избежать, поместив в исходныйкод директиву #pragma inline, которая фактически
заставляет компилятор включить опцию -B.
По умолчанию -B запускает TASM. Это умолчание можно
переопределить опцией -Exxx, где xxx - это другой ассемблер. Подробную
информацию см. в Главе 4, "Компилятор командной строки", Руководства
пользователя.
Для использования данного средства вы должны иметь копию
Turbo Assembler (TASM). Сначала компилятор генерирует ассемблерный файл, а
затем запускает для этого файла TASM, который создает .OBJ-файл.
Разумеется, вы должны быть знакомы с набором команд и
архитектурой 8086. Даже если вы не пишете отдельныхмодулей на языкеассемблера,
все равно вы должны знать, как именно работают команды ассемблера, как
ихприменять ив каких случаях использование этих команд запрещено.
Если все эти условия выполнены, то для включения в
С-программу встроенных команд на языке ассемблера достаточно использовать
ключевое слово asm. Формат этой команды:
asm код-операции операнды;или
новая-строка
где
- код-операции это одна из допустимых команд 8086
(все коды-операций 8086 приводятся ниже в таблице 6.4.
- операнды - это допустимый (допустимые) для данного
кода-операции операнд(ы); это могут быть константы, переменные и метки С.
- ;или новая-строка - это либо точка с запятой, либо
символ новой строки, обозначающие конец оператора asm.
Новый оператор asm может находиться в той же строке через
точку с запятой, однако никакой оператор asm неможет быть продолжен в новой
строке.
Если вы хотите включить в программу несколько операторов
asm, возьмите их в фигурные скобки:
asm (*
pop ax; pop
ds
iret
*)
Точки сзапятой в данном случае не могут служить признаком начала
комментария (как в TASM). Длякомментирования операторов asm следует
использовать комментарии С, например:
asm mov
ax,ds;/* Этот комментарий допустим */
asm (*pop ax;
pop ds; iret;*) /* Этот тоже допустим */
asm push ds ;ЭТОТ КОММЕНТАРИЙ
НЕВЕРЕН !!
Часть оператораasm, представляющая собой команду на
языке ассемблера, непосредственно копируется на выход и встраивается в
ассемблерныйкод, генерируемый Turbo C++ из команд С. Символическиеимена С
заменяются при этом соответствующими эквивалентами языка ассемблера.
Средствовстроенного ассемблирования не реализует полный
ассемблер, в результате чего многие ошибки не обнаруживаются им сразуже.
Возможные ошибки обнаруживает TASM. Однако, TASM может оказаться не в состоянии
идентифицировать местонахождение ошибки, в частности из-за того, что номер
исходной строки С к этому моменту уже утерян.
Каждый оператор asm считается
оператором С. Например,
myfunc()
(*
int i;
int x;
if (i>0)
asm mov x,4
else
i = 7;
*)
Данная конструкция представляет собой допустимый
оператор С. Отметим, чтоточка с запятой после команды mov x,4 не требуется.
Операторы asm являются единственными операторами С, зависящими от наличия
символа новой строки. Этоне соответствует практике, принятой для остальной
части языкаС, нозато соответствует соглашению, принятому в нескольких
компиляторах на базе UNIX.
Страницы: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40
|