GrOk No error Нет ошибки

Turbo C++ Programer`s guide
Первый символ должен являться буквой или символом подчеркивания Тело функции, представляющее собой коды, выполняемые при вызове функции Выражение-приведения это пустой (null) указатель Е1 является указателем, а Е2 - константой типа пустого указателя Выражение-инремента выполняет приращения одного или нескольких цикловых счетчиков Вызвать конструктор тем же образом, что и обычную функцию, нельзя. Вызов деструктора допустим только с полностью квалифицированным именем Инициализатор конструктора (см. "инициализатор-конструктора" в описании синтаксиса декларатора класса в таблице Или 16). Ноль означает по умолчанию десятичную Мы будем обозначать все семейство математических сопроцессоров 80x87 термином "сопроцессор" В режиме эмуляции 80Х87 циклический переход в регистрах, а также ряд других особенностей 80х87 не поддерживается GrOk No error Нет ошибки Кроме того, обратите внимание на то, что функция прерывания выполняет выход с помощью команды IRET (возврата из прерывания) Как идентифицировать диагностические сообщения Что происходит, когда доступ к компоненту объекта типа объединения происходит при помощи компонента другого типа Печатаемые диагностические сообщения и поведение при завершении функции assert
668870
знаков
13
таблиц
0
изображений

0 grOk No error Нет ошибки

-1 grNoInitGraph (BGI) graphics not installed (use initgraph)

(BGI) графика не инсталирована

(используйте initgraph)

-2 grNotDetected Graphics hardware not detected Графическое аппаратное обеспечение

не обнаружено

-3 grFileNotFound Device driver file not found Не найден файл драйвера устройства

-4 grInvalidDriver Invalid device driver file Неверный файл драйвера устройства

-5 grNoLoadMem Not enough memory to load driver Не хватает памяти для загрузки

драйвера

-6 grNoScanMem Out of memory in scan fill Кончилась память при сканирующем

заполнении

-7 grNofloodMem Out of memory in flood fill Кончилась память при лавинном

заполнении

-8 grFontNotFound Font file not found

Файл шрифта не найден

-9 grNoFontMem Not enough memory to load font Не хватает памяти для загрузки

шрифта

-10 grInvalidMode Invalid graphics mode for selrcted driver

Недопустимый графический режим для выбранного драйвера

-11 grError Graphics error Графическая ошибка

-12 grIOerror Graphics I/O error Графическая ошибка ввода/вывода

-13 grInvalidFont Invalid font file Неверный файл шрифта

-14 grInvalidFontNum Invalid font number Неверный номер шрифта

-15 grInvalidDeviceNum Invalid device number Неверный номер устройства

-16 grInvalidVersion Invalid version of file Неправильная версия файла

-----------------------------------------------------------

Вызов grapherrormsg(graphresult()) возвращает строку сообщения об ошибке из вышеприведенной таблицы.

Код возврата ошибки накапливается, изменяясь только когда графическая функция сообщает об ошибке. Код возврата ошибки сбрасывается в 0 только при успешном выполнении initgraph, либо при вызове graphresult. Таким образом, если вы хотите знать, какая графическая функция возвратила ошибку, нужно хранить значение graphresult во временной переменной и затем проверять ее.

Функции запроса состояния

Ниже приводится краткое изложение функций запроса состояния графического режима:

getarccoords Возвращает информацию о координатах, заданных в последнем вызове arc или ellipse.

getaspectratio Возвращает коэффициент сжатия для графического экрана.

getbkcolor Возвращает текущий цвет фона.

getcolor Возвращает текущий цвет вычерчивания.

getdrivername Возвращает имя текущего графического драйвера.

getfillpattern Возвращает шаблон заполнения, определяемый пользователем.

getfillsettings Возвращает информацию о текущем шаблоне

и цвете заполнения.

getgraphmode Возвращает текущий графический режим.

getlinesettings Возвращает текущие стиль, шаблон и толщину линии

getmaxcolor Возвращает максимально допустимое на текущий момент значение пикселя.

getmaxmode Возвращает максимально допустимый номер режима для текущего драйвера.

getmaxx Возвращает текущее разрешение по оси x.

getmaxy Возвращает текущее разрешение по оси y.

getmodename Возвращает имя данного режима драйвера.

getmoderange Возвращает диапазон режимов для данного

драйвера.

getpalette Возвращает текущую палитру и ее размер.

getpixel Возвращает цвет пикселя в (x,y).

gettextsettings Возвращает текущий шрифт, направление, размер и способ выравнивания текста.

getviewsettings Возвращает информацию о текущем графическом окне.

getx Возвращает координату x текущей позиции (CP).

gety Возвращает координату y текущей позиции (CP).

В каждой из категорий графических функций Turbo C++ имеется хотя бы одна функция запроса состояния. Эти функцииупоминались при рассмотрении соответствующих категорий и также рассматриваются здесьотдельно. Каждая из графических функций запроса состояния Turbo C++ имеет имя вида "getчто-то" (за исключением категории функций обработки ошибок). Некоторые из них не принимают никаких аргументов ивозвращают единственное значение, представляющее собой искомую информацию; прочие берут указатель структуры, определяемой в graphics.h, заполняют эту структуру соответствующей информацией и не возвращают никаких значений.

Функциями запроса состояния категории управленияграфической системы являются getgraphmode, getmaxmode и getmoderange. Первая из них возвращает целое число, определяющее текущийграфический драйвер и режим, вторая возвращает максимальный номер режима для этого драйвера, а третья возвращает диапазон режимов, поддерживаемых данным графическим драйвером. getmaxx и getmaxy возвращают соответственно максимальные экранные координаты x и y для текущего графического режима.

Функциями запроса состояниякатегории вычерчивания и заполнения являются getarccoords, getaspectratio, getfillpattern и getlinesettings. getarccoords заполняет структуру, содержащуу координаты, которые использовались при последнем вызове функций arc или ellipse; getaspectratio сообщает текущийкоэффициент сжатия, используемый графическойсистемой для того, чтобы окружности выглядели круглыми. getfillpatternвозвращает текущий определяемыйпользователем шаблон заполнения. getfillsettings заполняет некоторуюструктуру текущим шаюлоном и цветом заполнения. getlinesettings заполняет структуру текущим стилем линии(сплошная, пунктир и т.д.), толщиной (обычнаяили увеличенная), а также шаблоном линии.

Функциями запроса состояниякатегории манипулирования графическим окном являются getviewsettings, getx, gety и getpixel. После того, как графическое окно определено, вы можете найтиего абсолютные экранные координаты ивыяснить состояние режима отсечки, вызвав getwiewsettings, которая заполняет соответствующей информацией некоторую структуру. getx и gety возвращают (относительно графическогоокна) x- и y-координаты текущей позиции. getpixel возвращает цвет указанного пикселя.

Функция запросасостояния категориивывода текста в графическом режиме имеется только одна - gettextsettings. Эта функция заполняет структуруинформацией отекущем символьном шрифте, направлении вывода текста (по горизонтали или по вертикали)6 коэффициенте увеличениясимволов, а также виде выравнивания (как для горизонтально, так идля вертикально-ориентированных текстов).

Функциями запроса состоянии категорииуправления цветом являются getbkcolor,возвращающая текущий цвет фона, getcolor, возвращающая текущий цвет вычерчивания и getpalette,заполняющая структуру, которая включаетв себя размер текущей палитры и ее содержимое. getmaxcolor возвращает максимально допустимое значение пикселя для текущего графического драйвера и режима (размер палитры -1).

И наконец, getmodename и getdrivername возвращаютимя заданного режима драйвера и имя текущего графического драйвера, соответственно.

Глава 6

Интерфейс с языком ассемблера

В данной главе рассказывается, как написать ассемблерный код, который будет хорошо работать с Turbo C++. Предполагается, что вы знаете, как пишутсяподпрограммы на языке ассемблера икак определяются сегменты, константы данных и т. д. Если вы не знакомы с этими концепциями, почитайте руководство по Turbo Assembler, особенно главу "Интерфейс Turbo Assembler с Turbo C" в Руководстве пользователя. TurboAssembler версии 2.0включает несколько средств, делающих интерфейс с Turbo C++ более простым и прозрачным для программиста.

Смешанное программирование

Turbo C++ упрощает вызов из С-программ подпрограмм, написанных на языке ассемблера, и наоборот, вызов из ассемблерных программ подпрограмм на С. В данном разделе показано, насколько простинтерфейс между Turbo C++ и ассемблером; также приводится информация, помогающая на практике осуществить такой интерфейс.

Последовательности передачи параметров

Turbo C++ поддерживаетдва метода передачи параметров функции. Один из них является стандартным методом С, который мы рассмотрим первым; второй метод заимствован из Паскаля.

Последовательность передачи параметров в С

Предположим, вы объявили следующий прототип функции:

void funca(int p1, int p2, long p3);

По умолчанию Turbo C++ использует последовательность передачи параметров С, которая также называется соглашением о связях С. При вызове этой функции(funca) параметры помещаютсяв стек в последовательности справа-налево (p3,p2,p1), послечего в стек помещается адрес возврата. Таким образом, в случае вызова

main()

(*

int i,j;

long k;

...

i = 5; j = 7; k = 0x1407AA;

funca(i,j,k);

...

*)

то стек (непосредственно перед помещением в него адреса возврата) будет выглядеть следующим образом:

sp + 06: 0014

sp + 04: 07AA k = p3

sp + 02: 0007 j = p2

sp: 0005 i = p1

Вызываемой подпрограмме не требуется точно знать, сколько параметров помещено в стек. Она просто предполагает, что все нужные ей параметры находятся в стеке.

Кроме того - что очень важно - вызываемая подпрограмма не должна снимать параметры со стека. Почему? Дело в том, что это сделает вызывающая программа. Например, приведенная выше функция в ассемблерном виде, получаемом компилятором из исходного кода на С, будет выглядеть следующим образом:

mov WORD PTR [bp-8],5;установка i =5

mov WORD PTR [bp-6],7;установка j = 7

mov WORD PTR [bp-2],0014h;установка k = 0x1407AA

mov WORD PTR [bp-4],07AAh

push WORD PTR [bp-2];помещение в стек старшего слова k

push WORD PTR [bp-4];помещение в стек младшего слова k

push WORD PTR [bp-6];помещение в стек j

push WORD PTR [bp-8];помещение в стек i

call NEAR PTR funca ;вызов funca (помещение в стек

;адреса возврата)

add sp,8;настройка стека

Обратите внимание на последнюю команду, add sp,8. К этому моменту компилятору известно, сколько параметров было помещено в стек; компилятор также знает, что адрес возврата был помещен в стек при вызове funca и уже был снят оттуда командой ret в конце funca.

Последовательность передачи параметров Паскаля

Другим методом передачи параметров является стандартный метод передачи параметров Паскаля (называемый также соглашением о связях Паскаля). Это не значит, что вы можете вызывать из TurboC++ функции Turbo Pascal. Это невозможно. Если funca объявлена как

void pascal funca(int p1, int p2, long p3);

то при вызове этой функции параметры помещаются в стек в последовательности слева-направо (p1,p2,p3), после чего в стек помещается адрес возврата. Таким образом, при вызове:

main()

(*

int i,j;

long k;

...

i = 5; j = 7; k = 0x1407AA;

funca(i,j,k);

...

*)

то стек (непосредственно перед помещением в него адреса возврата) будет выглядеть следующим образом:

sp + 06: 0005 i = p1

sp + 04: 0007 j = p2

sp + 02: 0014

sp: 07AA k = p3

Итак, в чем здесь различие? Дело в том, что помимо изменения очередности помещения параметров в стек, последовательность передачи параметров Паскаля предполагает, что вызываемая функция (funca) знает, сколько параметров будет ей передано и соответственно настраиваетстек. Другимисловами, теперь в ассемблированном виде данная функция будет иметь вид:

push WORD PTR [bp-8];помещение в стек i

push WORD PTR [bp-6];помещение в стек j

push WORD PTR [bp-2];помещение в стек старшего слова k

push WORD PTR [bp-4];помещение в стек младшего слова k

call NEAR PTR funca ;вызов funca (помещение в стек

;адреса возврата)

Отметим, что теперь после вызова отсутствует командаadd sp,8. Вместо нее funca использует при окончании команду ret 8, при помощи которой очищает стек перед возвратом к main.

По умолчанию все функции, создаваемые в Turbo C++, используют способ передачи параметров С. Исключение делается при наличии опциикомпилятора -p (опция Pascal в диалоговомполе Code Generation); в этом случае все функции используют метод передачи параметров Паскаля. Тем не менее, вы можете задать для любой функции метод передачи параметров С при помощи модификатора cdecl:

void cdeclfunca(int p1, int p2, long p3);

Данное объявление переопределит директиву компилятора - p.

И однако, почему может возникнуть необходимость использовать соглашение о связях Паскаля вообще? Для этого есть две главных причины.

- Вам может понадобиться вызывать существующие подпрограммы на языке ассемблера, использующие соглашение о связях Паскаля.

- Получаемый в таком случае код несколько меньше по размеру, поскольку в этом случае не требуется в конце выполнять очистку стека.

Однако, использование соглашения о связях Паскаля может вызвать некоторые проблемы.

Прежде всего, соглашение о связях Паскаля дает меньше возможностей,чем для С. Вы не можете передавать переменное число параметров (как этодопускается всоглашении С), поскольку вызываемая подпрограмма должна знать число передаваемых ей параметров и соответственнымобразом настроить стек. Передача большего или меньшего числа параметроввызывает серьезные проблемы, тогда как в случае соглашения С ничего особенного в такихслучаях не происходит (кроме, возможно, того, что программа даст неправильный ответ).

Во-вторых, при использовании опции компилятора вы обязательно включить файлы заголовка длявсех вызываемых вашей программой стандартных функций С. Почему? Дело в том, что в противном случае Turbo C++ будет использовать для каждой из этих функций соглашение о связях (и именах) - и ваша программа не будет компоноваться.

В файле заголовка каждая из этих функций объявлена как cdecl, поэтому включение файловзаголовка заставит компилятор использовать для этих функций соглашение С.

Резюме:если вы собираетесь использовать вС-программе соглашение освязяхПаскаля, не забывайте о необходимости использовать прототипы функций везде, где это возможно, а каждую функцию явно объявляйте pascal или cdecl. Полезно также разрешить выдачу сообщения "Function call with no prototype" ("вызов функции без прототипа"), чтобы гарантировать наличие прототипов всех функций.

Подготовка к вызову .ASM из Turbo C++

При написании подпрограмм на языке ассемблера нужно принимать во внимание определенные соглашения для того, чтобы (1) обеспечить компоновщик нужной ему информацией и (2) обеспечить соответствие формата файла и модели памяти, используемой в программе на С. Упрощенные сегментные директивы

Обычно модули на языке ассемблера состоят из трех разделов: кода, инициализированных данных и неинициализированных данных. Каждый из этих типов информации организован в отдельный сегмент с использованием определенных имен, которые зависят от используемой в вашей С-программе модели памяти.

Turbo Assembler (TASM) предлагает вам три упрощенных сегментных директивы (.CODE, .DATA и .DATA?), которыемогут быть использованыпри определении этих сегментов. Они говорят компилятору о необходимостииспользовать имена сегментов по умолчанию для модели памяти, заданной вами при помощи директивы .MODEL. Например, если ваша программа на С использует модель памяти small, вы можете организовать каждый ассемблерный модуль с упрощенными сегментными директивами,как показано в следующей таблице:

-----------------------------------------------------------

.MODEL SMALL

.CODE

...кодовый сегмент...

.DATA

...сегмент инициализированных данных...

.DATA?

...сегмент неинициализированных данных...

Стандартные сегментные директивы

В некоторых случаях вам может понадобиться использовать другие имена сегментов, нежели те, что являются умолчаниямидля данной модели памяти. Для этого вы должны использовать стандартные сегментные директивы, как показано в Таблице 6.1.

Формат файла языка ассемблера Таблица 6.1

code SEGMENT BYTE PUBLIC 'CODE'

ASSUME CS:code, DS:dseg

...........кодовый сегмент.............

code ENDS

dseg GROUP _DATA,_BSS

data SEGMENT WORD PUBLIC 'DATA'

...инициализированный сегмент данных...

data ENDS

_BSS SEGMENT WORD PUBLIC 'BSS'

...неинициализированный сегмент данных...

_BSS ENDS

END

Идентификаторы code, data и dseg в данном макете имеют специальные заменители, зависящие от используемой модели памяти; в таблице 6.2 показано, какое имя должно использоваться для тойили иной модели. имя_файла в Таблице 6.2 - это имя модуля: оно должно быть тем же в директиве NAME и при заменах идентификаторов.

Отметим,что вслучаемоделипамятиhuge сегмент _BSS отсутствует, а определение GROUP опускается полностью. В целом, _BSS представляет собой опцию; определение ее необходимо только в случае использования.

Лучший способ создания "заготовки" для будущей ассемблерной программы состоит в том, чтобы скомпилировать пустую программу в .ASM-файл (при помощи опции TCC -S) и затем изучить сгенерированный таким образом ассемблерный код.

Замены идентификаторов и модели памяти Таблица 6.2

Модель Замены идентификатораУказатели кода и данных
Tiny,Small data dseg

code = _TEXTКод:DW _TEXT:xxx

_DATAДанные: DW DGROUP:xxx

DGROUP

Compact data dseg

code = _TEXTКод:DW _TEXT:xxx

_DATAДанные: DD DGROUP:xxx

DGROUP

Medium data dseg

code = имя_файла_TEXTКод:DD:xxx

_DATAДанные: DW DGROUP:xxx

DGROUP

Large

data

dseg

code = имя_файла_TEXTКод:DD:xxx

_DATAДанные: DD DGROUP:xxx

DGROUP

Huge code = имя_файла_TEXTКод:DD:xxx

data = имя_файла_DATAДанные: DD:xxx

Определение данных - констант и переменных

Модели памяти также влияют на то, каким образом вы определяете любые константы, являющиеся указателями кода, данных, либо того и другого. В таблице 6.2 показано, как должны выглядеть эти указатели, причем xxx - это адрес, на который устанавливается указатель.

Некоторые определения используют DW (определение слова), а некоторые - DD (определение двойного слова), что означает размер результирующего указателя. Числовые и текстовые константы определяются нормальным образом.

Переменные, разумеется, определяются так же, как и константы. Если вам нужны переменные,не инициализированныеконкретными значениями, вы можете объявить ихв сегменте _BSS, введя вопросительный знак(?) втом месте, где обычно находится значение.

Определение глобальных и внешних идентификаторов

После того, как вы создали модуль, вашей программе на Turbo

C++ требуется знать, какие функции она может вызывать и на

какие переменные ссылаться. Аналогичным образом, вам может

потребоваться иметь возможность вызывать функции Turbo C++из

подпрограмм на языке ассемблера, либо ссылаться оттуда напе-

ременные, определенные в программе на Turbo C++.

При выполнении таких вызовов вы должны хорошо представлять себе работу компилятора и компоновщика Turbo C++. При объявлении внешнего идентификатора компилятор автоматически добавляет к этому именисимволподчеркивания (_), прежде чем сохранить его в объектном модуле. Это означает, что вы должны поместить символ подчеркивания перед любыми идентификаторами вашего модуля на языке ассемблера, на которые вы хотите ссылаться из С-программы. Идентификаторы Паскаля обрабатываются иначе, чем идентификаторы С, - они состоят только из заглавных символов не имеют ведущего символа подчеркивания.

Символы подчеркивания в идентификаторах С необязательны, но по умолчаниюони помещаются передними. Их можно отменить при помощи командной строки -u-. Однако, при использовании стандартных библиотек Turbo C++ вы в таком случае столкнетесь с проблемами, и вам придется переделывать эти библиотеки. (Для этоговам понадобится другой продукт Turbo C++ - исходные тексты библиотек исполняющей системы;в этом случае за дополнительной информацией обращайтесь на фирму Borland).

Если какой-либоasm-код в исходном файле ссылается на идентификаторы С (данные или функции), эти идентификаторы должны начинаться знаком подчеркивания (если вы не используете один из описанных выше спецификаторов языка).

Turbo Assembler (TASM) не учитываетрегистры, которыми набраны символы идентификаторов; другими словами,при ассемблировании программы все идентификаторы записываются только заглавными буквами. Опция TASM /mx устанавливает учет регистра для общих и внешних имен. Компоновщик Turbo C++ также записывает идентификаторы extern заглавными буквами, поэтому тут все должно работать. В наших примерах ключевые слова и директивы записываются заглавными буквами, а все прочие идентификаторы и коды операций строчными; это соответствует стилю имен в справочном руководстве по TASM.Вам предоставляется свобода любых комбинаций заглавных и строчных букв в идентификаторах, по вашему усмотрению.

Для того, чтобы идентификаторы были видимыми извне ассемблерного модуля, вы должны объявить их как 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принимают конкретные значения, в зависимости от используемоймоделипамяти. Ниже приводится эта взаимозависимость:

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.

Ассемблерный оператор может быть использован как в качестве выполняемого оператора внутри функции, так и в качестве внешнего объявления вне этой функции. Ассемблерные операторы, находящиеся вне функций, помещаются в сегмент DATA, анаходящиеся внутри функций помещаются в сегмент CODE.

Ниже приводится версия функции min (которая рассматривалась в разделе "обработка значений возврата" на стр.257 оригинала), использующая встроенное ассемблирование.

int min (int V1, int V2)

(*

asm (*

mov ax,V1

cmp ax,V2

jle minexit

mov ax,V2

*)

minexit:

return (_AX);

*)

Отметим схожесть данного кода с кодом настр.260 оригинала, который используетрасширение Turbo Assembler, связанное с заданием конкретного языка.

В качестве операторов встроенного ассемблирования допускается включать любые кодыопераций 8086. Существует четыре класса команд, позволяемых компилятором Turbo C++:

- обычные команды - стандартный набор кодов операций 8086

- строковые команды - специальные коды обработки строк

- команды перехода - различные коды операций перехода

- директивы ассемблирования - размещения и определения данных

Отметим,что компилятор допускает задания любых операндов, даже если они ошибочны или не разрешены ассемблером. Точный формат операндов не может быть принудительно установлен компилятором.

Коды операций

___________________________________________________________

Ниже приводится полный перечень мнемоническихимен кодов операций, которые могут быть использованы в операторах встроенного ассемблирования:

Мнемонические имена кодов операций Таблица 6.4

aaafdvtr fpatan lsl

aadfeni fprem mov

aamffroe** fplan mul

aasfiadd frndint neg

adcficom frstor nop

addficomp fsave not

andfidiv fscale or

boundfidifr fsqrt out

callfild fst pop

cbwfimul fstcw popa

clcfincstp** fslenv popi

cldfinit fstp push

clifist fstsw pusha

cmcfistp fsub pushf

cmpfisub fsubp rcl

cwdfisubr fsubr rcr

daafld fsubrp ret

dasfld1 ftst rol

decfldcw fweit ror

divfldenv fxam sahf

enterfldl2e fxch sal

f2xm1fldl2t fxtract sar

fabsfldlg2 fyl2x sbb

faddfldln2 fyl2xp1 shl

faddpfldpi hlt shr

foldfldz idiv smsw

fbstpfmul imul stc

fchsfmulp in std

fclexfnclex inc sti

fcomfndisi int sub

fcompfneni into test

fcomppfninit iret verr

fdecstp** fnop lahf verw

fdisifnsave lds wait

fdivfnstcw lea xchg

fdivpfnstenv leave xlat

fdivrfnstsw les xor

При использовании средства встроенного ассемблирования в подпрограммах, эмулирующих операции с плавающей точкой (опцияTCC -O), коды операции, помеченные **, не поддерживаются.

При использовании в операторах встроенного ассемблирования мнемонических команд 80186 необходимо включать опцию командной строки -1. Тогда компилятор включит в генерируемый им ассемблерный код соответствующие операторы, в результате чего Turbo Assembler будет ожидать появление данныхмнемоническихимен.При использовании предыдущих версий ассемблера эти мнемонические имена могут не поддерживаться.

Строковые команды

Помимокодов операций, приведенных выше, возможно использование следующих строковых команд, как в исходном виде, так и с префиксами циклического выполнения.

Строковые команды Таблица 6.5

capslasw movsb

capsblods movsw

capswlodsb outs

laslodsw outsb

lasbmovs

outswstos

scasstosb

scasbstosw

scasw

Префиксы ________________________ __________________________________
Допустимы следующие префиксы:

lock rep reperepnerepnzrepz

Команды перехода

Команды перехода рассматриваются отдельно. Поскольку метка не может быть включена в саму команду, переходы выполняются к меткам С (см. раздел "Использование команд перехода и меток" на стр.274 оригинала). В следующей таблице перечисленыдопустимые команды перехода:

Команды перехода Таблица 6.6

jajge jnc jnp

jaejl jne jns

jbjle jng jnx

jbejmp jnge jo

jcjna jnl jp

jcxzjnae jnle jpe

jejnb jno jpo

jgjnbe

js

jz

loop

loope

loopae

loopnz

loopz

Директивы ассемблирования

В операторах встроенного ассемблирования Turbo C++ допустимы следующие директивы:

db dd dw extra

Ссылки из операторов встроенного ассемблирования к данным и функциям

В операторах asm допускается использовать символические имена С; Turbo C++ автоматически преобразовывает их в соответствующие операнды языка ассемблера и вставляет перед этими именами символ подчеркивания. Допускается использование любых символических имен, включая автоматически распределяемые (локальные)переменные, регистровые переменные и параметры функций.

В целом, вы можете использовать символическое имя С в любой позиции, где допустимы адресные операнды. Разумеется, допустимо использование регистровых переменных везде, где допустимым операндом является регистр.

Как только ассемблер встречает во время лексического анализа операндов встроенного ассемблера идентификатор, просматривается таблица символических имен С. Имена регистров 8086 из этого поиска исключаются. Имена регистров могут быть набраны какзаглавными, так и строчными буквами.

Встроенное ассемблирование и регистровые переменные

Встроенный ассемблерный код может свободно использовать рабочие регистры SIи DI.При использовании во встроенном ассемблерномкоде регистров SI и DI компилятор не станет распределять их для регистровых переменных.

Встроенное ассемблирование, смещения и переопределение размера

___________________________________________________________

Во времяпрограммирования вам не требуется знать точные смещения локальных переменных. При использовании имени правильное значение смещения будет включено автоматически.

Однако, может оказаться необходимым включение в ассемблерную команду соответствующего WORD PTR, BYTE PTR, или любого другого переопределения размера. Переопределение DWORD PTR требуется задавать в командах LES или косвенного дальнего вызова.

Использование компонентов структур С

___________________________________________________________

В операторе встроенного ассемблирования допускается обращение ккомпонентамструктур обычным способом (т.е. переменная.компонент). В таком случае вы имеете дело с переменной и можете записыватьв нееили обращаться к хранимым в ней значениям. Однако, вы можете также непосредственно обращаться к имени компонента (без имени переменной) в форме числовой константы. В данной ситуации константа равна (в байтах) смещению от начала структуры, содержащей этот компонент. Рассмотрим следующий фрагмент программы:

struct myStruct (*

int a_a;

int a_b;

int a_c;

*) myA;

myfunc ()

(*

...

asm (*mov ax, myA.a_b

mov bx, [di].a_b

*)

...

*)

Мы объявили тип структуры с именем myStruct с тремя компонентами,a_a, a_b и a_c; мы также объявили переменную myA типа myStruct. Первый оператор встроенного ассемблирования пересылает значение из myA.a_b в регистрAX. Второй оператор пересылает значение по адресу [di]+смещение(a_c) в регистр BX(он беретадрес,хранимый в DI, и складывает со смещениемa_c относительно начала myStruct.) В такой последовательностиэти ассемблерные операторы образуют следующий ассемблерный код:

mov ax, DGROUP : myA+2

mov bx, [di+4]

Для чего это может понадобиться? Загрузив регистр (например, DI) адресом структуры типа myStruct вы можетеиспользовать имена компонентов для непосредственных ссылок к этим компонентам. Фактически имя компонента может быть использовано везде, где в качестве операнда ассемблерного операторадопустима числовая константа.

Компоненту структуры обязательно должна предшествовать точка (.), котораясообщает, чтоданноеимя -это имя компонента структуры, а не обычное символическое имя С. Имена компонентов в ассемблерном виде на выходе компилятора заменяются числовыми смещениями (числовое значение a_c равно 4), аинформация о типе теряется. Таким образом, компоненты структуры могут использоваться в ассемблерных операторах как константы времени компиляции.

Однако,здесьсуществует одно ограничение. Еслидве

структуры, используемые во встроенных ассемблерныхоператорах, имеют одинаковые имена, вы должны различать их. Для этого вставьте тип структуры (вкруглых скобках) между точкой и именем компонента, как если бы речь шла о приведении типов. Например,

asm mov bx,[di].(struct tm)tm_hour

Использование команд перехода и меток

___________________________________________________________

Вы можете использовать в операторахвстроенного ассемблирования любые команды условного и безусловного перехода, а такжецикловые команды. Они являются допустимыми исключительно внутри функций. Поскольку операторы asm не позволяют объявления меток, команды перехода ассемблера должны использовать в качестве объектов перехода имена метокgoto C. Прямые дальние переходы генерироваться не могут.

В следующем примере кода переход выполняется к метке C goto a.

int x()

(*

a: /* это метка команды C goto "a" */

...

asm jmp a /* переход к метке"a" */

...

*)

Также допустимы косвенные переходы. Для того, чтобы выполнить косвенный переход, в качестве операнда команды перехода указывается имя регистра.

Функции прерывания

8086 резервирует первые 1024 байта памяти для набора из 256 дальних указателей, называемых также векторамипрерывания, указывающих на специальные системные подпрограммы, которые называются обработчиками прерываний. Эти подпрограммы вызываются при выполнении следующей команды:

int int#

где int# это число от 0h до FFh. Когда встречается данная команда, компьютер сохраняет кодовый сегмент (CS), указатель команд (IP) и состояния флагов, затем запрещает прерывания и выполняет дальний переход по адресу, на который указывает соответствующий вектор прерывания. Например, часто встречается прерывание

int 21h

вызывающее большинство подпрограмм DOS. Однако, многие векторы прерывание не использованы, что означает для вас возможность написать собственные обработчики прерываний и поместить дальние указатели на них в свободные векторы прерывания.

Для того, чтобы написать в Turbo C++ обработчик прерывания, вы должны определить функцию с типом interrupt; более конкретно, она может выглядеть следующим образом:

void interrupt myhandler(bp, di, si, ds, es, dx,

cx, bx, ax, ip, cs, flags, ...);

Как можно заметить, все регистры передаютсяв качестве параметров, что позволяет использовать и модифицировать их в вашей программе, не прибегая к рассмотренным выше в данной главе псевдопеременным. Допускается также передачаобработчику прерываний дополнительных параметров (flags,...); последние должны иметь соответствующие определения.

Функция типа interrupt автоматически сохраняет (помимо SI, DI и BP) регистры от AX до DX и DS. Эти же регистры при выходе из обработчика прерывания восстанавливаются.

Обработчики прерываниймогут использовать арифметические операции с плавающей точкой при всех моделяхпамяти. Любойкод обработчика прерывания, использующий 80х87, должен сохранять состояние сопроцессора при входе и восстанавливать его при выходе.

Функция прерывания может модифицировать передаваемые ей параметры. Изменение объявленных параметров приведет к модификации соответствующего регистра при выходе из обработчика прерывания. Это свойство может оказаться полезным, когда обработчик прерывания используется как служебнаяфункция пользователя, какэто происходит вслучаеслужебной функции DOS INT


Информация о работе «Turbo C++ Programer`s guide»
Раздел: Информатика, программирование
Количество знаков с пробелами: 668870
Количество таблиц: 13
Количество изображений: 0

Похожие работы

Скачать
14945
0
7

... к сожалению, обратное утверждение не верно. C++ Builder содержит инструменты, которые при помощи drag-and-drop действительно делают разработку визуальной, упрощает программирование благодаря встроенному WYSIWYG - редактору интерфейса и пр. Delphi — язык программирования, который используется в одноимённой среде разработки. Сначала язык назывался Object Pascal. Начиная со среды разработки Delphi ...

Скачать
225728
6
0

... ориентированы на 32 разрядные шинные архитектуры компьютеров с процессорами 80386, 80486 или Pentium. Фирма Novell также подготовила варианты сетевой ОС NetWare, предназначенные для работы под управлением многозадачных, многопользовательских операционных систем OS/2 и UNIX. Версию 3.12 ОС NetWare можно приобрести для 20, 100 или 250 пользователей, а версия 4.0 имеет возможность поддержки до 1000 ...

Скачать
49224
0
9

... завдання поширюється на розробку системи обліку зареєстрованих автомобілів в ДАІ, призначеної для збору, зберігання, а також полегшення для доступу та використання інформації. Програма з обліку зареєстрованих автомобілів в ДАІ, представляє собою, перехід від паперових носіїв інформації до електронних. Система обліку зареєстрованих автомобілів значно допоможе працівникам ДАІ з обліку, аналізу та ...

Скачать
132529
1
5

... меньше времени и ответ клиенту агентство может дать уже в день подачи заявки. Каждая турфирма разрабатывает индивидуальный образец листа бронирования. Согласно Федеральному Закону «Об основах туристской деятельности в Российской Федерации» (гл. IV, ст. 9) – это конкретный заказ туриста или лица, уполномоченного представлять группу туристов, туроператору на формирование туристского продукта. ...

0 комментариев


Наверх