Back
Глава 4 Модели памяти, операции с плавающей точкой и оверлеи
Данная глава рассматривает три основных вопроса:
- Модели памяти, от tiny до huge. Мы расскажем вам, что они из се-
бя представляют, как выбрать модель памяти и зачем может понадо-
биться (или почему может не понадобиться) использовать ту или
иную конкретную модель памяти.
- Опции операций с плавающей точкой. Как и когда использовать зти
опции.
- Оверлеи. Как они работают, и как их использовать.
Модели памяти
Краткий обзор каждой модели памяти см. на стр.194 оригинала.
Turbo C++ предоставляет шесть моделей памяти, каждая из
которых соответствует определенному типу программы и размеру
кодов. Каждая модель памяти по-своему работает с памятью. Почему
необходимо разбираться в моделях памяти? Для ответа на этот вопрос
следует рассмотреть систему компьютера, с которой вы работаете. В
основе блока центрального процессора системы лежит микропроцессор,
принадлежащий к семейству микропроцессоров Intel iAPx86; это могут
быть процессоры 8088 или 80286, на также и 8086, 80186, 80386 или
80486. Пока будем обозначать процессор как 8086.
Регистры 8086
Процессор 8086 содержит некоторый показанный ниже набор
регистров. Существует, помимо того, еще один регистр - IP
(указатель команд) - однако Turbo C++ не имеет возможности
непосредственного к нему доступа, и потому он не показан.
Регистры общего назначения
_________________________________________________________________
--------------------------------
AX | AH | AL | сумматор (матем.операции
--------------------------------
BX | BH | BL | базовый регистр (индексация)
--------------------------------
CX | CH | CL | счетчик (индексация)
--------------------------------
DX | DH | DL | данные (содердит данные)
--------------------------------
Адресные сегментные регистры
--------------------------------
CS | | указатель кодового сегмента
--------------------------------
DS | | указатель сегмента данных
--------------------------------
SS | | указатель сегмента стека
--------------------------------
ES | | указатель вспомог. сегмента
--------------------------------
Регистры специального назначения
--------------------------------
SP | | указатель стека
--------------------------------
BP | | указатель базы
--------------------------------
SI | | исходный индекс
--------------------------------
DI | | индекс назначения
--------------------------------
Рис.4.1 Регистры 8086
Регистры общего назначения
_________________________________________________________________
Регистрами общего назначения называются наиболее часто
используемые для хранения и манипулирования данными регистры.
Каждый из них имеет некоторую специальную функцию, свойственную
только ему. Например,
- Некоторые математические операции могут быть выполнены только с
помощью АХ.
- ВХ можно использовать как индексный регистр.
- СХ используется командой LOOP и некоторыми строковыми командами.
-DX неявно используется некоторыми математическими операциями.
Однако, существует множество операций, которые могут равно
выполняться всеми этими регистрами; во многих случаях они
взаимозаменяемы.
Сегментные регистры
_________________________________________________________________
Сегментные регистры содержат начальные адреса каждого из
четырех сегментов. Как будет описано в следующем разделе,
16-битовое значение в сегментном регисире сдвигается влево на 4
бита (т.е. умножается на 16), в результате чего получается
20-битовый адрес данного сегмента.
Регистры специального назначения
_________________________________________________________________
8086 имеет несколько регистров специального назначения:
- Регистры SI и DI могут выполнять многие функции регистров общего
назначения, плюс они могут быть использованы в качесве индексных
регистров.
- Регистр SP указывает на текущую вершину стека и часто содержит
смещение для регистра стека.
- Регистр BP - это вторичный указатель стека, обычно используемый
для индексирования стека с целью доступа к аргументам или дина-
мическим локальным переменным.
Функции С используют в качестве базового адреса аргументов и
динамических локальных переменных регистр - указатель базы (ВР).
Параметры имеют положительные смещения относительно ВР, зависящие
от модели памяти. ВР всегда указывает на предыдущее сохраненное
значение ВР. Функции, не имеющие аргументов, не используют и не
сохраняют ВР, если опция Standart Stack Frame ("Стандартный
стековый фрейм") находится в состоянии Off.
Динамические локальные переменные имеют отрицательные
смещения относительно ВР. Смещения эти зависят от того, сколько
памяти было уже распределено переменным этого типа.
Регистр флагов
_________________________________________________________________
16-битовый регистр флагов содержит всю необходимую информацию
о состоянии 8086 и результатах выполнения последних команд.
виртуальный режим 8086
возобновление
вложенная задача
уровень защищенного
ввода/вывода
переполнение
признак
направления
прерывание
разрешено
внутреннее
прерывание
знак
ноль
перенос с
заемом
четность
перенос
31 23 15 7 0
VR NIOPODITSZ A P C
\/ \/ \/
только 80386 80286 все процессоры 80х86
80386
Рис.4.2 Регистр флагов 8086
Например, вам понадобилось узнать, равен ли результат
операции вычитания нулю; для этого вам достаточно проверить флаг
нуля (бит Z регистра флагов) непосредственно сразу же после
выполнения команды; если данный флаг установлен, то результат
действительно был равен нулю. Прочие флаги, такие как фоаги
переноса или переполнения, аналогичным образом сообщают вам о
результатах выполнения тех или иных математических или логических
операций.
Прочие флаги контролируют режимы работы 8086. Флаг
направления управляет направлением смещения строковых команд, а
флаг прерываний управляет тем, разрешено ли внешним аппаратным
устройствам, таким как клавиатура или модем, временно
приостанавливать выполнение текущего кода для обслуживания
возникающих у них потребностей. Флаг внутренних прерываний
используется только программным обеспечением, предназначенным для
отладки другого программного обеспечения.
Обычно регистр флагов не считывается и не модифицируется
непосредственно. Обычно обращения к этому регистру выполняются
посредством специальных ассемблерных команд (таких как CLD, STI и
CMC), а также при помощи арифметических и логических команд,
модифицирующих конкретные флаги. Подобным же образом, содержимое
конкретных битов регистра флагов влияет на работу таких команд,
как JZ, RCR и MOVSB. Регистр флагов фактически никогда не
используется как адрес памяти, а содержит данные о состоянии и
управлении 8086.
Сегментация памяти
Микропроцессор Intel 8086 имеет сегментированную архитектуру
памяти. Он имеет общий объем памяти 1 Мб, но позволяет
одновременно адресовать только 64 Кб памяти. Такой участок памяти
называется сегментом; отсюда и название "сегментированная
архитектура памяти".
- 8086 позволяет работу с четырьмя различными сегментами: кодовым,
данных, стека и вспомогательным. Кодовый сегмент содержит машин-
ные команды программы; в сегменте данных хранится информация;
сегмент стека имеет, разумеется, организацию и назначение стека;
вспомогательный сегмент используется для хранения некоторых
вспомогательных данных.
- 8086 имеет четыре 16-битовых сегментных регистра (один на каждый
сегмент) с именами CS, DS, SS и ES; они указывают на кодовый
сегмент, сенгмент данных, стека и вспомогательный сегмент, соот-
ветственно.
- Сегмент может располагаться в произвольном адресе памяти - прак-
тически везде. По причинам, которые станут вам ясны по мере оз-
накомления с материалом, сегмент должен располагаться в памяти,
начиная с адреса, кратного 16 (десятичное).
Вычисление адреса
_________________________________________________________________
Полный адрес в 8086 состоит из двух 16-битовых значений:
адреса сегмента и смещения. Предположим, что адрес сегмента данных
- т.е. значение в регистре DS -- равен 2F84 (шестнадцатиричное) и
вы желаете вычислить фактический адрес некоторого элемента данных,
который имеет значение 0532 (основание 16) от начала сегмента
данных; как это сделать?
Вычисление адреса будет выполнено следующим образом: нужно
сдвинуть влево значение сегментного регистра на 4 бита (это
эквивалентно одной шестнадцатиричной цифре), а затем сложить с
величиной смещения.
Полученное 20-битовое значение и есть фактический адрес
данных, как показано ниже:
регистр DS (после сдвига): 0010 1111 1000 0100 0000 = 2F840
смещение: 0000 0101 0011 0010 = 00532
-------------------------- --------------------------------
Адрес: 0010 1111 1101 0111 0010 = 2FD72
Участок памяти величиной 16 байт называется параграфом,
поэтому говорят, что сегмент всегда начинается на границе
параграфа.
Начальный адрес сегмента всегда является 20-битовым числом,
но сегментный регистр имеет всего 16 битов - поэтому младшие 4
бита всегда предполагаются равными нулю. Это означает - как было
уже сказано - что начало сегмента может находиться только в
адресах памяти, кратных 16, т.е. адресах, в которых последние 4
бита (или один шестнадцатиричный разряд) равен нулю. Поэтому если
регистр DS содержит значение 2F84, то фактически сегмент данных
начинается в адресе 2F840.
Стандартная запись адреса имеет форму сегмент:смещение;
например, предыдущий адрес можно записать как 2F84:0532. Отметим,
что поскольку смещения могут перекрываться, данная пара
сегмент:смещение не является уникальной; следующие адреса
относятся к одной и той же точке памяти:
0000:0123
0002:0103
0008:00A3
0010:0023
0012:0003
Сегменты могут (но не должны) перекрываться. Например, все
четыре сегмента могут начинаться с одного и того же адреса, что
означает, что вся ваша программа в целом займет не более 64 Кб -
но тогда в пределах этой памяти должны поместиться и коды
программы, и данные, и стек.
Указатели
Какое отношение имеют указатели к моделям памяти и Turbo C++?
Самое непосредственное. Тип выбранной вами модели памяти
определяет тип по умолчанию указателей, используемых для кода и
данных (хотя вы можете явно объявить указатель или функцию как
имеющие тот или иной конкретный тип, независимо от текущей модели
памяти). Указатели бывают четырех разновидностей: near (16 битов),
far (32 бита), huge (также 32 бита) и segment (16 битов).
Ближние указатели (near)
_________________________________________________________________
16-битовый (near) указатель для вычисления полного адреса
связывается с одним из сегментных регистров; например, указатель
функции складывает свое 16-битовое значение со сдвинутым влево
содержимым регистра кодового сегмента (CS). Аналогичным образом,
ближний указатель данных содержит смещение в сегменте данных,
адресуемом регистром сегмента данных (DS). С ближними указателями
легко манипулировать, поскольку все арифметические операции с ним
(например, сложение) можно выполнять безотносительно к сегменту.
Дальние указатели (far)
_________________________________________________________________
Дальний (32-битовый) указатель содержит не только смещение
относительно регистра, но также и (в остальных 16 битах) адрес
сегмента, который затем должен быть сдвинут влево и сложен со
значением смещения. Использование дальних указателей позволяет
иметь в программе несколько кодовых сегментов; это, в свою
очередь, позволяет программе превышать 64К. Можно также адресовать
более 64К данных.
При использовании дальних указателей для адресации данных вам
следует учитывать некоторые потенциальные проблемы, которые могут
возникать при манипулировании такими указателями. Как объяснялось
в разделе, посвященном вычислениям адреса, может существовать
множество пар типа сегмент:смещение, адресующих одну и ту же точку
памяти. Например, дальние указатели 0000:0120, 0010Ж0020 и
0012:0000 разрешаются к одному и тому же 2-битовому адресу.
Однако, если у вас были бы три переменных типа дальнего указателя
- a, b и c, содержащих соответственно три указанных значения, то
следующие выражения все давали бы значение "ложь":
if (a == b) . . .
if (b == c) . . .
if (a == c) . . .
Аналогичная проблема возникает, когда вам требуется
сравнивать дальние указатели при помощи операций >, >=, < и <=. В
этих случаях в сравнении участвует только смещение (как unsigned);
при указанных выше значениях a, b и c следующие выражения дадут
значения "истина":
if (a > b) . . .
if (b > c) . . .
if (a > c) . . .
Операции равенства (==) и неравенства (!=) используют
32-битовые значения как unsigned long (а не в виде полного адреса
памяти). Операции сравнения (<=, >=, < и >) используют только
смещение.
Операции == и != требуют все 32 бита, что позволяет
компьютеру выполнять сравнение с пустым (NULL) указателем
(0000:0000). Если для проверки равенства использовалось только
значение смещения, то любой указатель со смещением 0000 будет
равен пустому указателю, что явно не совпадает с тем, что вы
хотели получить.
Важное замечание
_________________________________________________________________
При сложении некоторого значения с дальним указателем
изменяется только смещение. Если слагаемое настолько велико, что
сумма превышает FFFF (максимально возможная для смещения
величина), то указатель перейдет снова к началу сегмента.
Например, если сложить 1 и 5031:FFFF, то результат будет равен
5031:0000 (а не 6031:0000). Подобным же образом, при вычитании 1
из 5031:0000 получится 5031:FFFF (а не 5030:000F).
Если вам понадобится выполнить сравнение указателей, то
безопасный способ состоит в том, чтобы либо использовать для этого
ближние указатели - все с одним адресом сегмента - либо
описываемые ниже указатели huge.
Указатели huge
_________________________________________________________________
Указатели huge также имеют размер 32 бита. как и указатели
far, они содержат одновременно адрес сегмента и смещение. Однако,
в отличие от дальних указателей, они нормализованы, что позволяет
избежать проблем, связанных с дальними указателями.
Что такое нормализованный указатель? Это 32-битовый
указатель, который содержит в своем адресе сегмента максимально
возможное там значение. Поскольку сегмент может начинаться через
каждые 16 байт (10 при основании 16), это означает, что величина
смещения будет равна числу от 0 до 15 (от 0 до F с основанием 16).
Для нормализации указателя он преобразуется к 20-битовому
адресу, после чего правые 4 бита берутся в качестве смещения, а
левые 16 битов - как адрес сегмента. Например, указатель 2F84:0532
можно сначала преобразовать к абсолютному адресу 2FD72, после чего
получить нормализованный указатель 2FD7:0002. Приведем еще
ннесколько указателей с нормализованными значениями:
0000:0123 0012:0003
0040:0056 0045:0006
500D:9407 594D:0007
7418:D03F 811B:000F
Существует три причины, заставляющие всегда хранить указатель
huge в нормализованном виде:
1. Поскольку в таком случае любому заданному адресу памяти соот-
ветствует только один возможный адрес в виде сегмент:смещение
типа huge. Это означает, что для указателей huge операции == и
!= всегда будут возвращать правильное значение.
2. Кроме того, операции >, >=, < и <= работают с полным 32-битовым
значением указателя huge. Нормализация гарантирует в данном
случае корректность результата.
3. И наконец, вследствие нормализации смещение в указателе huge
выполняет автоматический переход через 16 но в отличие от
дальних указателей, переход затрагивает и сегмент. Например,
при инкременте 811B:000F результатбудет равен 811C:0000;
аналогичным образом, при декременте 800C:0000 получится
811B:000F. Эта особенность указателей huge позволяет
манипулировать со структурами данных с размером более 64К.
Гарантируется, например, что если у вас имеется массив структур
типа huge, превышающий 64К, индексация этого массива и выбор
поля структуры всегда будут выполняться правильно, независимо
от размера структуры.
Использование указателей huge имеет свою цену: увеличение
времени обработки. Арифметические операции с указателями huge
выполняются при помощи обращений к специальным подпрограммам.
Вследствие этого арифметические вычисления занимают существенно
больше времени по сравнению с вычислениями для указателей far или
near.
Шесть моделей памяти
Turbo C++ работает с шестью моделями памяти: tiny, small,
medium, compact, large и huge. выбор модели памяти определяется
требованиями вашей программы. Ниже приводятся краткие описания
каждой из них:
Tiny
Эта модель памяти используется в тех случаях, когда
абсолютным критерием достоинства программы является размер ее
звгрузочного кода.
Как вы уже поняли, это минимальная из моделей памяти. Все
четыре сегментных регистра (CS, DS, SS и ES) устанавливаются на
один и тот же адрес, что дает общий размер кода, данных и стека,
равный 64К. Используются исключительно ближние указатели.
Программы с моделью памяти tuny могут быть преобразованы к формату
.COM при компоновке с опцией /t.
Small
Эта модель хорошо подходит для небольших прикладных программ.
Сегменты кода и данных расположены отдельно друг от друга и
не перекрываются, что позволяет иметь 64К кода программы и 64К
данных и стека. Используются только ближние указатели.
Medium
Годится для больших программ, для которых не требуется
держать в памяти большой объем данных.
Для кода, но не для данных используются дальние указатели. В
результате данные плюс стек ограничены размером 64К, а код может
занимать до 1 Мб.
Compact
Лучше всего использовать в тех случаях, когда размер кода
невелик, но требуется адресация большого объема данных.
Ситуация, противоположная относительно модели Medium: дальние
указатели используются для данных, но не для кода. Следовательно,
код здесь ограничен 64К, а предельный размер данных - 1 Мб.
Large
Модели large и huge применяются только в очень больших
программах.
Дальние указатели используются как для кода, так и для
данных, что дает предельный размер 1 Мб для обоих.
Huge
Дальние указатели используются как для кода, так и для
данных. Turbo C++ обычно ограничивает размер статических данных
64К; модель памяти huge отменяет это ограничение, позволяя
статическим данным занимать более 64К.
Для выбора любой из этих моделей памяти вы должны либо
воспользоваться соответствующей опцией меню интегрированной среды,
либо ввести опцию при запуске компилятора командной строки.
Следующие иллюстрации (Рис.4.3 - 4.8) показывают, как
выполняется распределение памяти для всех шести моделей памяти
Turbo C++.
Сегментные регистры: Размер сегмента:
Младший ^ CS,DS,SS-----> ---------------------------
адрес / | _TEXT класс 'CODE' | \
| код |
|-------------------------|
| _DATA класс 'DATA' |
|инициализированные данные|
|-------------------------|
| _BSS класс 'BSS' |
DGROUP / |неинициализирован. данные| \ до 64К
\ |-------------------------| /
| куча |
| v |
|-------------------------| Свободная
||---область
SP(TOS)--->|-------------------------| памяти
| ^ |
Старший \ | стек |/
адрес v Начало SP----> ---------------------------
Рис.4.3 Сегментация для модели памяти tiny
Сегментные регистры: Размер сегмента:
Младший ^ CS-----------> ---------------------------
адрес | _TEXT класс 'CODE' |
| код | до 64К
DS,SS--------> |-------------------------|
/ | _DATA класс 'DATA' |\
|инициализированные данные|
|-------------------------|
| _BSS класс 'BSS' |
DGROUP / |неинициализирован. данные| \ до 64К
\ |-------------------------| /
| куча |
| v |
|-------------------------| Свободная
||---область
SP(TOS)--->|-------------------------| памяти
| ^ |
\ | стек |/
Начало SP----->|--------------------------
| дальняя | До конца
| куча v | памяти
|-------------------------| Свободная
Старший ||----область
адрес v --------------------------- памяти
Рис.4.4 Сегментация для модели памяти small
Сегментные регистры: Размер сегмента:
Младший ^ ---------------------------
адрес | _TEXT класс 'CODE'| до 64К
| sfile код |каждый sfile
DS,SS--------> |---/---------------------|
/ | / _DATA класс 'DATA' |\
Несколько ____|_/инициализирован. данные|
--------- |-------------------------|
|sfile A| | _BSS класс 'BSS' |
CS->|sfile B| |неинициализирован. данные| \ до 64К
| | |-------------------------| /
|sfile Z| | куча |
--------- | v |
DGROUP / |-------------------------| Свободная
\ ||---область
SP(TOS)--->|-------------------------| памяти
| ^ |
\ | стек |/
Начало SP----->|--------------------------
| дальняя | До конца
| куча v | памяти
|-------------------------| Свободная
Старший ||----область
адрес v --------------------------- памяти
Рис.4.5 Сегментация для модели памяти medium
CS указывает одновременно только на один sfile.
Сегментные регистры: Размер сегмента:
Младший ^ CS-----------> ---------------------------
адрес | _TEXT класс 'CODE' |
| код | до 64К
DS ----------> |-------------------------|
/ | _DATA класс 'DATA' |\
|инициализированные данные|
DGROUP / |-------------------------| \ до 64К
\ | _BSS класс 'BSS' | /
|неинициализирован. данные|
\ | |/
SS ---------> |-------------------------| Свободная
||----область
SP(TOS)---->|-------------------------| памяти
| ^ |
| стек | до 64К
Начало SP----->|--------------------------
| куча | До конца
| v | памяти
|-------------------------| Свободная
Старший ||----область
адрес v --------------------------- памяти
Рис.4.6 Сегментация для модели памяти compact
Несколько
---------
|sfile A|
CS->|sfile B|
| |
|sfile Z|<---
--------- \
\
\
\
\
Сегментные регистры: \ Размер сегмента:
Младший ^ ----\----------------------
адрес | \ _TEXT класс 'CODE'| до 64К
| sfile код |каждый sfile
DS ----------> |-------------------------|
/ | _DATA класс 'DATA' |\
| инициализирован. данные|
DGROUP / |-------------------------| \ до 64К
\ | _BSS класс 'BSS' | /
|неинициализирован. данные|
\ | |/
SS ---------> |-------------------------| Свободная
||----область
SP(TOS)---->|-------------------------| памяти
| ^ |
| стек | до 64К
Начало SP----->|--------------------------
| куча | До конца
| v | памяти
|-------------------------| Свободная
Старший ||----область
адрес v --------------------------- памяти
Рис.4.7 Сегментация для модели памяти large
Несколько
---------
|sfile A|
CS->|sfile B|
| |
|sfile Z|<---
--------- \
\
\
\
\
Сегментные регистры: \ Размер сегмента:
Младший ^ ----\----------------------
адрес | \ _TEXT класс 'CODE'| до 64К
| sfile код |каждый sfile
|-------------------------|
Несколько | |
--------- | |
|sfile A| | |
DS->|sfile B|<-|sfile _DATA класс 'DATA' | до 64К
| | |неинициализирован. данные| каждый sfile
|sfile Z| | |
SS ---------> |-------------------------| Свободная
||----область
SP(TOS)---->|-------------------------| памяти
| ^ |
| стек | до 64К
Начало SP----->|--------------------------
| куча | До конца
| v | памяти
|-------------------------| Свободная
Старший ||----область
адрес v --------------------------- памяти
Рис.4.8 Сегментация для модели памяти huge
Таблица 4.1. суммирует различные модели, а также результаты их
сравнения друг с другом. Эти модели часто группируются в
соответствии с тем, насколько малы (64К) или велики (1М) размеры
их модулей кода и данных; Эти группы соответствуют рядам и
колонкам табл. 4.1.
------------------------------------------------------------------
Размер | Размер кода
данных | ----------------------------------------------------------
| 64 K | 1 Mb
------------------------------------------------------------------
| Tiny (данные, коды |
64K | перекрываются |
| максимальный размер 64К) |
| |
| Small (без перекрытия | Medium (данные - small
| максимальный размер 128К) | коды - large)
| |
------------------------------------------------------------------
1Mb | Compact ( данные - large | Large ( данные и коды
| коды - small) | large)
| |
| | Huge (также как и large,
| | но cтатические
| | данные >64 K )
------------------------------------------------------------------
Важно!
Когда Вы компилируете модуль (исходный файл с некоторым
количеством процедур в нем), то результирующий код для этого
модуля не может превышать 64К, т.к. он должен вмещаться в один
кодовый сегмент. Это остается правилом, даже если Вы используете
один из больших кодовых модулей (medium, large, huge). Если ваши
модули очень велики ( > 64К ), Вы должны разбить их на несколько
маленьких исходных файлов, компилировать их раздельно, а потом
собирать в один файл. Аналогичным образом, несмотря на то, что
huge модель позволяет набору статических данных превышать размер
64 К, все равно он должен быть < 64K в каждом модуле.
Программирование с использованием различных моделей памяти:
адресные модификаторы
Turbo C++ вводит 8 новых ключевых слов, не имеющихся в
стандартном ANSI C ( near, far, huge, _cs, _ds, _es, _ss, _seg),
которые могут использоваться в качестве модификаторов адресных
указателей (а иногда и функций) с некоторыми ограничениями.
В Turbo C ++ вы можете модифицировать объявления функций и
адресных указателей с помощью ключевых слов near, far, huge. Мы
уже объяснили смысл указателей near, far, huge ранее в этой
главе. near-функции вызываются near-вызовами с последующим
near-выходом из них. Аналогичным образом far-функции вызываются
far-вызовами с последующим far-выходом из них. huge-функции ана-
логичны far-функциям, за исключением того, что huge-функции
устанавливают регистр DS в новое значение.
Кроме того имеются четыре специальных near-указателей
данных: _cs, _ds, _es, _ss. Это шестнадцатибитовые указатели,
которые специально ассоциируются с соответствующими сегментными
регистрами, например, если бы вы должны были объявить указатель
равным:
char _ss *p;
то р содержал бы в этом случае шестнадцатибитовое смещение в
сегменте стека.
Существует некоторое ограничение на использование
сегментных указателей:
- Вы не можете делать инкремент и декремент с указателями
сегментов. Когда вы прибавляете или вычитаете целое из
сегментного указателя, он автоматически преобразуется в
far-указатель, а арифметическая операция выполняется так, как
если бы целое было прибавлено или вычтено из far-указателя.
- Когда сегментные указатели используются в косвенном выражении
они также преобразуются в far-указатели.
- Так же как и расширение к двоичному + оператору, если
сегментный указатель прибавляется к near-указателю, результатом
будет far-указатель, который формируется путем взятия сегмента
из сегментного указателя и смещения из near-указателя. Такая
операция разрешается, только если оба указателя указывают на
одинаковый тип, или если один из указателей указывает на тип
void.
- Указатели сегментов можно сравнивать. Сравнение их выполняется
таким образом, как если бы их значения имели целочисленный тип
unsigned.
Функции и указатели в данной программе по умолчанию бывают
ближними или дальними, в зависимости от выбранной модели памяти.
Если функция или указатель являются ближними, то они автоматически
связываются с регистром CS или DS.
В следующей таблице показано, как это происходит. Отметим,
что размер указателя соответствует предельному размеру памяти,
равному 64К (ближний, в пределах сегмента) или 1 Мб (дальний,
содержит собственный адрес сегмента).
Типы указателей Таблица 4.2
-----------------------------------------------------------------
Модель памяти Указатели функции Указатели данных
-----------------------------------------------------------------
Tiny near, _cs near, _ds
Small near, _cs near, _ds
Medium far near, _ds
Compact near, _cs far
Large far far
Huge far far
-----------------------------------------------------------------
Указатели данных могут быть также объявлены с модификатором
_seg. Это 16-битовые указатели сегмента.
Объявление ближних или дальних функций
В некоторых случаях вам может захотеться (или понадобиться)
переопределить умолчание типа функции для модели памяти,
показанное в таблице 4.1 (стр.198 оригинала).
Например, вы используете модель памяти large, и в программе
имеется рекурсивная функция:
double power(double x,int exp)
{
if (exp <= 0)
return(1);
else
return(x * power(x, exp-1));
}
Каждый раз, когда power вызывает сама себя, она должна
выполнить дальний вызов, причем используется большая область стека
и число тактовых циклов. Объявив power как near, можно ускорить
выполнение ее благодаря тому, что вызовы этой функции будут
ближними:
double near power(double x,int exp)
Это гарантирует, что power может быть вызвана только из того
кодоваго сегмента, в котором она компилировалась, и что все
обращения к ней будут ближними.
Это означает, что при использовании большой модели памяти
(medium, large или huge) power можно вызывать только из того
модуля, в котором она определена. Прочие модули имеют свои
собственные кодовые сегменты и не могут вызывать функции near из
других модулей. Более того, ближняя функция до первого к ней
обращения должна быть либо определена, либо объявлена, иначе
компилятор не знает о необходимости генерировать ближний вызов.
И наоборот, объявление функции как дальней означает
генерирование дальнего возврата. В малых моделях кодовой памяти
дальняя функция должна быть объявлена или определена до первого к
ней обращения, что обеспечит дальний вызов.
Вернемся к примеру функции power. Хорошо также объявить power
как static, поскольку предусматривается вызывать ее только из
текущего модуля. Если функция будет объявлена как static, то имя
ее не будет доступно ни одной функции вне данного модуля.
Объявление указателей near, far или huge
Только что были рассмотрены случаи, в которых может
понадобиться объявить функцию с другой моделью памяти, нежели
остальная часть программы. Зачем то же самое может понадобиться
для указателей? По тем же причинам, что и для функций: либо для
улучшения характеристик быстродействия (объявив near там, где по
умолчанию было бы far), либо для ссылки за пределы сегмента по
умолчанию (объявив far или huge там, где по умолчанию бывает
near).
Разумеется, при объявлении функций или указателей с другим
типом, нежели по умолчанию, потенциально появляется возможность
ошибок. Предположим, имеется следующий пример программы с моделью
small:
void myputs(s)
char *s;
{
int i;
for (i = 0; s[i] != 0; i++) putc(s[i]);
}
main()
{
char near *mystr;
mystr = "Hello, world\n";
myputs(mystr);
}
Эта программа работает удовлетворительно, хотя объявление
mystr как near избыточно, поскольку все указатели, как кода, так и
данных, будут near по умолчанию.
Однако, что произойдет, если перекомпилировать эту программу
с моделью памяти compact (либо large или huge)? Указатель mystr в
main останется ближним (16-битовым). Однако, указатель s в myputs
теперь будет far, поскольку умолчанием теперь будет являться far.
Это означает, что попытка создания дальнего указателя приведет к
снятию со стека двух слов, и полученный таким образом адрес,
безусловно, не будет являться адресом mystr.
Как избежать этой проблемы? Решение состоит в том, чтобы
определить myputs в современном стиле С:
void myputs(char *s)
{
/* тело myputs */
}
Теперь при компиляции вашей программы Turbo C++ знает, что
myputs ожидает указатель на char; и поскольку компиляция
выполняется с моделью large, то известно, что указатель должен
быть far. Вследствие этого Turbo C++ поместит в стек регистр
сегмента данных (DS) и 16-битовое значение mystr, образуя тем
самым дальний указатель.
Если вы собираетесь явно объявлять указатели как far или
near, не забывайте использовать прототипы тех функций, которые
могут использовать эти указатели.
Как быть в обратном случае: когда аргументы myputs объявлены
как far, а компиляция выполняется с моделью памяти small? И в этом
случае без прототипа функции у вас возникнут проблемы, поскольку
main будет помещать в стек и смещение, и адрес сегмента, тогда как
myputs будет ожидать приема только одного смещения. При наличии
определений функций в прототипах main будет помещать в стек только
смещение.
Создание указателя данного адреса сегмент:смещение
__________________________________________________________________
Как создать дальний указатель на конкретный адрес памяти
(конкретный адрес сегмент:смещение)? Для этого можно
воспользоваться встроенной библиотечной подпрограммой MK_FP,
которая в качестве аргумента принимает сегмент и смещение, и
возвращает дальний указатель. Например,
MK_FP(segment_value, offset_value)
Имея дальний указатель fp, вы можете получить компонент
сегмента полного адреса с помощью FP_SEG(fp) и компонент смещения
с помощью FP_OFF(fp). Более полную информацию об этих трех
библиотечных функциях Turbo C++ см. в Справочнике по библиотеке.
Использование библиотечных файлов
Turbo C++ предлягает для каждой из шести моделей памяти
собственную версию библиотеки стандартных подпрограмм. Turbo C++
при этом проявляет достаточно "интеллекта", чтобы при компоновке
брать нужные библиотеки и в нужной последовательности, в
зависимости от выбранной вами модели памяти. Однако, при
непосредственном использовании компоновщика Turbo C++ TLINK (как
автономного компоновщика) вы должны явно указывать используемые
библиотеки. Более подробно это описано в разделе TLINK Главы 5Б
"Утилиты", Руководства пользователя.
Компоновка смешанных модулей
Что произойдет, если вы компилируете один модуль с
использованием модели памяти small, второй - модели large, и затем
хотите скомпоновать их? Что при этом произойдет?
Файлы скомпонуются удовлетворительно, но при этом вы
встретитесь с проблемами, которые будут аналогичны с описанными
выше в разделе "Объявление функций как near или far". Если функция
модуля с моделью small вызывает функцию в модуле с моделью large,
она будет использовать при этом ближний вызов, что даст абсолютно
неверные результаты. Кроме того, у вас возникнут проблемы с
указателями, описанные в разделе "Объявление указателей как near,
far или huge", поскольку функция в модуле small ожидает, что
принимаемые и передаваемые ей указатели будут near, тогда как
функция в модуле large ожидает рабрту с указателями far.
И снова решение заключается в использовании прототипов
функций. Предположим, что вы поместили myputs в отдельный модуль и
скомпилировали его с моделью памяти large. Затем вы создаете файл
заголовка myputs.h (либо с любым другим именем и расширением .h),
который содержит следующий прототип функции:
void far myputs(char far *s);
Теперь, если поместить main в отдельный модуль (MYMAIN.C) и
выполнить следующие установки:
#include
#include "myputs.h"
main()
{
char near *mystr;
mystr = "Hello, wirld\n";
myputs(mystr);
}
то при компиляции данной программы Turbo C++ считает прототип
функции из MYPUTS.H и увидит, что это дальняя функция, ожидающая
дальний указатель. Вследствие этого даже при модели памяти small
при компиляции будет сгенерирован правильный вызывающий код.
Что произойдет, если помимо этого вам требуется компоновка с
библиотечными подпрограммами? Лучший подход здесь заключается в
том, чтобы выбрать одну из библиотек с моделью large и объявить
все как far. Для этого сделайте копии всех файлов заголовка,
которые вы обычно включаете (таких, как stdio.h) и переименуйте
эти копии (например, fstdio.h).
Затем отредактируйте копии прототипов функций таким образом,
чтобы там было явно указано far, например:
int far cdecl printf(char far* format, ...);
Тем самым, не только вызовы подпрограмм будут дальними, но и
передаваемые указатели также будут дальними. Модифицируйте вашу
программу таким образом, чтобы она включала новый файл заголовка:
#include
main()
{
char near *mystr;
mystr = "Hello, world\n";
printf(mystr);
}
Скомпилируйте вашу программу при помощи TCC, затем
скомпонуйте ее при помощью TLINK, указав библиотеки с моделью
памяти large, напрмер CL.LIB. Смешивание модулей с разными
моделями - вещь экстравагантная, но допустимая; будьте, однако,
готовы к тому, что любые неточности здесь приводят к ошибкам,
которые очень трудно найти и исправиь при отладке.
Опции типа чисел с плавающей точкой
-----------------------------------------------------------------
С работает с двумя числовыми типами: интегральным (int,
short, long и т.д.) и с плавающей точкой (float double и long
double). Процессор вашего компьютер легко справляется с обработкой
чисел интегральных типов, однако числа с плавающей точкой отнимают
больше времени и усилий.
Однако, семейство процессоров iAPx86 имеет сопутствующее ему
семейство математических сопроцессоров, 8087, 80287 и 80387. Мы
будем обозначать все семейство математических сопроцессоров 80x87
термином "сопроцессор".
В случае процессора 80487 вы имеете математический
сопроцессор уже встроенным в основной.
80х87 представляет собой специальный аппаратно-реализованный
числовой процессор, который можно установить на вашем PC. Он
служит для выполнения с большой скоростью команд с плавающей
точкой. При большом количестве в вашей программе операций с
плавающей точкой вам, безусловно, нужен сопроцессор. Блок
центрального процессора в вашем компьютере осуществляет интерфейс
с 80х87 по специальным шинам интерфейса.
Эмулирование платы 80х87
По умолчанию в Turbo C++ устанавливается опция генерации кода
"эмуляция" (опция компилятора командной строки -f). Эта опция
предназначена для программ, которые могут вообще не иметь операций
с плавающей точкой, а также для программ, которые должны идти и на
машинах, на которых сопроцессор 808х87 не установлен.
В случае опции эмуляции компилятор генерирует код, как если
бы сопроцессор имелся, но при компоновке подключает библиотеку
эмуляции операций с плавающей точкой (EMU.LIB). При выполнении
такой программы сопроцессор 80х87, если он установлен, будет
использоваться; если же во время выполнения процессора не
окажется, то программа будет использовать специальное программное
обеспечение, эмулирующее 80х87.
Получение кода только для машин с 80х87
Если вы планируете использовать вашу программу исключительно
на машинах с установленным математическим сопроцессором 80х87, то
можно сэкономить около 10К памяти программы, опустив из нее логику
автоматического определения присутствия 80х87 и эмулятора. Для
этого следует просто выбрать опцию генерации кода операций с
плавающей точкой при наличии 80х87 (или опцию компилятора
командной строки -f87). Turbo C++ в этом случае скомпонует вашу
программу с библиотекой FP87.LIB вместо EMU.LIB.
Получение кода без операций с плавающей точкой
При отсутствии в программе операций с плавающей точкой вы
можете сэкономить немного времени компиляции, выбрав опцию
генерации операций с плавающей точкой None ("отсутствуют") (или
опцию компилятора командной строки -f-). Тогда Turbo C++ не будет
выполнять компоновку ни с EMU.LIB, ни с FP87.LIB, ни с MATHx.LIB.
Опция быстрых вычислений с плавающей точкой
Turbo C++ имеет опцию быстрых вычислений с плавающей точкой
(опция компилятора командной строки -ff). Выключить эту опцию
можно при помощи опции командной строки -ff-. Ее назначение
состоит в выполнении некоторой оптимизации, противоречащей
правильной семантике С. Например,
double x;
x = (float)(3.5*x);
Для вычисления по обычным правилам x умножается на 3.5, давая
точность результата double, которая затем усекается до точности
float, после чего x записывается как double. При использовании
опции быстрых вычислений с плавающей точкой произведение типа long
double преобразуется непосредственно в double. Поскольку лишь
очень немногие программы чувствительны к потере точности при
преобразовании от более точного к менее точному типу с плавающей
точкой, то данная опция является умолчанием.
Переменная операционной среды 87
При построении программы с эмуляцией 80x87, которая
устанавливается по умолчанию, ваша программа станет автоматически
проверять наличие 80х87 и использовать его, если он установлен в
машине.
Существует ряд ситуаций, в которых вам может понадобиться
отменить режим автоматического определения наличия сопроцессора по
умолчанию. Например, ваша собственная исполняющая система
можетиметь 80х87, но вам требуется проверить, будет ли программа
работать так, как вы предполагали, в системе без соопроцессора.
Либо ваша программа предназначена для работы в системе,
совместимой с PC, но данная конкретная система возвращает логике
автоматического определения наличия сопроцессора неверную
информацию (либо при оссутствии 80х87 говорит, что он на месте,
либо наоборот).
Turbo C++ имеет опцию для переопределения логики определения
наличия сопроцессора при загрузке программыж эта опция -
соответствующая переменная операционной среды системы 87.
Переменная операционной среды 87 устанавливается по
приглашению DOS при помощи команды SET:
C>SET 87=N
или
C>SET 87=X
Ни с какой стороны знака равенства не должно быть пробелов.
Установка переменной операционной среды 87 в N (это значит "Нет")
говорит загрузочному коду исполняющей системы о том, что вы не
хотите использовать 80х87 даже в том случае, если он установлен в
системе.
Установка переменной операционной среды в Y ("Да") означает,
что сопроцессор на месте и вы желаете, чтобы программа его
использовала. Программист должен знать следующее: !!! Если
установить 87=Y, а физически 80х87 в системе не установлен, то
система повиснет.
Если переменная операционной среды 86 была определена (в
любое значение), и вы желаете сделать ее неопределенной, введите
на приглашение DOS:
C>SET=
Непосредственно после знака равенства нажмите Enter, и
переменная 87 станет неопределенной.
Регистры и 80х87
Существует два момента, связанных с использованием регистров,
которые вы должны учитывать при работе с плавающей точкой:
1. В режиме эмуляции 80Х87 циклический переход в регистрах, а так-
же ряд других особенностей 80х87 не поддерживается.
2. Если вы смешиваете операции с плавающей точкой и встроенные ко-
ды на языке ассемблера, то при использовании регистров вы
должны принимать некоторые меры предосторожности. Это связано с
тем, что набор регистров 80х87 перед вызовом функции в Turbo
C++ очищается. Вам может понадобиться извлечь из стека и сохра-
нить регистры 80х87 до вызова функции, использующей сопроцессор,
если вы не уверены, что свободных регистров достаточно.
Отмена обработки особых ситуаций
для операций с плавающей точкой -------------------------------
По умолчанию программа на Turbo C++ в случае переполнения или
деления на ноль в операциях с плавающей точкой аварийно
прерывается. Вы можете замаскировать эти особые ситуации для
операций с плавающей точкой, вызывая в main _control87 перед любой
операцией с плавающей точкой. Например,
#include
main() {
_control87(MCW_EM,MCW_EM);
...
}
Вы можете определить особую ситуацию для операции с плавающей
точкой, вызвав функции _status87 или _clear87. См. описания этих
функций в Главе 1 Справочника по библиотеке.
Определенные математические ошибки могут также произойти в
библиотечных функциях; например, при попытке извлечения
квадратного корня из отрицательного числа. По умолчанию в таких
случаях выполняется вывод на экран сообщений об ошибке и возврат
значения NAN (код IEEE "not-a-number -- "не-число"). Использование
NAN скорее всего приведет далее к возникновению особой ситуации с
плавающей точкой, которая в свою очередь вызовет , если она не
замаскирована, аварийное прерывание программы. Если вы не желаете,
чтобы сообщение выводилось на экран, вставьте в программу
соответствующую версию matherr.
#include
int cdecl matherr(struct exception *e)
{
return 1; /* ошибка обработана */
}
Любое другое использование matherr для внутренней обработки
математических ошибок недопустимо, так как она считается
устаревшей и может не поддерживаться последующими версиями Turbo
C++.
Математические операции с комплексными числами
Комплексными называются числа вида x +yi, где x и y это
действительные числа, а i это корень квадратный из -1. В Turbo C++
всегда существовал тип
struct complex
{
double x, y;
};
определенный в math.h. Этот тип удобен для представления комплекс-
ных чисел, поскольку их можно рассматривать в качестве пары дейст-
вительных чисел. Однако, ограничения С делают арифметические опе-
рации с комплексными числами несколько громоздкими. В С++ операции
с комплексными числами выполняются несколько проще.
Для с комплексными числами в С++ достаточно включить
complex.h. В complex.h для обработки комплексных числе
перегружены:
- все обычные арифметические операции
- операции потоков >> и <<
- обычные арифметические функции, такие как sqrt и log
Дополнительную информацию см. в описании класса complex в
Справочнике по библиотеке.
Библиотека complex активизируется только при наличии
аргументов типа complex. Таким образом, для получении комплексного
квадратного корня из -1 используйте
sqrt(complex(-1))
а не
sqrt(-1)
Примером вычислений с комплексными числами служит следующая
функция, выполняющая комплексное преобразование Фурье:
#include
// вычисление дискретного преобразования Аурье для
// a[0],...,a[n-1]
void Fourier(int n, complex a[], complex b[])
{
int j,k;
complex i(0,1); // корень квадратный из -1
for (j = 0; j < n; ++j)
{
b[j] = 0;
for (k = 0; k < n; ++k)
b[j] += a[k] * exp(2*M_PI*j*k8i/n);
b[j] /= sqrt(n);
}
}
Использование двоично-десятичной (BCD) математики
Turbo C++, также как и большинство прочик компьютеров и
компиляторов, выполняет математические вычисления с числами в
двоичном представлении (то есть в системе счисления с основанием
2). Это иногда путает людей, привыкших исключительно к десятичной
математике (в системе счисления с основанием 10). Многие числа с
точным представлением в десятичной системе счисления, такие как
0.01, в двоичной системе счисления могут иметь лишь приближенные
представления.
Двоичные числа предпочтительны в большинстве прикладных
программ, однако в некоторых ситуациях ошибка округления в
преобразованиях меджу системами счисления с основаниями 2 и 10
нежелательна. Наиболее характерным случаев здесь являются
финансовые или учетные задачи, где предполагается сложение центов.
Рассмотрим программу, складывающую до 100 центов и вычитающую
доллар:
#include
int i;
float x =0.0;
for (i = 0; i < 100; ++i)
x += 0.01;
x -= 1.0;
print("100*.01 - 1 = %g\1",x);
Правильным ответом является 0.0, однако ответ, полученный
данной программой, будет малой величиной, близкой к 0.0. При
вычислении ошибка округления, возникающая во время преобразования
0.01 в двоичное число, накапливается. Изменение типа x на double
или long double только уменьшает ошибку вычисления, но не
устраняет ее вообще.
Для решения этой проблемы Turbo C++ предлягает специфичный
для C++ тип bcd (двоично-десятичный), объявленный в bcd.h. В
случае двоично-десятичного представления число 0.01 будет иметь
точное представление, а переменная x типа bcd даст точное
исчисление центов.
#include
int i;
bcd x = 0.0;
for (i = 0; i < 100; ++i)
x += 0.01;
x -= 1.0;
cout << "100*0.1 - 1 = " << x << "\n";
При этом необходимо учитывать следующие особенности типа bcd:
- bcd не уничтожает ошибку округления вообще. Вычисление типа
1.0/3.0 все равно будет иметь ошибку округления.
- Обычные математические функции, такие как sqrt и log, для аргу-
ментов с типом bcd перегружаются.
- Числа типа bcd имеют точность представления около 17 разрядов и
-125 125
диапазон принимаемых значений от 1x10 до 1x10.
Преобразования двоично-десятичных чисел
__________________________________________________________________
bcd - это определяемый тип, отличный от float, double или
long double; десятичная арифметика выполняется только когда хотя
бы один операнд имеет тип bcd.
Важное замечание !
Для преобразования двоично-десятичного числа обратно, к
обычной системе счисления с основанием 2 (тип float, double или
long double), служит функция-компонент real класса bcd; это
преобразование не выполняется автоматически. real выполняет все
необходимые преобразования к long double, который может быть затем
преобразован к другим типам при помощи обычных средств С.
Например,
bcd a = 12.1;
можно вывести при помощи любой из приводимых ниже строк кода:
double x = a; printf("a = %g", x);
printf("a = %Lg", real(a));
printf("a = %g", (double)real(a));
cout << "a =" << a;
Отметим, что поскольку printf не выполняет контроль типа
аргументов, спецификатор формата должен иметь L, если передается
значение real(a) типа long double.
Число десятичных знаков
__________________________________________________________________
Вы можете задать, сколько десятичных знаков должно
участвовать в преобразовании из двоичного типа в bcd. Это число
является вторым, опциональным аргументом в конструкторе bcd.
Например, для преобразования $1000.00/7 в bcd-переменную,
округленную до ближайшего цента, можно записать:
bcd a = bcd(1000.00/7, 2)
где 2 обозначает два разряда после десятичной точки. Таким образом,
1000.00/7 = 142.85714
bcd(1000.00/7, 2) = 142.860
bcd(1000.00/7, 1) = 142.900
bcd(1000.00/7, 0) = 142.000
bcd(1000.00/7, -1) = 140.000
bcd(1000.00/7, -2) = 100.000
Округление происходит по банковским правилам, что означает
оаругление до ближайшего целого числа, причем в случае одинакового
"расстояния" до ближайшего целого в прямую и обратную сторону
округление выполняется в сторону четного. Например,
bcd(12.335, 2) = 12.34
bcd(12.245, 2) = 12.34
bcd(12.355, 2) = 12.36
Использование оперативной памяти Turbo C++
При компиляции Turbo C++ не генерирует каких-либо
промежуточных структур данных на диске (Turbo C++ пишет на диск
только .OBJ-файлы); между проходами он помещает промежуточные
структуры данных в оперативную память. Вследствие этого вы можете
получить сообщение "Out of memory...", что означает, что
компилятору не хватает памяти.
Решение данной проблемы состоит в том, чтобы сделать ваши
функции меньше по размеру, либо разбить на несколько частей файл с
большими функциями. Можно также удалить из памяти ранее
размещенные там резидентные программы, чтобы освободить больше
памяти для использования Turbo C++.
Оверлеи (VROOMM)
Оверлеями называются части кода программы, разделяющие общую
область памяти. Одновременно в памяти находятся только те части
программы, которые в текущий момент нужны данной функции.
Оверлеи могут существенно снизить требования к выделяемой
программе во время выполнения памяти. При помощи оверлеев можно
создавать программы, значительно превышающие по размеру общую
доступную память системы, поскольку одновременно в памяти
находится лишь часть данной программы.
Работа программ с оверлеями
Программа управления оверлеями (VROOMM, или Virtual Run-time
Object-Oriented Memory Manager) очень сложна; она выполняет за вас
большую часть работы по организации оверлеев. В обычных оверлейных
системах модули группируются в базовый и набор оверлейных блоков.
Подпрограммы в данном оверлейном блоке могут вызывать подпрограммы
из этого же блока и из базового блока, но не из других блоков.
Оверлейные блоки перекрываются друг с другом; т.е. одновременно в
памяти может находиться только один оверлейный блок, и все они при
активизации занимают один и тот же участок физической памяти.
Общий объем памяти, необходимой для запуска данной программы,
определяется размером базового, плюс максимального оверлейного
блока.
Эта обычная схема не обеспечивает достаточной гибкости. Она
требует полного учета всех возможных обращений между модулями
программы и соответственной, планируемой вами, группировки
оверлеев. Если вы не можете разбить вашу программу в соответствии
со взаимозависимостями обращений между ее модулями, то вы не
сможете и разбить ее на оверлеи.
Схема VROOMM совершенно иная. Она обеспечивает динамический
свопинг сегментов. Базовым блоком свопинга является сегмент.
Сегмент может состоять из одного или нескольких модулей. И что еще
более важно, любой сегмент может вызывать любой другой сегмент.
Вся память делится на базовую область и область свопинга. Как
только встречается вызов функции, которая не находится ни в
базовой, ни в области свопинга, сегмент, содержащий вызываемую
функцию, помещается в обдасть свопинга, возможно, выгружая оттуда
при этом другие сегменты. Это мощное средство - подобное
виртуальной программной памяти. От вас больше не требуется
разбивать код на статические, отдельные оверлейные блоки. Вы
просто запускаете программу!
Что происходит, когда возникает необходимость поместить
сегмент в область свопинга? Если эта область имеет достаточно
свободного места, то данная задача выполняется просто. Если же
нет, то из области свопинга должно быть выгружено один или более
сегментов, чтобы искомая свободная область освободилась. Как
выбрать сегменты для выгрузки? Действующий здесь алгоритм очень
сложен. Упрощенная версия его такова: если в области свопинга
имеется неактивный сегмент, то для выгрузки выбирается он.
Неактивными считаются сегменты, в которых в текущий момент нет
выполняемых функций.В противном случае берется активный сегмент.
Удаление сегментов из памяти продолжается до тех пор, пока в
области свопинга не образуется достаточно свободной памяти для
размещения там требуемого сегмента. Такой метод называется
динамическим свопингом.
Чем больше памяти выделено для области свопинга, тем лучше
работает программа. Область свопинга работает как кэш-память: чем
больше кэш, тем быстрее работает программа. Наилучшие значения
размера области свопинга определяются размерами рабочего множества
данной программы.
После загрузки оверлея в память он помещается в оверлейный
буфер, который расположен в памяти между сегментом стека и дальней
кучей. По умолчанию размер оверлейного буфера вычисляется и
устанавливается при загрузке программы, но его можно изменить при
помощи глобальной переменной _ovrbuffer. Если достаточный размер
памяти недоступен, то появляется либо сообщение об ошибке DOS
("Program too big to fit in memory" - "Программа слишком велика
для имеющейся памяти"), либо сообщение кода загрузки С ("Not
enough memmory to run program").
Важной опцией программы управления оверлеями является его
способность при удалении моделей из оверлейного буфера выполнять
их свопинг с дополнительной расширенной памятью. Следующий раз,
как только данный модуль понадобится, он в этом случае будет не
считан с диска, а просто скопирован из этой памяти. Это
существенно ускоряет свопинг.
При использовании оверлеев память распределяется, как
показано на следующем рисунке:
Модель MEDIUM Модель LARGE
----------------- ------------ -----------------
| класс CODE | Резидентный | класс CODE |
| | код | |
--|----------------| |----------------|
Эти сегменты | | класс OVRINFO | Данные для | класс OVRINFO |
генерируются | | | управления | |
компоновщиком | | | оверлеями | |
автоматически-| |----------------| |----------------|
| | класс STUBSEG | Один сегмент | класс STUBSEG |
| | | stub для | |
| | | каждого | |
| | | оверлейного | |
| | | сегмента | |
--|----------------| ------------ |----------------|
| _DATA | | _DATA |
Ближняя куча | класс DATA | | класс DATA |
и стек разделяют | | | |
сегмент данных | ближняя куча | Отдельный |----------------|
| ^ | сегмент | ^ |
| стек | стека | стек |
|----------------| ------------ |----------------|
|оверлейный буфер| |оверлейный буфер|
|(распределяется | |(распределяется |
| при загрузке) | | при загрузке) |
|----------------| ------------ |----------------|
| дальняя | | дальняя |
| v куча | | v куча |
| | | |
------------------ ------------------
Модель HUGE
------------ -----------------
Резидентный | класс CODE |
код | |
-- |----------------|
Эти сегменты | Данные для | класс OVRINFO |
генерируются | управления | |
компоновщиком | оверлеями | |
автоматически-| |----------------|
| Один сегмент | класс STUBSEG |
| stub для | |
| каждого | |
| оверлейного | |
| сегмента | |
-- ------------ |----------------|
. . . Несколько |----------------|
сегментов |----------------|
данных |----------------|
|----------------|
Отдельный |----------------|
сегмент | ^ |
стека | стек |
------------ |----------------|
|оверлейный буфер|
|(распределяется |
| при загрузке) |
------------ |----------------|
| дальняя |
| v куча |
| |
------------------
Рис.4.9 Распределение памяти для оверлейных структур
Использование преимуществ использования оверлеев Turbo C++
__________________________________________________________________
Для полного использования преимуществ оверлейных структур,
создаваемых Turbo C,
- Минимизируйте резидентный код (резидентные библиотеки исполняю-
щей системы, обработчики прерываний и драйверы устройств).
- Установите размер оверлейного пула таким образом, чтобы добиться
наиболее комфортных условий для создаваемой программы (начните
со 128К и регулируйте этот размер вверх и вниз, пока не устано-
вите желаемое соотношение между бустродействием и размером прог-
раммы.
- Подумайте об универсальности и многосторонности создаваемого ко-
да: воспользуйтесь преимуществами оверлейной структуры и обес-
печьте поддержку обработки специальных случаев, интерактивную
справочную систему по программе и прочие не рассматриваемые
здесь средства, являющиеся достоинствами с точки зрения конечно-
го пользователя.
Требования
При создании оверлеев следует помнить несколько простых пра-
вил, а именно:
- Минимальная часть программы, которая может быть выделена в ка-
честве лверлея, это сегмент.
- Прикладные программы с оверлейной структурой должны иметь одну
из трех следующих моделей памяти: medium, large или huge; модели
tiny, small и compact оверлеи не поддерживают.
- Перекрывающиеся сегменты подчиняются обычным правилам слияния
сегментов. То есть, в одном и том же сегменте может участвовать
несколько .OBJ- файлов.
Генерирование оверлеев во время компоновки полностью
независимо от управления сегментами во время исполнения программы;
компоновщик не включает автоматически каких-либо кодов для
управления оверлеями. Действительно, с точки зрения компоновшика
программа управления оверлеями является просто одним из подлежащих
компоновке участков кода. Единственное предположение, которое
делает компоновщик, состоит в том, что программа управления
оверлеями принимает вектор прерываний (обычно INT 3FH), через
который происходит управление динамической загрузкой. Такой
уровень прозрачности упрощает создание пользовательских программ
управления оверлеями, наилучшим образом управляющих требованиям
конкретной прикладной программы.
Использование оверлеев
Для создания программы с оверлейной структурой все ее модули
должны компилироваться с включенной опцией компилятора -Y. Для
того, чтобы сделать оверлейным конкретный модуль, его следует
компилировать с опцией -Yo. (-Yo автоматически включает опцию -Y).
Опция -Yo распространяется на все модули и библиотеки,
следующие за ней в командной строке TCC. Отменить ее можно, задав
-Yo-. Эти две опции являются единственными опциями командной
строки, которые могут следовать после имен файлов. Например, для
того, чтобы сделать оверлейным модуль OVL.C, но не библиотеку
GRAPHICS.LIB, можно использовать любую из следующих командных
строк:
TCC -ml - Yo ovl.c -Yo- graphics.lib
или
TCC -ml graphics.lib -Yo ovl.c
Если при запуске TLINK явно задана компоновка файла .EXE, то
в командной строке компоновщика должна быть задана опция /o.
Использование опции /o описано в разделе TLINK главы 5, "Утилиты",
Руководства пользователя.
Пример оверлейной структуры
__________________________________________________________________
Предположим, вы хотите иметь оверлейную структуру в
программе, состоящей из трех модулей: MAIN.C, O1.C и O2.C.
Оверлеями должны являться модули O1.C и O2.C. (MAIN.C содержит
зависящие от текущего времени подпрограммы и обработчики
прерываний и потому должна оставаться резидентной). Предположим,
что данная программа использует модель памяти large.
Следующая команда позволяет выполнить данную задачу:
TCC -ml -Y main.c -Yo o1.c o2.c
В результате получится выполняющийся файл MAIN.EXE с двумя
оверлеями.
Организация оверлеев в интегрированной среде разработки (IDE)
__________________________________________________________________
Для организации оверлейных модулей в условиях интегрированной
среды разработки следует выполнить шаги:
1. Войти в диалоговое поле Options | Compiler | Code Generation и
установить опцию Overlays On.
2. Войти в Options | Linker и установит опцию Overlay On.
3. В менеджере проекта используйте элемент Options для задания
каждого модуля, который должен войти в оверлей.
Первый шаг представляет собой эквивалент интегрированной
среды разработки опции -Y компилятора командной строки. Если не
установить данную опцию в состояние on, то нельзя использовать и
две указанных далее опции. Вторая опция управляет тем, должна ли
данная информация о задании оверлеев использоваться во время
компоновки в IDE. Переведя эту опцию в состояние off, вы можете
тем самым глобально отменить создание ооверлеев, не выполняя
перекомпиляцию и не изменяя значений индивидуальных установок
модулей в менеджере проекта. Опция на третьем шаге управляет тем,
какие модули войдут в оверлеи, а какие останутся фиксированными.
Эта опция похожа на опцию командной строки -Yo.
Для построения .EXE-файла с оверлеями скомпилируйте все
модули с опцией Code Generation | Overlays On (сперва убедитесь,
что выбрана опция Options | Full Menus On).
Ни один из входящих в оверлей модулей никогда не должен
изменять умолчание имени класса кода (Code Class). Интегрированная
среда позволяет изменить набор входящих в оверлеи модулей, не
заботясь о рекомпиляции. Это возможно (с текущей информацией об
.OBJ-файлах) только при условии, что оверлеи сохраняют имена
класса кода по умолчанию.
Разработка программ с перекрытиями
Этот раздел содержит важные сведения о разработке программ с
перекрытиями. Тщательно изучите его, поскольку некоторые
рассматриваемые здесь вопросы жизненно важны для создания
прикладных программ с перекрытиями, имеющих высокие
характеристики.
Требование дальних (far) вызовов
__________________________________________________________________
При компиляции оверлейного модуля вы должны использовать
большую модель памяти (medium, large или huge). При всяком вызове
функции из оверлейного модуля вы обязаны гарантировать, что все
активные в текущий момент функции являются дальними.
Вы обязаны компилировать все оверлейные модули с опцией -Y,
что обеспечит оверлейную структуру генерируемого кода.
Важное замечание !
Невыполнение требования дальних вызовов в оверлейной
программе приведет при выполнении программы к непредсказуемым и
возможно, катастрофическим результатам.
Размер буфера
__________________________________________________________________
Размер оверлейного буфера по умолчанию в два раза превышает
размер самого большого оверлея. Для большинства прикладных
программ такое умолчание вполне адекватно. Одако же, представим
себе ситуацию, когда какая-либо функция программы реализована
несколькими модулями, каждый из которых является оверлейным. Если
общий размер этих модулей превышает размер оверлейного буфера, то
если модули часто вызывют друг друга, это приведет к большому
числу свопингов.
Очевидно, что решение здесь заключается в увеличении размера
оверлейного буфера до таких размеров, чтобы в любой момент времени
в нем помещались все часто вызывающие друг друга оверлеи. Это
можно сделать, установив через глобальную переменную _ovrbuffer
требуемый размер в параграфах. Например, для установки размера
оверлейного буфера равным 128К, включите в ваш код следующий
оператор:
unsigned _ovrbuffer = 0x2000;
Общей формулы для определения идеального размера оверлейного
буфера не существует. Turbo Prifiler фирмы Borland поможет
определить необходимое значение. Если вы не располагаете данным
продуктом, то найти размер буфера вам поможет понимание прикладной
задачи и немного зкспериментирования.
В каких случаях не следует создавать оверлейные структуры
__________________________________________________________________
Не создавайте оверлейных модулей, содержащих обработчики
прерываний, а также в случаях небольших или критичных к
быстродействию программ. Вследствие не-реентерабельной природы
операционной системы DOS модули, которые могут вызываться
функциями прерываний, не должны быть оверлейными.
Программа управления оверлеями Turbo C++ полностью
поддерживает передачу оверлейных функций как аргументов,
присвоение и инциализацию переменных типа указателя функции,
адресующих оверлейные функции, а также вызов оверлейных
подпрограмм через указатели функций.
Отладка оверлеев
__________________________________________________________________
Большинство отладчиков либо имеет весьма ограниченные
средства отладки программ с оверлейной структурой, либо вообще не
имеет таких средств. Иначе дело обстоит с интегрированным со
средой разработки отладчиком Turbo C++ и отладчиком автономным
отладчиком Turbo Debugger. Оба эти отладчика полностью
поддерживают пошаговую отладку и установку точек прерывания в
оверлеях совершенно прозрачным для вас способом. Благодаря
использованию оверлеев вы имеете возможность легко разрабатывать и
отлаживать громоздкие прикладные программы - как в интегрированной
среде, так и при помощи Turbo Debugger.
Внешние (external) подпрограммы в оверлеях
__________________________________________________________________
Подобно обычным функциям С, внешние (external) подпрограммы
на языке ассемблера должны подчиняться некоторым правилам, чтобы
хорошо работать с программой управления оверлеями.
Если подпрограмма на языке ассемблера выполняет вызов любой
оверлейной функции, то такая подпрограмма должна иметь объявление
FAR и должна устанавливать стековый фрейм при помощи регистра BP.
Например, если OtherFunc это оверлейная функция в другом модуле, и
ее вызывает подпрограмма на языке ассемблера ExternFunc, то тогда
ExternFunc должна быть FAR и устанавливать стековый фрейм, как
показано ниже:
ExternFunc PROC FAR
push bp ;сохранить bp
mov bp,sp ;установить стековый фрейм
sub sp,LocalSize ;распределить локальные переменные
...
call OtherFunc ;вызов другого оверлейного модуля
...
mov sp,bp ;освобождение локальных переменных
pop bp ;восстановление BP
RET ;возврат
ExternFunc ENDP
где LocalSize - это размер локальных переменных. Если LocalSize
равен нулю, вы можете опустить две строки, распределения и осво-
бождения локальных переменных, но ни в коем случае нельзя опускать
установку стекового фрейма BP, даже если аргументов и переменных в
стеке нет.
Эти требования остаются теми же в случае, когда TxternFunc
делает косвенные ссылки на оверлейные функции. Например, если
OtherFunc вызывает оверлейные функции, но сама не является
оверлейной, то ExternFunc должна быть FAR и также должна
устанавливать стековый фрейм.
В случае, когда яссемблерная подпрограмма не делает ни
прямых, ни косвенных ссылок на оверлейные функции, то специальные
требования отсутствуют; подпрограмма на языке ассемблера может
быть объявлена как NEAR. Она не обязана устанавливать стековый
фрейм.
Оверлейные подпрограммы на языке ассемблера не должны
создавать переменные в кодовом сегменте, поскольку все изменения,
внесенные в оверлейный кодовый сегмент, теряются при освобождении
оверлея. Подобним же образом, указатели объектов, расположенных в
оверлейных сегментах, не сохраняют достоверность после вызова
других оверлеев, поскольку программа управления оверлеями свободно
перемещает и освобождает оверлейные кодовые сегменты в памяти.
Свопинг
Если в системе компьютера установлена дополнительная или
расширенная память, вы можете сообщить программе управления
оверлеев, что тот должен использовать ее при свопинге. В этом
случае при удалении модуля из оверлейного буфера (когда туда
требуется загрузить новый модуль, а буфер полон) программа
управления оверлеями может поместить удаляемый модуль в эту
память. При любой последующей загрузке этого модуля экономится
время за счет того, что модуль перемещается в памяти, а не
считывается с диска.
В обоих случаях есть две возможности: программа управления
оверлеями может либо обнаруживать наличие дополнительной или
расширенной памяти самостоятельно и затем брать на себя управление
этой памятью, либо использовать уже обнаруженную и распределенную
часть такой памяти. В случае расширенной памяти обнаружение памяти
не во всех случаях выполняется удачно, поскольку многие программы
кэширования памяти и программы организации виртуального диска
могут использовать эту память, не делая об этом никаких отметок.
Чтобы избежать этих проблем, вы должны сообщить программе
управления оверлеями начальный адрес расширенной памяти и какой
участок ее можно безопасно использовать.
Дополнительная память (EMS)
Свопинг с дополнительной памятью инициализируется функцией
_OvrInitEms. Вот ее прототип:
extern int far _OvrInitEms
{
unsigned emsHandle,
unsigned emsFirst,
unsigned emsPages
}
_OvrInitEms и _OvrinitExt определены в dos.h
Если параметр emsHandle равен нулю, программа управления
оверлеями проверяет наличие дополнительной памяти и распределяет
ее количество (если может), достаточное для размещения там всех
оверлеев, минус размер оверлейного буфера. В противном случае
emsHandle должен быть допустимым логическим номером EMS, emsFirst
- первой используемой EMS страницей, а emsPages - числом доступных
программе управления оверлеями страниц. Если дополнительная память
доступна, то данная функция возвращает 0.
Расширенная память (Ext)
Свопинг с расширенной памятью инициализируется функцией
_OvrinitExt). Вот ее прототип:
extern int far -OvrininExt
{
unsigned long extStart,
unsigned long extLength
};
Если параметр extStart равен нулю, то программа управления
оверлеями проверяет наличие расширенной памяти. Если это возможно,
то для свопинга отводится ее участок, равный сумме всех оверлеев,
минус размер оверлейного буфера. В противном случае extStart
содержит начало для использования расширенной памяти, с extLength
байт, доступных для использования программой управления оверлеями.
Если extLength равна нулю, то программа управления оверлеями может
использовать всю расширенную память с адресами, старшими чем
extStart. Данная функция возвращает 0, если расширенная память
доступна. _OvrinitExt определена в dos.h.
Важное замечание !
Использование расширенной памяти не стандартизовано. Хотя
программа управления оверлеями и пробует применить все известные
методы для определения размера уже использованной расширенной
памяти, эту функцию следует применять осторожно. Например, если у
вас в системе установлена программа кэширования жесткого диска,
использующая 2 Мб оперативной памяти (и расширенной памяти в том
числе), то можно заставить программу управления оверлеями занять
остальную часть расширенной памяти при помощи вызова:
if (_OvrinitExt (1024L * (2048 + 1024), OL))
puts ("Доступная расширенная память для свопинга оверлеев
отсутствует");
Глава 5 Видео функции
Turbo C++ поставляется с полной библиотекой графических
функций, позволяющих создание экранных графиков и диаграмм. Данная
глава содержит краткое описание видео режимов и окон. Затем
объясняется, как программировать в текстовом и графическом
режимах.
Видео функции Turbo C++ аналогичны соответствующим
подпрограммам в Turbo Pascal. Если вы не знакомы с методами
управления экранными режимами вашего PC или создания и управления
окнами и графическими окнами, потратьте несколько минут и прочтите
краткое изложение этих вопросов.
Несколько слов о видео режимах
Ваш компьютер обязательно имеет некоторый видео адаптер. Это
может быть Монохромный дисплейный адаптер (MDA) для базового
(только текстового) дисплея, либо это может быть графический
адаптер, например Цветной графический адаптер (CGA), Улучшенный
графический адаптер (EGA), либо монохромный графический адаптер
Hercules. Каждый из этих адаптеров может работать в нескольких
режимах; режим определяет величину экрана - 80 или 40 символов в
строке (только в текстовом режиме), разрешающую способность экрана
(только в графическом режиме) и тип дисплея (цветной или
черно-белый).
Рабочий режим экрана определяется, когда ваша программа
вызывает одну из функций определения режима (textmode, Initgraph
или setgraphmode).
- В текстовом режиме экран компьютера разделен на ячейки (80 или
40 столбцов в ширину и 25, 42 или 50 строк по высоте). Каждая
ячейка состоит из аттрибута и символа . Символ представляет со-
бой имеющий графическое отображение ASCII-символ, а аттрибут за-
дает, каким образом данный символ будет выведен на экран (его
цвет, яркость, и т.д.). Turbo C++ предоставляет полный набор
подпрограмм для манипулирования текстовым экраном, для вывода
текста непосредственно на экран и управления аттрибутами ячеек.
- В графическом режиме экран компьютера делится на пиксели; каждый
пиксель представляет собой отображение на экране одной точки.
Число пикселей на экране (т.е. его разрешающая способность) за-
висит от типа подключенного к вашей системе видео адаптера и ре-
жима, в который установлен этот адаптер. Для получения на экране
графических изображений Turbo C++ предоставляет библиотеку гра-
фических функций: вы можете создавать на экране линии и формы,
заполненные шаблонами замкнутые области, а также управлять цве-
том каждого пикселя.
В текстовом режиме позиция верхнего левого угла экрана
определяется координатами (1,1), где x-координата растет
слева-направо, а y-координата растет сверху-вниз. В графическом
режиме позиция верхнего левого угла определяется координатами
(0,0), с теми же направления возрастания координат.
Несколько слов о текстовых и графических окнах
Turbo C++ обеспечивает функции для создания окон и управления
ими в текстовом режиме (и графических окон в графическом режиме).
Если вы не знакомы с текстовыми и графическими окнами,
ознакомьтесь со следующим кратким их изложением. Функции Turbo
C++, позволяющие управлять текстовыми и графическими окнами,
описаны ниже в разделах "Программирование в текстовом режиме" и
"Программирование в графическом режиме".
Что такое окно ?
Окно представляет собой прямоуголтную область, определенную
на видео экране вашего PC, когда он находится в текстовом режиме.
Когда ваша программа выполняет вывод на экран, то область вывода
будет в таком случае ограничена активным окном. Остальная часть
экрана (вне окна) остается бе изменений.
По умолчанию размер окна равен всему экрану. Ваша программа
может изменить данное умолчание полноэкранного текстового окна на
текстовое окно, меньшее чем полный экран (при помощи функции
window). Эта функция задает позицию окна в экранных координатах.
Что такое графическое окно ?
В графическом режиме вы также можете определить некоторую
прямоугольную область экрана PC; эта область называется
графическим окном. Когда ваша графическая программа выполняет
вывод рисунков и т.д., графическое окно действует как виртуальный
экран. Остальная часть экрана (вне графического окна) остается без
изменений. Определить графическое окно можно через экранные
координаты, вызвав функцию setviewport.
Координаты
За исключением функций определения текстовых и графических
окон, все остальные функции, как текстового, так и графического
режимов, даются в локальных координатах активного текстового или
графческого окна, а не в абсолютных экранных координатах. При этом
верхний левый угол текстового окна будет являться началом
координат (1,1); в графическом режиме начало координат
графического окна будет равно (0,0).
Программирование в текстовом режиме
В данном разделе приводится краткое изложение функций,
используемых в текстовом режиме. Более подробную информацию об
этих функциях см. в Главе 1, "Библиотека исполняющей системы" В
Справочнике по библиотеке.
В Turbo C++ пакет функций прямого ввода/вывода на консоль
(cprintf, cputs и т.д.) обеспечивает высококачественный вывод
текста, управление окнами, позиционирование курсора и управление
аттрибутами видео изображений. Все эти функции являются частью
стандартных библиотек Turbo C++; они имеют прототипы в файле
заголовка conio.h.
Функции консольного ввода/вывода
Функции текстового режима TUrbo C++ работают в любом из шести
возможных текстовых видео режимов. Режимы, доступные в вашей
системе, зависят от типа видео адаптера и монитора системы.
Текущий текстовый режим задается вызовом textmode. Мы объясним,
как использовать эту функцию, ниже в данной главе, и кроме того,
она описана в Главе 1 Справочника по библиотеке.
Функции текстового режима делятся на четыре отдельные группы:
- вывода и манипулирование текстом
- управления окнами и режимом
- управления аттрибутами
- запроса состояния
Эти четыре группы функций для текстового режима
рассматриваются в следующих четырех разделах:
Вывод и манипулирование текстом
_________________________________________________________________
Ниже перечислены функции вывода и манипулирования текстом в
текстовом режиме:
Запись и чтение текста:
cprintf Посылает на экран форматированный вывод.
cputs Посылает на экран строку.
getche Считывает символ с эхо-отображением его на экране
putch Посылает на экран отдельный символ.
Манипулирование текстом (и курсором) на экране:
clreol Стирание от курсора до конца строки.
clrscr Стирание текстового окна.
delline Удаление текущей строки курсора.
gotoxy Позиционирование курсора.
Insline Вставка пустой строки под текущей позицией
курсора.
movetext Копирование текста из одной области экрана на
другую.
Пересылка блоков текста между памятью и экраном:
gettext Копирование текста из области экрана в память.
puttext Копирование текста из памяти в область экрана.
По умолчанию ваши программы экранного вывода работают с
полноэкранным текстовым окном, поэтому вы можете в них сразу же
начинать писать, читать и манипулировать текстом без каких-либо
предварительных установок режима. Запись текста на экран
выполняется при помощи консольных функций прямого вывода cprintf,
cputs и putch, а ввод с консоли с эхо-отображением символов наа
экране выполняется функцией getche. Циклический переход текста по
экрану определяется глобальной переменной -wscroll. Если _wscroll
равна 1, то текст при достижении конца строки переходит на
следующую строку экрана, причем при необходимости выполняется
вертикальный скроллинг. Если _wscroll равен 0, то текст переходит
на ту же самую строку, и скроллинг не выполняется. По умолчанию
_wscroll равна 1.
После того, как ваш текст выведен на экран, вы можете стереть
активное окно при помощи clrscr, либо стереть часть строки от
текущей позиции курсора при помощи clreol, либо удальть целую
строку при помощи delline, либо вставить пустую строку при помощи
insline. Три последние функции работают относительно текущей
позиции курсора; перемещение курсора в желаемую позицию
выполняется при помощи gotoxy. Вы можете также скопировать целый
блок текста из одной прямоугольной области в другую при помощи
movetext.
Имеется также функция копирования прямоугольной области
текста с экрана в память gettext и обратная функция копирования
текста из памяти на экран (в любую позицию) puttext.
Управление окнами и режимом
_________________________________________________________________
Существует две функции управления окнами и режимом:
textmode Устанавливает текстовый режим экрана.
window Определяет окно текстового режима.
Функция textmode позволяет устанавливать для экрана один из
нескольких текстовых видео режимов (эта возможность ограничена
только типами монитора и адапрера, установленных в вашей системе.
Функция инициализирует экран как полноэкранное текстовое окно в
конкретном заданном режиме и очищает экран от любых оставщихся на
нем текстов или изображений.
Когда экран находится в текстовом режиме, вы можете выполнять
полноэкранный вывод на него, либо установить для вывода часть
экрана - окно - в которое ваша программа и будет выполнять
дальнейший вывод текста. Для создания текстового окна следует
вызвать функцию window, задав область на экране, которую это окно
будет занимать.
Управление аттрибутами
_________________________________________________________________
Ниже кратко перечислены функции управления аттрибутами видео
изображений в текстовом режиме:
Установка аттрибутов переднего плана и фона:
textattr Одновременная установка (аттрибутов) цветоа
переднего плана и фона.
textbackground Установка (аттрибута) цвета фона.
textcolor Установка (аттрибута) цвета переднего плана.
Модификация яркости:
highvideo Установка повышенной яркости текста.
lowvideo Установка низкой яркости текста.
normvideo Установка обычной яркости текста.
Функции управления аттрибутами устанавливают текущий
аттрибут, представляющий собой 8-битовое значение. Четыре младших
бита аттрибута задают цвет переднего плана, следующие три бита
задают цвет фона, а старший бит задает "мигание" изображения, для
которого установлен данный аттрибут.
Вывод всех последующих текстов выполняется с использованием
текущего аттрибута. Благодаря функциям управления аттрибутами вы
можете устанавливать цвета фона и переднего плана (символов) как
порознь (textbackground и textcolor), так и объединяя спецификации
этих цветов в одном обращении к textattr. Вы можете задавать также
аттрибут мигания переднего плана (т.е. собственно выводимых
символов). Большинство цветных мониторов в цветных режимах дает
истинные заданные цвета. Не-цветные мониторы могут преобразовывать
некоторые из этих аттрибутов или все аттрибуты в различные
монохроматические оттенки или другие визуальные эффекты, как
например, жирный шрифт, подчеркивание, инверсное изображение и
т.д.
Вы можете также переопределить для переднего плана
изображения яркость цветов вывода при помощи функции lowvideo
(которая выключает бит повышенной яркости отображения символов).
Либо наоборот, вы можете изменить яркость на повышенную при помощи
функции highvideo (которая включает бит повышенной яркости). Когда
выделение яркостью больше не требуется, можно восстановить
исходную установку яркости при помощи функции normvideo.
Запрос состояния
_________________________________________________________________
Ниже кратко перечислены функции, при помощи которых ваша
программа может сделать запрос состояния видео параметров:
gettextinfo Заполняет структуру text_info информацией о
текущем текстовом окне.
wherex Дает x-координату ячейки экрана, в которой в
текущий момент находится курсор.
wherey Дает y-координату ячейки экрана, в которой в
текущий момент находится курсор.
Функции консольного ввода/вывода Turbo C++ включают в себя
несколько специальных функций запроса состояния. При помощи этих
функций вы можете получить достеп к информации о текущем текстовом
режиме и текущей позиции курсора в этом окне.
Функция gettextinfo заполняет структуру text_info
(определенную в conio.h) подробными данными о текстовом окне,
включая:
- текущий видео режим
- позиция окна в абсолютных экранных координатах
- размеры окна
- текущие цвета переднего плана и фона
- текущая позиция курсора
Иногда вам нужны только некоторые из этих деталей. Вместо
обращения к полной информации о данном текстовом окне можно
отдельно получить сведения о текущей позиции (относительно начала
окна) курсора с помощью функций wherex и wherey.
Форма курсора
_________________________________________________________________
Для изменения способа представления курсора служит новая
функция _setcursortype. Она принимает значения _NOCURSOR, которая
вообще отменяет курсор, _SOLIDCURSOR, которая дает курсор в виде
сплошного прямоугольничка и _NORMALCURSOR, которое соответствует
обычному курсору в форме подчеркивания.
Текстовые окна
По умолчанию текстовое окно имеет размер всего экрана; это
умолчание можно изменить, создав окно, по размеру меньше экрана, с
помощью функции window. Текстовые окна могут содержать до 50 строк
и от 40 до 80 столбцов.
Начало координат окна (точка, от которой отсчитываются
координаты окна) в Turbo C++ лежит в верхнем девом углу окна.
Координаты верхнего левого угла окна равны (1,1); координаты
правого нижнего окна полноэкранного окна равны при 80 столбцах и
25 строках (80,25).
Пример
_________________________________________________________________
Предположим, у вас имеется 100%-совместимая с PC система,
находящаяся в текстовом режиме с 80 столбцами, и вы желаете
создать окно. Верхний левый угол окна должен иметь экранные
координаты (10.8), а нижний правый - (50,21). Для этого требуется
вызвать функцию window:
window(10, 8, 50, 21);
Теперь, создав текстовое окно, вы можете поместить курсор в
позицию окна (5,8) и вывести, начиная с этой позиции, некоторый
текст с использованием gotoxy и cputs.
gotoxy(5, 8);
cputs("С днем рождения, Френк Борланд");
Следующий рисунок иллюстрирует сказанное.
Столбец 1 экрана
|
Строка 1--xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
экрана x x
x x
x x
x x
x x
x x
Строка 1--x --- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx x
окна x x x x
x x x x
x x С днем рождения, Френк Борланд x x
x x x x
Строка 14-x---- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx x
окна x x
x | | x
x | | x
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-Строка
| | | 25
| | | экрана
| | |
Столбец 1 Столбец 41 Столбец 80
окна окна экрана
Рис.5.1 Окно в текстовом режиме 80x25
Тип text_mode
Вы можете перевести свой монитор в один из семи текстовых
режимов PC, вызвав для этого функцию textmode. Определяемый в
conio.h перечислимый тип text_mode позволяет использовать в
качестве аргумента режима, задаваемого при вызове text_mode, любое
символическое имя, определенное для данного перечислимого типа,
вместо "сырых" номеров режимов. Однако, чтобы воспользоваться
данными симвоическими константами, следует ввести:
#include
в исходный код программы.
Числовые и символические значения, определенные в text_mode,
приводятся ниже:
-----------------------------------------------------------------
Символическая Числовое
константа значение Текстовый видео режим
-----------------------------------------------------------------
LASTMODE -1 Предыдущий текстовый режим
BW40 0 Черно-белый, 40 столбцов
C40 1 16 цветов, 40 столбцов
BW80 2 Черно-белый, 80 столбцов
C80 3 16 цветов, 80 столбцов
MONO 7 Монохромный, 80 столбцов
C4350 64 EGA, 80x43; VGA, 80x50 строк
-----------------------------------------------------------------
Например, следующие вызовы textmode помещают ваш цветной
монитор в указанный рабочий режим:
textmode(0) Черно-белый, 40 столбцов
textmode(BW80) Черно-белый, 80 столбцов
textmode(c40) 16 цветов, 40 столбцов
textmode(3) 16 цветов, 80 столбцов
textmode(7) Монохромный, 80 столбцов
textmode(C4350) EGA, 80x43; VGA, 80x50 строк
После вызова textmode с режимом C4350 следует при помощи
функции settextinfo задать число строк экрана.
Цвета текста
Подробную информацию о том, как хранятся аттрибуты ячеек, см.
в описании textattr в Главе 1 Справочника по библиотеке.
Когда символ занимает ячейку, цвет этого символа называется
цветом переднего плана; цвет оставшейся части ячейки называется
цветом фона. Цветные мониторы с цветными видеоадаптерами могут
выводить до 16 различных цветов; монохромные мониторы заменяют
цвета различными визуальными аттрибутами (яркость, подчеркивание,
инверсное отображение и т.д.).
Включаемый файл conio.h определяет вместо различных цветов
символические имена. При использовании этих символических констант
вы должны включить в исходный код conio.h.
В следующей таблице приводятся эти символические константы и
соответствующие им числовые значения. Отметим, что только первые
восемь цветов являются доступными и для переднего плана, и для
фона; последние 8 цветов (номера 8 - 15) доступны исключительно
для переднего плана (собственно символов).
-----------------------------------------------------------------
Символическая Числовое Передний план
константа значение или фон?
-----------------------------------------------------------------
BLACK 0 Оба
BLUE 1 Оба
GREEN 2 Оба
CYAN 3 Оба
RED 4 Оба
MAGENTA 5 Оба
BROWN 6 Оба
LIGHTGRAY 7 Оба
DARKGRAY 8 Только передний план
LIGHTBLUE 9 Только передний план
LIGHTGREEN 10 Только передний план
LIGHTCYAN 11 Только передний план
LIGHTRED 12 Только передний план
LIGHTMAGENTA 13 Только передний план
YELLOW 14 Только передний план
WHITE 15 Только передний план
BLINK 128 Только передний план
-----------------------------------------------------------------
Если вы желаете, чтобы символ мигал, добавьте к аргументу
переднего плана символическую константу BLINK (числовое значение
128).
Высокоскоростной вывод: переменная directvideo
Пакет консольных функциий ввода/вывода Turbo C++ включает
переменную directvideo. Эта переменная управляет тем, выполняется
ли вывод на консоль из вашей программы непосредственно в
оперативную память дисплея (directvideo = 1), либо направлфется
туда через вызовы BIOS (directvideo = 0).
По умолчанию значение directvideo = 1 (консольный вывод
направляется непосредственно в память дисплея). В целом, работа
непосредственно с дисплейной памятью ускоряет вывод, но для этого
требуется 100% совместимость вашей машины с IBM PC. Ваша видео
аппаратура должна быть идентична дисплейным адаптерам IBM.
Установка directvideo=0 позволит вам работать на любой машине с
IBM-совместимым BIOS, но вывод на консоль будет несколько
замедлен.
Программирование в графическом режиме
В данном режиме приводится краткое изложение функций,
используемых в графическом режиме. Более подробная информация об
этих функциях находится в Главе 1 Справочника по библиотеке.
Turbo C++ имеет отдельную библиотеку с более чем 70
графическими функциями, начиная от функций высокого уровня (таких
как setviewport, bar3d и drawpoly) и кончая бит-ориентированными
функциями (ипа getimage и putimage). Графическая библиотека
поддерживает многочисленные стили линий и заполнителей, а также
предоставляют вам различные текстовые шрифты, которые вы можете
изменять по размерам, способу выравнивания, а также ориентировать
их либо по горизонтали, либо по вертикали.
Эти функции находятся в библиотечном файле GRAPHICS.LIB, а их
прототипы - в файле заголовка graphics.h. Кроме этих двух файлов,
в состав графического пакета входят драйверы графических устройств
(файлы *.BGI) и символьные шрифты (*.CHR-файлы).; эти
дополнительные файлы рассматриваются в следующих разделах.
Для использования графических функций:
- При работе в интегрированной среде переключите Full menus в
состояние on и выберите Options | Linker | Graphics Library. При
создании программы компоновщик автоматически выполнит компоновку
графической библиотеки Turbo C++.
- Если вы используете TCC.EXE, вы должны в командной строке ука-
зать GRAPHICS.LIB. Например, если ваша программа, MYPROG.C, ис-
пользует графику, то командная строка TCC должна иметь вид:
tcc myprog graphics.lib
Важное замечание !
Поскольку графические функции используют указатели far,
графика в случае модели рамяти tiny не поддерживается.
Графическая библиотека только одна и не имеет версий по
моделям памяти (по сравнению со стандартными библиотеками CS.LIB,
CC.LIB, CM.LIB и т.д., которые зависят от используемой модели
памяти). Каждая функция в GRAPHICS.LIB является far (дальней)
функцией, а графические функции, использующие указатели работают с
дальними указателями. Для правильной работы графических функций
требуется директива #include graphics.h в каждом использующем
graphics модуле.
Функции библиотеки graphics
Графические функции Turbo C++ делятся на несколько категорий:
- управления графической системой
- черчения и заполнения
- манипулирования экранами и графическими окнами
- вывода текстов
- управления цветами
- обработки ошибок
- запроса состояния
Управление графической системой
_________________________________________________________________
Ниже приводится краткое перечисление всех функций управления
графической системой:
closegraph Закрывает графическую систему.
detectgraph Проверяет аппаратное обеспечение и определяет,
какие графические драйверы использовать;
рекомендует предпочтительный режим.
graphdefaults Сбрасывает все переменные графической системы
в значения по умолчанию.
_graphfreemem Отменяет распределенную графике память; испо-
льзуется для определения собственной подпро-
граммы.
_graphgetmem Распределяет память графике; используется
для определения собственной подпрограммы.
getgraphmode Возвращает текущий графический режим.
getmoderange Допускает младший и старший допустимые режимы
для заданного драйвера.
initgraph Инициализирует графическую систему и переводит
аппаратное обеспечение в графический режим.
installuserdriver Инсталлирует дополнительный драйвер устройства
в таблицу драйверов устройста BGI.
installuserfont Загружает поставляемый файл штрихового шрифта в
таблицу символьных файлов BGI.
registerbgldriver Регистрирует внешний или загруженный
пользователем файл драйвера для включения во
время компоновки.
restorecrtmode Восстанавливает первоначальный (существовавший
до Initgraph) режим экрана.
setgraphbufsize Задает размер внутреннего графического буфера.
setgraphmode Выбирает заданный графический режим, очищает
зкран и восстанавливает все умолчания.
Графический пакет Turbo C++ обеспечивает графические драйверы
для следующих графических адаптеров (и полностью совместимых с
ними):
- Цветной/графический адаптер (CGA)
- Многоцветная графическая матрица (MCGA)
- Улучшенный графический адаптер (EGA)
- Видео графическая матрица (VGA)
- Графический адаптер Hercules
- Графический адаптер серии AT&T 400
- Графический адаптер 3270 PC
- Графический адаптер IBM 8514
Для запуска графической системы вы должны прежде всего
вызвать функцию initgraph. initgraph загружает графический драйвер
и переводит систему в графический режим.
Вы можете указать initgraph использование конкретного
графического драйвера и конкретный режим, либо задать
автодетектирование установленного видео адаптера и выбор
соответственного драйвера уже во время выполнения. Если вы задали
в initgraph автодетектирование, то она сама вызовет detectgraph
для выбора графического драйвера и режима. Если вы задали в
initgraph использование конкретного графического драйвера и
режима, то вы сами отвечаете за физическое присутствие
соответствующего аппаратного обеспечения. Если заставить initgraph
пытаться использовать отсутствующее аппаратное обеспечение, то
результат в таком случае непредсказуем.
После того, как графический драйвер загружен, вы можете
определить его имя при помощи функции getdrivename, а число
поддерживаемых драйвером режимов при помощи функции getmaxmode.
getgraphmode сообщит вам, в каком графическом режиме вы находитесь
в текущий момент. Имея номер режима, вы можете определить его имя
при помощи функции getmodename. Вы также имеете возможность
изменить графический режим при помощи функции setgraphmode и
вернуть исходный видео режим (тот, который был установлен до
инициализации графики) с помощью restorecrtmode. restorecrtmode
вернет экран в текстовый режим, но не закроет при этом графическую
систему (загруженные шрифты и драйверы останутся в памяти).
graphdefaults сбрасывает установки состояния графической
системы (размеры графического окна, цвет линий, цвет и шаблон
заполнителя и т.д.) в исходное состояние.
installuserdriver и installuserfont позволяют установить в
графической системе новые драйверы устройства и шрифты.
И наконец, закончив работу в графике, вы должны вызвать
функцию closegraph для того, чтобы закрыть графическую систему.
closegraph выгружает драйвер из памяти и восстанавливает
первоначальный видео режим (через обращение к restorecrtmode).
Более подробное рассмотрение графики
_________________________________________________________________
Выше кратко рассматривалась работа функции initgraph. В
следующих параграфах initgraph, _graphgetmem и _graphfreemem будут
описаны подробно.
Обычно подпрограмма initgraph загружает графический драйвер,
распределяя для этого драйвера память и затем загружая туда с
диска соответствующий файл .BGI. В качестве альтернативы данной
схеме динамической загрузки вы можете скомпоновать нужный файл
графического драйвера (или несколько таких файлов) непосредственно
с файлом выполняемой программы. Для этого .BGI-файл сначала
преобразуется в .OBJ-файл (при помощи утилиты BGIOBJ - см.
документацию в файле UTIL.DOC, который поставляется на одном из
дистрибутивных дисков), после чего в исходный код помещается вызов
registerbgidriver (до вызова initgraph), чтобы зарегистрировать
графический драйвер(ы) в системе. При построении программы вы
должны выполнить компоновку .OBJ-файлов всех зарегистрированных
драйверов. После определения того, который графический драйвер
должен быть использован (посредством detectgraph) initgraph
проверяет, был ли желаемый драйвер зарегистрирован. Если был, то
initgraph обращается к зарегистрированному драйверу
непосредственно в памяти. В противном случае initgraph
распределяет память для драйвера и зпгружает нужный .BGI-файл с
диска.
Примечание
Использование функции registerbgidriver относится к более
сложным методам программирования, не рекомендуемым для начинающих
программистов. Более подробно данная функция описана в Главе 1
Справочника по библиотеке.
Во время выполнения графической системе может понадобиться
выполнить распределение памяти для драйверов, шрифтов и внутренних
буферов. При необходимости она вызывает _graphgetmem для
распределения памяти и _graphfreemem для ее освобождения. По
умолчанию данные подпрограммы просто вызывают malloc и free,
соответственно.
Действие этих функций по умолчанию можно переопределить,
определив собственные функции _graphgetmem и _graphfreemem.
Благодаря этому вы можете сами управлять распределением памяти для
графики. Однако, ваши варианты функций управления распределением
памяти должны иметь те же имена: они заменят собой функции по
умолчанию с теми же именами из стандартных библиотек С.
Определив собственные функции _graphgetmem и _graphfreemem,
вы можете получить предупреждение "duplicate symbols" ("повторение
символических имен"). Это предупреждение можно игнорировать.
Черчение и заполнение
_________________________________________________________________
Ниже приводится краткий обзор функций черчения и заполнения:
Черчение:
arc Чертит дугу окружности.
circle Чертит окружность.
drawpoly Чертит контур многоугольника.
ellipse Чертит эллиптическую дугу.
getarccoords Возврашает координаты последнего вызова arc или
ellipse.
getaspectratio Возвращает коэффициент сжатия для текущего
графического режима.
getlinesettings Возвращает текущий стиль линии, шаблон линии и
толщину линии.
line Чертит линию из (x0,y0) в (x1,y1).
linerel Чертит линию в точку, задаваемую относительным
расстоянием от текущей позиции (CP).
lineto Чертит линию из текущей позиции (CP) в (x,y).
moveto Перемещает текущую позицию (CP) в (x,y).
moverel Перемещает текущую позицию (CP) на относительное
расстояние.
rectangle Рисует прямоугольник.
setaspectratio Изменяет коэффициент сжатия по умолчанию.
setlinestyle Устанавливает ширину и стиль текущей линии.
Заполнение:
bar Чертит и заполняет столбик.
bar3d Чертит и заполняет трехмерный столбик.
fillellipse Чертит и заполняет эллипс.
fillpoly Чертит и заполняет многоугольник.
getfillpattern Возвращает определяемый пользователем шаблон
заполнения.
getfillsettings Возвращает информацию о текущкм шаблоне и цвете
заполнения.
pieslice Чертит и заполняет сектор окружности.
sector Чертит и заполняет эллиптический сектор.
setfillpattern Выбирает шаблон заполнения, определяемый
пользвателем.
setfillstyle Устанавливает шаблон и цвет заполнения.
При помощи функций черчения и раскрашивания Turbo C++ вы
можете вычерчивать цветные линии, дуги, окружности, эллипсы,
прямоугольники, секторы, дву- и трехмерные столбики,
многоугольники, а такжеразличные правильные или неправильные
формы, являющиеся комбинациями перечисленных графических
примитивов. Ограниченную форму изнутри или снаружи можно заполнить
одним из 11 предопределенных шаблонов, либо шаблоном, определенным
пользователем. Можно также управлять толщиной и стилем линии
вычерчивания, а также местоположением текущей позиции (CP).
Линии и незаполненные формы вычерчиваются при помощи функций
arc, circle, drawpoly, ellipse, line, linerel, lineto и rectangle.
Затем можно заполнить эти формы с помощью floodfil, либо можно
объединить вычерчивание/заполнение в одном шаге при помощи функций
bar, bar3d, fillellipse, fillpoly, pieslice и sector. Функция
setlinestyle позволяет задать стиль линий (играничных линий форм):
толстая или тонкая, сплошная, пунктир и т.д., либоможно задать ваш
собственный шаблон для вычерчивания линии. Можно выбрать
предопределенный шаблон заполнения при помощи функции
setfillstyle, либо определить собственный шаблон заполнения в
setfill psttern. Функция moveto позволяет переместить CP в
желаемую позицию, а функция moverel позволяет сдвинуть ее на
желаемую величину смещения.
Выяснить текущий стиль и толщину линии позволяет функция
getlinesettings. Информацию о текущем шаблоне заполнения и цвете
заполнителя можно получить через функцию getfillsettings;
определяемый пользователем шаблон заполнения можно получить при
помощи getfillpattern.
Получить сведения о коэффициенте сжатия (коэффициенте
масштабирования, применяемом графической системой для того, чтобы
окружности выглядели круглыми) позволяет функция getaspectratio, а
получить координаты последней нарисованной дуги или эллипса -
функция getarccoords. Если окружности не получаются идеально
круглыми, можно исправить дело при помощи функции setaspectratio.
Манипулирование экраном и графическими окнами
_________________________________________________________________
Ниже приводится краткий обзор функций манипулирования с
экраном, графическими окнами, битовыми образами и пикселями:
Манипуляции с экраном:
cleardevice Очищает экран (активную страницу).
setactivepage Устанавливает активную страницу для
графического вывода
setvisualpage Устанавливает номер визуальной графической
страницы.
Манипуляции с графическими окнами:
clearviewport Очищает текущее графическое окно.
getviewsettings Возвращает информацию о текущем графическом
окне.
setviewport Устанавливает текущее графическое окно для
направления на него графического вывода.
Манипуляции с битовыми образами:
getimage Записывает битовый образ в заданный участок
памяти.
imagesize Возвращает число байт, требуемых для хранения
некоторой прямоугольной области экрана.
putimage Помещает на экран ранее записанный в память
битовый образ.
Манипуляции с пикселями:
getpixel Принимает цвет пикселя в (x,y).
putpixel Помещает пиксель на экран в (x,y).
Помимо черчения и закрашивания, графическая библиотека
предлягает несколько функций для манипулирования экраном,
графическими окнами, образами и пикселями. Вызвав cleardevice,
можно сразу очистить весь экран; данная подпрограмма стирает экран
и помещает CP в графическое окно, но при этом оставляет
действующими все прочие установки графической системы (стили
линии, заполнения и текста; раскраска, установки графического окна
и т.д.).
В зависимости от имеющегося у вас графического адаптера ваша
система может иметь от одного до четырех буферов экранных страниц,
представляющих собой области памяти, где хранится информация по
точкам о конкретных полноэкранных образах. Вы можете указать
активную страницу экрана (т.е. куда будет направлен вывод
графических функций), и визуальную страницу экрана
(т.е.находящуюся в текущий момент на дисплее) при помощи функций
setactivepage и setvisualpage, соответственно.
Когда ваш экран находится в графическом режиме, вы можете
определить графическое окно (или прямоугольное "виртуальное окно")
на экране с помощью функции setviewport. Позиция графического окна
задается в абсолютных экранных координатах; кроме того, задается
активное или неактивное состояние функции "отсечки". Очистка
графического окна выполняется при помощи clearviewport. Для того,
чтобы получить абсолютные экранные координаты и статус "отсечки",
следует воспользоваться функцией getviewsettings.
Можно взять часть экранного образа при помощи getimage,
вызвать imagesize для вычисления числа байтов для хранения этого
образа в памяти, а затем вернуть образ на экран (в любую желаемую
позицию) с помощью функции putimage.
Координаты всех функций вывода (черчения, заполнения, тексты
и т.д.) зависят от выбранного графического окна.
Можно также манипулировать цветом отдельных пикселей
благодаря функциям getpixel (возвращающей цвет данного пикселя) и
putpixel (которая отображает данный пиксель на экране заданным
цветом).
Текстовый вывод в графическом режиме
_________________________________________________________________
Ниже приводится краткое описание функций текстового вывода в
графическом режиме:
gettextsettings Возвращает текущий текстовый шрифт,
направление, размер и выравнивание.
outtext Посылает строку на экран в текущую позицию
(CP).
outtextxy Посылает текст на экран в заданную позицию.
registerbgifont Регистрирует прикомпонуемый или определяемый
пользователем шрифт.
settextjustify Устанавливает значения выравнивания текста,
используемые outtext и outtextxy.
settextstyle Устанавливает шрифт, стиль и коэффициент
увеличения текущего текста.
setusercharsize Устанавливает соотношение между высотой и
шириной штриховых шрифтов.
textheight Возвращает высоту строки в пикселях.
textwidth Возвращает ширину строки в пикселях.
Графическая библиотека включает в себя матричный шрифт 8х8 и
несколько штриховых шрифтов для вывода текста в графическом
режиме.
- В матричном битовом шрифте каждый символ определяется как матрица
пикселей.
- В штриховом шрифте каждый символ определяется как последователь-
ность векторов, сообщающих графической системе, как создается
данный символ.
Преимущество использования штриховых шрифтов становится
очевидным, когда вы начинаете рисовать большие по размеру символы.
Поскольку штриховой шрифт определяется как последовательность
векторов, при увеличении размера он сохранит хорошее разрешение и
качество изображения. И напротив, когда вы увеличиваете битовый
матричный шрифт, матрица умножается на соответственный коэффициент
масштабирования; чем больше этот коэффициент, темхуже становится
разрешение символов. Для малых размеров такой вид шрифта вполне
удовлетворителен, однако для больших размеров вам лучше выбрать
штриховой шрифт.
В графике текст выводится функциями outtext или outtextxy, а
управление его выравниванием (относительно CP) выполняет функция
settextjustify. Вы должны выбрать символьный шрифт, направление
его размещения (горизонтальное или вертикальное) и размер
(масштаб) при помощи функции settextstyle. Узнать текущие
установки вывода текстов можно при помощи функции gettextsettings,
которая возвращает текущий текстовый шрифыт, выравнивание,
увеличение и направление в структуре textsettings. setusercharsize
позволяет модифицировать ширину и высоту штриховых шрифтов.
Если средство отсечки включено, то выводимые функциями
outtext и outtextxy текстовые строки будут отсекаться по границам
графического окна. Если отсечка отключена, то тексты с матричным
шрифтом, символы которых не помещаются целиком в окне,
отбрасываются полностью; в случае же штриховых шрифтов не
поместившиеся тексты просто отсекаются по границе окна.
По умолчанию битовый 8х8 матричный шрифт является встроенным
в графический пакет и поэтому всегда доступен во время выполнения.
Штриховые шрифты все хранятся в отдельных .CHR-файлах; они могут
загружаться во время выполнения или преобразовываться в .OBJ-файлы
(при помощи утилиты BGIOBJ) и затем компоноваться с вашим
.EXE-файлом.
Обычно подпрограмма settextstyle загружает файл шрифта,
распределяя память для него и затем загружая с диска
соответствующий .CHR-файл. В качестве альтернативы данной схеме
динамической загрузки вы можете скомпоновать файл шрифта (или
несколько таких файлов) непосредственно с выполняемым файлом
программы. Для этого сначала требуется преобразовать .CHR-файл в
.OBJ-файл (с помощью утилиты BGIOBJ - прочтите об этом в файле
документации UTIL.DOC, который находится на дистрибутивном диске),
а затем поместить в исходную программу вызовы registerbgifont
(перед вызовом settextstyle) для того, чтобы зарегистрировать
данный символьный шрифт(ы). При построении программы необходимо
скомпоновать полученные .OBJ-файлы для всех зарегистрированных
вами штриховых шрифтов.
Примечание
Использование registerbgifont относится к сложным методам
программирования и не рекомендуется начинающим программистам.
Более подробно эта функция описана в UTIL.DOC, который находится
на дистрибутивном диске.
Управление цветом
_________________________________________________________________
Ниже приводится краткое описание функция для управления
цветом изображений:
Функции получения информации о цвете:
getbcolor Возврашает текущий цвет фона.
getcolor Возвращает текущий цвет вычерчивания.
getdefaultpalette Возвращает структуру определения палитры.
getmaxcolor Возвращает максимальное значение цвета,
доступное в текущем графическом режиме.
getpalette Возвращает текущую палитру и ее размер.
getpalettesize Возвращает размер просмотровой таблицы
палитры.
Функции установки одного или более цветов:
setallpalette Изменяет все цвета палитры, как задано.
setbkcolor Устанавливает текущий цвет фона
setcolor Устанавливает текущий цвет вычерчивания.
setpalette Изменяет один из цветов палитры, как
указано ее аргументами.
Прежде чем перейти к рассмотрению работы функций управления
цветом изображения, дадим базовое описание того, как эти цвета
фактически получаются на вашем графическом экране.
Пиксели и палитры
_________________________________________________________________
Графический экран представляет собой массив пикселей; каждый
пиксель соответствует одной (цветной) точке на экране. Значение
пикселя не задает точный цвет этой точки напрямую; на самом деле
это некоторый индекс таблицы цветов, называемой палитрой. Каждый
элемент палитры, соответствующий данному значению пикселя,
содержит точную информацию о цвете, которым будет отображен этот
пиксель.
Такая схема косвенных обращений имеет множество следствий.
Хотя аппаратное обеспечение может позволять отображение множества
цветов, одновременно на экране может находиться только некоторое
их подмножество. Количество одновременно находящихся на экране
цветов равно числу элементов палитры (размеру палитры). Например,
EGA позволяет наличие 64 цветов, но лишь 16 из них может
находиться на экране сразу; таким образом, размер палитры EGA
равен 16.
Размер палитры определяет диапазон значений, которые может
принимать пиксель, от 0 до (размер-1). Функция getmaxcolor
возвращает максимальное допустимое значение пикселя (размер-1) для
текущего графического драйвера и режима.
При обсуждении графических функций Turbo C++ мы часто
используем термин "цвет", например текущий цвет вычерчивания, цвет
заполнения и цвет пикселя. Фактически цветом мы здесь называем
значение пикселя: это некоторый индекс в палитре. Только палитра
реально определяет фактический цвет на экране. Манипулируя
палитрой, вы можете изменять фактические цвета, выводимые на
дисплей, даже хотя значения пикселей (цвета вычерчивания,
заполнения и т.д.) могут не изменяться.
Цвет фона и вычерчивания
_________________________________________________________________
Цвет фона всегда соответствует значению пикселя 0. Когда
выполняется очистка области экрана в цвет фона, это означает
просто установку всех пикселей этой области в значение 0.
Цветом вычерчивания называется значение, в которое
устанавливаются пиксели при вычерчивании линий. Цвет вычерчивания
устанавливается функцией setcolor(n), где n есть допустимое для
текущей палитры значение пикселя.
Управление цветом на CGA
Вследствие различий в графическом аппаратном обеспечении
фактическое управление цветами различно для CGA и EGA, что
заставляет нас рассмотреть их по отдельности. Управление цветом
для драйвера AT&T, а также режимы низкой разрешающей способности
драйвера MCGA аналогичны управлению цветом CGA.
В случае CGA вы можете выбрать либо режим низкой разрешающей
способности (320х200), который допускает использование четырех
цветов, либо режим высокой разрешающей способностей (640х200), где
допускается использование двух цветов.
CGA в режиме низкой разрешающей способности
_________________________________________________________________
В режиме низкой разрешающей способности вы имеете возможность
выбрать одну из четырех четырехцветных палитр. В каждой из этих
четырех палитр вы можете сами установить только первый (цвет 0)
элемент; цвета 1, 2 и 3 являются фиксированными. Первый элемент
палитры (цвет 0) - это цвет фона. Этот цвет может являться одним
из 16 имеющихся цветов (см. таблицу цветов фона, приводимую ниже).
Вы выбираете желаемую палитру, выбирая соответствующий режим
(CGAC0, CGAC1, CGAC2, CGAC3); эти режимы используют палитры цветов
от 0 до 3, соответственно, как показано в следующей таблице. Цвета
вычерчивания в CGA и эквивалентные им константы определяются в
graphics.h.
-----------------------------------------------------------------
Константа, присвоенная номеру цвета (значению пикселя)
Номер ------------------------------------------------------
палитры 1 2 3
-----------------------------------------------------------------
0 CGA_LIGHTGREEN CGA_LIGHTRED CGA_YELLOW
1 CGA_LIGHTCYAN CGA_LIGHTMAGENTA CGA_WHITE
2 CGA_GREEN CGA_RED CGA_BROWN
3 CGA_CYAN CGA_MAGENTA CGA_LIGHTGRAY
-----------------------------------------------------------------
Для того, чтобы назначить один из этих цветов цветом
вычерчивания CGA, нужно вызвать функцию setcolor, задав в ней в
качестве аргумента либо номер цвета, либо имя соответствующей
константы; например, если вы используете палитру 3 и желаете
назначить цветом вычерчивания cyan, то можно записать:
setcolor(1);
или
setcolor(CGA_CYAN);
В следующей таблице перечислены назначаемые для CGA цвета
фона:
-----------------------------------------------------------------
Числовое Символическое Числовое Символическое
значение имя значение имя
-----------------------------------------------------------------
0 BLACK 8 DARKGRAY
1 BLUE 9 LIGHTBLUE
2 GREEN 10 LIGHTGREEN
3 CYAN 11 LIGTHCYAN
4 RED 12 LIGHTRED
5 MAGENTA 13 LIGHTMAGENTA
6 BROWN 14 YELLOW
7 LIGHTGRAY 15 WHITE
-----------------------------------------------------------------
Цвета CGA для переднего плана те же, что находятся в данной
таблице.
Для назначения одного из этих цветов в качестве фонового
цвета служит функция setbkcolor(цвет), где цвет - это один из
элементов приведенной выше таблицы. Отметим, что для CGA цвет не
является значением пикселя (индексом в палитре); он прямо задает
фактический цвет, помещаемый в первый элемент палитры.
CGA в режиме высокой разрешающей способности
_________________________________________________________________
В режиме высокой разрешающей способности (640x200) CGA
работает с двумя цветами - черным цветом фона и цветным передним
планом. Пиксели могут принимать при этом значения только 0 или 1.
В связи с особенностями CGA цветом переднего плана фактически
является тот цвет, который аппаратное обеспечение считает цветом
фона; таким образом, цвет переднего плана устанавливается
подпрограммой setbkcolor. (Странно, но факт).
Цвет для переднего плана может быть выбран из предыдущей
таблицы. CGA далее будет использовать этот цвет для отображения им
всех пикселей, имеющих значение 1.
Следующие режимы работают аналогичным оюразрм: CGAHI,
MCGAMED, MCGAHI, ATT400MED и ATT400HI.
Подпрограммы управления палитрой в случае CGA
Поскольку палитра CGA является предопределенной, подпрограмму
setallpalette использовать в данном случае нельзя. Также не
следует использовать setpalette(индекс, фактический_цвет), за
исключением индекс=0. (Это альтернативный способ установки
фонового цвета CGA равным фактическому_цвету).
Управление цветом для EGA и VGA
_________________________________________________________________
В случае EGA палитра содержит 16 элементов из общего
количества 64 возможных цветов, причем каждый из элементов палитры
может быть задан пользователем. Доступ к текущей палитре
выполняется через getpalette, которая заполняет структуру,
включающую в себя размер палитры (16) и массив фактических
элементов палитры ("аппаратные номера цветов", хранимые в
палитре). Элементы палитры можно изменять как по отдельности при
помощи setpalette, либо все сразу через setallpalette.
Палитра EGA по умолчанию соответствует 16 цветам CGA, которые
были даны в предыдущей таблице цветов: черный равен элементу 0,
голубой равен элементу 1, ... , белый равен элементу 15. В
graphics.h определены константы, которые содержат соответствующие
цветам аппаратные значения: это EGA_BLACK, EGA_WHITE и т.д. Эти
значения могут быть также получены через getpalette.
Подпрограмма setbkcolor(цвет) на EGA работает несколько
иначе, чем на CGA. На EGA setbkcolor копирует фактическое значение
цвета, хранящееся в элементе #цвет, в элемент #0.
Что касается цветов, то драйвер VGA работает фактически так
же, как и драйвер EGA; он просто имеет более высокое разрешение (и
меньшие по размеру пиксели).
Обработка ошибок в графическом режиме
_________________________________________________________________
Ниже приводится краткий обзор функций обработки ошибок в
графическом режиме:
grapherrormsg Возвращает строку с сообщением об ошибке для
заданного кода ошибки.
graphresult Возвращает код ошибки для последней графической
операции, в которой встретилась ошибка.
Если ошибка произошла при вызове графической библиотечной
функции (например, не найден шрифт, запрошенный функцией
settextstyle), устанавливается внутренний код ошибки. Доступ к
коду ошибки для последней графической операции, сообщившей об
ошибке, выполняется при помощи graphresult. Определены следующие
коды возврата ошибки:
-----------------------------------------------------------------
Код Константа Соответствующая строка
ошибки графической_ошибки с сообщением об ошибке
-----------------------------------------------------------------
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" в Руководстве
пользователя. Turbo Assembler версии 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.
Последовательность передачи параметров Паскаля
__________________________________________________________________
Другим методом передачи параметров является стандартный метод
передачи параметров Паскаля (называемый также соглашением о связях
Паскаля). Это не значит, что вы можете вызывать из Turbo C++
функции 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 cdecl funca(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 code = _TEXT Код: DW _TEXT:xxx
data = _DATA Данные: DW DGROUP:xxx
dseg = DGROUP
Compact code = _TEXT Код: DW _TEXT:xxx
data = _DATA Данные: DD DGROUP:xxx
dseg = DGROUP
Medium code = имя_файла_TEXT Код: DD:xxx
data = _DATA Данные: DW DGROUP:xxx
dseg = DGROUP
Large code = имя_файла_TEXT Код: DD:xxx
data = _DATA Данные: DD DGROUP:xxx
dseg = 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 Assembler 2.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 Assembler 2.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++.
Псевдопеременные, встраиваемые ассемблерные коды и функции прерывания
Как быть в том случае, если вам требуется выполнить
какие-либо операции нижнего уровня, но при этом вы не хотите
связываться с созданием отдельного модуля на языку ассемблера?
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 int AX Общего назначения/сумматор
_AL unsigned char AL Младший байт AX
_AH unsigned char AH Старший байт AX
_BX unsigned int BX Общего назначения/индексный
_BL unsigned char BL Младший байт BX
_BH unsigned char BH Старший байт BX
_CX unsigned int CX Общего назн./счетчик циклов
_CL unsigned char CL Младший байт CX
_CH unsigned char CH Старший байт CX
_DX unsigned int DX Общего назн./хранение данных
_DL unsigned char DL Младший байт DX
_DH unsigned char DH Старший байт DX
_CS unsigned int CS Адрес кодового сегмента
_DS unsigned int DS Адрес сегмента данных
_SS unsigned int SS Адрес стекового сегмента
_ES unsigned 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
-----------------------------------------------------------------
aaa fdvtr fpatan lsl
aad feni fprem mov
aam ffroe** fplan mul
aas fiadd frndint neg
adc ficom frstor nop
add ficomp fsave not
and fidiv fscale or
bound fidifr fsqrt out
call fild fst pop
cbw fimul fstcw popa
clc fincstp** fslenv popi
cld finit fstp push
cli fist fstsw pusha
cmc fistp fsub pushf
cmp fisub fsubp rcl
cwd fisubr fsubr rcr
daa fld fsubrp ret
das fld1 ftst rol
dec fldcw fweit ror
div fldenv fxam sahf
enter fldl2e fxch sal
f2xm1 fldl2t fxtract sar
fabs fldlg2 fyl2x sbb
fadd fldln2 fyl2xp1 shl
faddp fldpi hlt shr
fold fldz idiv smsw
fbstp fmul imul stc
fchs fmulp in std
fclex fnclex inc sti
fcom fndisi int sub
fcomp fneni into test
fcompp fninit iret verr
fdecstp** fnop lahf verw
fdisi fnsave lds wait
fdiv fnstcw lea xchg
fdivp fnstenv leave xlat
fdivr fnstsw les xor
-----------------------------------------------------------------
При использовании средства встроенного ассемблирования в
подпрограммах, эмулирующих операции с плавающей точкой (опция TCC
-O), коды операции, помеченные **, не поддерживаются.
При использовании в операторах встроенного ассемблирования
мнемонических команд 80186 необходимо включать опцию командной
строки -1. Тогда компилятор включит в генерируемый им ассемблерный
код соответствующие операторы, в результате чего Turbo Assembler
будет ожидать появление данных мнемонических имен. При
использовании предыдущих версий ассемблера эти мнемонические имена
могут не поддерживаться.
Строковые команды
__________________________________________________________________
Помимо кодов операций, приведенных выше, возможно
использование следующих строковых команд, как в исходном виде, так
и с префиксами циклического выполнения.
Строковые команды Таблица 6.5
-----------------------------------------------------------------
caps lasw movsb outsw stos
capsb lods movsw scas stosb
capsw lodsb outs scasb stosw
las lodsw outsb scasw
lasb movs
-----------------------------------------------------------------
Префиксы
__________________________________________________________________
Допустимы следующие префиксы:
lock rep repe repne repnz repz
Команды перехода
__________________________________________________________________
Команды перехода рассматриваются отдельно. Поскольку метка не
может быть включена в саму команду, переходы выполняются к меткам
С (см. раздел "Использование команд перехода и меток" на стр.274
оригинала). В следующей таблице перечислены допустимые команды
перехода:
Команды перехода Таблица 6.6
-----------------------------------------------------------------
ja jge jnc jnp js
jae jl jne jns jz
jb jle jng jnx loop
jbe jmp jnge jo loope
jc jna jnl jp loopae
jcxz jnae jnle jpe loopnz
je jnb jno jpo loopz
jg jnbe
-----------------------------------------------------------------
Директивы ассемблирования
__________________________________________________________________
В операторах встроенного ассемблирования 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 21. Кроме того,
обратите внимание на то, что функция прерывания выполняет выход с
помощью команды IRET (возврата из прерывания).
Итак, в каких случаях может понадобиться написать собственный
обработчик прерываний? Дело в том, что так работает большинство
резидентных программ. Они инсталлируются как обработчики
прерываний. Тем самым, при выполнении некоторого периодического
или специального действия (тактовом сигнале часов, нажатии клавиши
и т.д.) происходит обращение к соответствующему обработчику
прерывания и соответствующие действия. Затем управление
возвращается программе, при выполнении которой встретилось данное
прерывание.
Практические примеры программ низкого уровня
Мы уже рассмотрели несколько примеров обращений к функциям
низкого уровня из С-программы; рассмотрим еще несколько таких
практических примеров. Начнем с обработчика прерывания, который
выполняет некоторые безвредные, но ощутимые (в данном случае
слышимые) действия: при его вызове раздается звуковой сигнал.
Прежде всего, напишем саму функцию: Она может выглядеть
следующим образом:
#include
void interrupt mybeep(unsigned bp, unsigned di, unsigned si,
unsigned ds, unsigned es, unsigned dx,
unsigned cx, unsigned bx, unsigned ax)
{
int i, j;
char originalbits, bits;
unsigned char bcount = ax >> 8;
/* прием текущих установок управляющего порта */
bits = originalbits = inportb(0x61);
for (i = 0; i <= bcount; i++) (
/* временное выключение динамика */
outportb(0x61, bits & 0xfc);
for (j = 0; j <= 100; j++)
; /* пустой оператор */
/* теперь динамик на некоторое время включается */
outportb(0x61, bits | 2);
for (j = 0; j <= 100; j++)
; /* еще один пустой оператор */
)
/* восстановление установок управляющего порта */
outportb(0x61, originalbits);
}
Затем напишем функцию, которая будет инсталлировать данный
обработчик прерываний. Ей передается адрес и номер обработчика (от
0 до 255 или от 0x00 до 0xFF).
void install(void interrupt (*faddr)(), int inum)
{
setvect(inum, faddr);
}
И наконец, вызовем для проверки написанную вами сигналящую
подпрограмму. Это сделает следующая функция:
void testbeep(unsigned char bcount, int inum)
{
_AH = bcount;
geninterrupt(inum);
}
Функция main может иметь вид:
main()
{
char ch;
install(mybeep,10);
testbeep(3,10);
ch = getch();
}
Вы можете также сохранить исходный вектор прерывания и
восстановить его при выходе из главной программы. Для этого служат
функции getvect и setvect.
Глава 7 Сообщения об ошибках
Turbo C++ различает две категории ошибок: времени выполнения
и времени компиляции. Сообщения об ошибках времени выполнения вы-
даются непосредственно при их обнаружении. Сообщения об ошибках
времени компиляции делятся на три категории: фатальные ошибки,
не-фатальные ошибки и предупреждения. Более подробно они описаны,
начиная со стр.283 оригинала.
Следующие обобщенные имена и значения используются в перечис-
ленных в данной главе сообщениях (большинство из них понятны и так
и в разъяснениях не нуждается). В фактических сообщениях об ошиб-
ках вместо обобщенных имен и значений подставляются конкретные.
-----------------------------------------------------------------
Обобщенное имя Фактическое имя или значение
в данном руководстве выводимое на экран
-----------------------------------------------------------------
аргумент Аргумент командной строки или иной аргумент
класс Имя класса
поле Ссылка на поле
имя_файла Имя файла (с расширением или без)
группа Имя группы
идентификатор Идентификатор (имя переменной или другой)
язык Название языка программирования
компонент Имя компонента данных или функции компонента
сообщение Строка сообщения
модуль Имя модуля
число Фактическое число
опция Опция командной строки или другая опция
параметр Имя параметры
сегмент Имя сегмента
спецификатор Спецификатор типа
символическое_имя Символическое имя
XXXXh 4-значное шестнадцатиричное число,
за которым следует h
-----------------------------------------------------------------
Сообщения об ошибках перечислены в алфавитном порядке, по
старшинству символов ASCII; обычно первыми идут сообщения, начина-
ющиеся символами (равенство, запятая, фигурная скобка и т.д.).
Поскольку сообщения, в которых на первом месте выводится имя, спе-
цифическое для данной программы, не могут быть расставлены по ал-
фавиту, то они также находятся в начальной части каждого списка
сообщений.
Например, если у вас имеется функция С++ goforit, то факти-
чески вы можете получить сообщение об ошибке
goforit must be declared with no arguments
Для того, чтобы найти описание данного сообщения в этой гла-
ве, искать следует сообщение
функция must be declared with no arguments
в начале списка сообщений об ошибках.
Если же некоторая переменная включается в текст сообщения
позже (например, "Incorrect command-line argument: аргумент"), то
такое сообщение можно найти по алфавиту, в данном случае на букву
I.
Сообщения об ошибках времени выполнения
-----------------------------------------------------------------
Количество ошибок времени выполнения в Turbo C++ невелико.
Эти ошибки могут быть обнаружены в уже откомпилированной и выпол-
няющейся программе. В данном разделе они перечислены в алфавитном
порядке и приводятся с объяснениями.
Эти ошибки могут являться следствием случайного затирание па-
мяти программой.
Abnormal program termination
Аварийное завершение программы
Данное сообщение может появляться, если для выполнения прог-
раммы не может быть выделено достаточного количества памяти. Более
подробно оно рассматривается в конце раздела, касающегося ошибок
операций с плавающей точкой. Вызов abort также приводит к появле-
нию данного сообщения.
Divide by 0
Деление на ноль
Данное сообщение выдается при целочисленном делении на ноль,
например
int n = 0;
n = 2 / n;
Эту ошибку можно отследить при помощи функции signal. В про-
тивном случае вызывается abort, и программа завершается.
Floating point error:Divide by 0.
Ошибка операции с плавающей точкой:Деление на 0.
Floating point error:Domain.
Ошибка операции с плавающей точкой:Выход из области определения.
Floating point error:Overflow.
Ошибка операции с плавающей точкой:Переполнение.
Данные фатальные ошибки являются следствием операции с плава-
ющей точкой, для которой результат не определен.
- "Деление на 0" означает, что результат равен в точности +INF или
-INF (плюс или минус неопределенность), например для операции
1.0/0.0.
- "Выход из области определения" означает, что результат равен NAN
(not a number - не число), например для 0.0/0.0.
- "Переполнение" означает, что результат равен +INF (неопределен-
ность) или -INF при полной потере точности, например в случае
присвоения 1e200*1e200 переменной типа double.
Floating point error:Partial loss of precision.
Ошибка операции с плавающей точкой:Частичная потеря точности.
Floating point error:Underflow.
Ошибка операции с плавающей точкой:Отрицательное переполнение.
Эти особые ситуации по умолчанию маскируются, и сообще-
ния об ошибках не выдаются. Отрицательное переполнение преобразу-
ется в ноль,а потери точности игнорируются. Отменить маску можно,
вызвав _control87.
Floating polnt error:Stack fault.
Ошибка операции с плавающей точкой:Сбой в стеке.
Это сообщение указывает на выход за пределы стека плавающей
точки. В нормальных условиях оно не появляется и поэтому может оз-
начать либо то, что ассемблерный код использует слишком много ре-
гистров, либо неправильное объявление функции с плавающей точкой.
Этой ошибки можно избежать, маскируя особые ситуации таким
образом, чтобы они не появлялись, либо перехватывая их функцией
signal. См. подробное описание функций _control87 и signal в Спра-
вочнике по библиотеке.
В каждом из приведенных случаев программа печатает сообщение
об ошибке и затем вызывает abort, которая в свою очередь выдает
сообщение
Abnormal program termination
и вызывает _exit(3). См. подробные описания функций abort и _exit.
Null pointer assignment.
Присвоение пустому указателю
При выходе из программы с моделью памяти small или medium вы-
полняется проверка, чтобы определить, были ли изменены значения
двух первых байтов в сегменте данных программы. Эти байты никогда
не должны изменяться работающей программой. Если же они были изме-
нены, то выдается сообщение "Null pointer asignment", говорящее о
том, что (вероятно) некоторое значение было записано в неинициали-
зированный указатель. Во всех прочих отношениях программа может
работать правильно; однако данная ошибка является серьезной ошиб-
кой, и ей следует заняться немедленно. Если вы не сможете испра-
вить неинициализированный указатель, это приведет к непредсказуе-
мому поведению компьютера (вплоть до его "блокирования" в случае
моделей памяти large, compact и huge.) Для отслеживания таких оши-
бок может служить интегрированный отладчик, входящий в среду раз-
работки.
Stack overflow
Переполнение стека
По умолчанию размер стека для программ Turbo C++ равен 4,096
байт. Для большинства программ этого достаточно, однако программы
с рекурсивными функциями или большими объемами локальных данных
могут переполнить стек. Данное сообщение выдается только в том
случае, когда включено средство контроля стека. При получении это-
го сообщения вы можете перейти к большей модели памяти, увеличить
размер стека, либо уменьшить использование стека вашей программой.
Информацию о том, как изменить размер стека с помощью глобальной
переменной _stklen, см. в Главе 2, "Глобальные переменные" в Спра-
вочнике по библиотеке. Для уменьшения количества используемых фун-
кцией локальных данных можно поступить так, как показано в приводи-
мом ниже примере. Переменная buffer объявлена как static и потому,
в отличие от list, не расходует стек.
void anyfunction( void )
{
static int buffer[ 2000 ];/*размещается в сегменте данных*/
int list[ 2000 ]; /*размещается в стеке*/
}
Объявление локальных переменных как static имеет два недос-
татка.
1. Теперь такая переменная занимает место, отводимое обычно гло-
бальным переменным и куче. (Чтобы заплатить Павлу, приходится
грабить Петра). Однако этот недостаток не самый главный.
2. Функция не может более являться реентерабельной. Это означает,
что если функция должна вызываться рекурсивно или асинхронно, и
при этом важно, чтобы каждый вызов функции работал со своей
собственной копией переменной, то такая переменная не может яв-
ляться статической. Это связано с тем, что при каждом вызове
функции данная переменная будет занимать ту же самую область
памяти, вместо того, чтобы ей распределялась всякий раз новая.
Также возникает проблема с разделением доступа к переменной,
если функция содержит вызов самой себя (рекурсивно), либо долж-
на выполняться одновременно сама с собой (асинхронно). Для
большинства программ DOS это не проблема. Если вы не пишете ре-
курсивных функций и не работаете в мультизадачном режиме, то
вам не о чем беспокоиться. В противном случае приведенные выше
объяснения помогут вам разобраться, что к чему.
Сообщения об ошибках компилятора
-----------------------------------------------------------------
Диагностические сообщения компилятора Turbo C++ делятся на
три категории: фатальные ошибки, ошибки и предупреждения.
Фатальные ошибки встречаются редко. Некоторые из них обозна-
чают внутреннюю ошибку компилятора. В случае фатальной ошибки ком-
пиляция немедленно прекращается. Далее вы должны предпринять соот-
ветствующие действия и затем повторить компиляцию.
Ошибки обозначают синтаксические ошибки программы, ошибки
доступа к диску или к памяти, а также ошибки командной строки.
Компилятор закончит текущую фазу компиляции и остановится. На каж-
дой фазе (препроцессор, лексический анализ, оптимизация и генера-
ция кода) компилятор пытается найти столько фактических ошибок,
сколько возможно.
Предупреждения не приводят к остановке работы компилятора.
Они лишь указывают на ситуации, которые кажутся компилятору подоз-
рительными, но являются допустимыми с точки зрения языка. Компиля-
тор также выдает предупреждения о наличии в исходных файлах маши-
но-зависимых конструкций.
В сообщении компилятор сначала указывает класс этого сообще-
ния, затем имя исходного файла, далее номер строки, в которой об-
наружена ошибка, и наконец, собственно текст сообщения об ошибке.
В приводимых ниже перечнях сообщения об ошибках представлены
по классам в алфавитном порядке. Для каждого сообщения приводится
возможная причина его появления и рекомендации по устранению ошиб-
ки.
Вы должны знать о номерах строк в сообщениях следующее: ком-
пилятор генерирует сообщения об ошибках по мере их обнаружения.
Поскольку С никак не регламентирует размещение операторов по стро-
кам текста, истинная причина ошибки может находиться несколькими
строками выше, чем указанная в сообщении строка. В следующих пе-
речнях сообщений мы отметили те из них, которые часто на самом де-
ле находятся выше, нежели строка, указанная компилятором.
Фатальные ошибки
-----------------------------------------------------------------
Bad call of inline function
Неправильный вызов встроенной функции
Вы вызвали встроенную функцию из макро определения, но сдела-
ли это неправильно. Встроенная функция в С должна начинаться двумя
символами подчеркивания (__).
Irreducible expression tree
Неупрощаемое дерево выражения
Это сообщение указывает на некоторую ошибку компилятора. Вы-
ражение в указанной строке исходного файла вызвало условие, когда
генератор кода не может выполнить свою работу. Как бы это выраже-
ние ни было необходимым, его следует переписать. В случае, если
вы получите такое сообщение, уведомьте об этом Borland.
Out of memory
Недостаточно памяти
Исчерпана общая рабочая память. Повторите компиляцию этого
файла на машине с большей доступной памятью. Если у вас и так име-
ется 640К, следует упростить исходный файл.
Register allocation error
Ошибка распределения регистров
Это сообщение указывает на некоторую ошибку компилятора. Вы-
ражение в указанной строке исходного файла оказалось настолько
сложным, что генератор кода не смог выполнить свою работу. Упрос-
тите это выражение. Если это не помогло, постарайтесь обойтись без
него. В случае, если вы получите такое сообщение, уведомьте об
этом Borland.
Ошибки
-----------------------------------------------------------------
конструктор cannot return a value
конструктор не может возвращать значение
Конструктор С++ не может иметь выражения в операторе возвра-
та.
конструктор is not a base class of класс
конструктор не относится к базовому классу класс
Конструктор класса С++ "класс" пытается вызвать конструктор
базового класса "конструктор", либо вы пытаетесь изменить права
доступа класс::конструктор. Конструктор не относится к базовому
классу "класс". Проверьте объявления.
функция1 cannot be distingulshed from функция2
функция1 не отличается от функции2
Списки типов параметров в объявлениях этих двух функций не-
достаточно отличаются, чтобы эти функции отличались друг от друга.
Попытайтесь изменить в объявлении одной из них последовательность
или тип параметров.
функция is ambiguous
функция неоднозначна
Данному вызову "функции" соответствует по списку аргументов
более одной перегруженной функции (с учетом преобразования ар-
гументов по умолчанию). Используйте явное приведение типов одного
или более аргументов для разрешения этой неоднозначности.
функция must be declared with no arguments
функция должна быть объявлена без аргументов
Данная функция-операция С++ была неправильно объявлена с ар-
гументами.
функция must be declared with one argument
функция должна быть объявлена с одним аргументом
Данная функция-операция С++ была неправильно объявлена с бо-
лее чем одним аргументом
функция must be declared with two arguments
функция должна быть объявлена с двумя аргументами
Данная функция-операция С++ была неправильно объявлена с чис-
лом аргументов, отличным от двух.
функция was previously declared without static
функция была ранее объявлена без атрибута static
Здесь функция объявлена как static, тогда как выше она была
объявлена как extern (или global). ANSI C не позволяет смешивать
эти объявления.
функция was previously declared with the language язык
функция была выше объявлена с модификатором языка "язык"
Функция может иметь только один модификатор языка (cdecl,
pascal или interrupt). Данная функция в двух местах была объявлена
с двумя разными модификаторами языка.
идентификатор cannot be declared in an anonymous union
идентификатор не может быть объявлен в анонимном объединении
Компилятор обнаружил объявление функции компонента или
статического компонента в анонимном объединении. Такие объединения
могут содержать только компоненты данные.
идентификатор cannot be used in a static member function
идентификатор не может использоваться в статической функции-
компоненте
Статическая функция-компонент может использовать только
статические компоненты своего класса, хотя и имеет полные права
доступа. Данная ошибка является результатом попытки использования
компонента, для которого требуется указатель this.
идентификатор is inaccessible because also in класс
идентификатор недоступен поскольку также используется в классе
Недопустимо использовать класс в качестве и прямого, и кос-
венного базового класса, поскольку поля его автоматически стано-
вятся неоднозначными. Попытайтесь сделать базовый класс виртуаль-
ным в обоих местах.
идентификатор is not a data member and can't be initlallzed here
идентификатор не является компонентом данных и не может быть здесь
инициализирован
В инициализаторах конструктора допускается инициализация
только компонентов данных. Это сообщение означает, что список
включает в себя статический компонент или функцию-компонент.
идентификатор is not a member of struct
идентификатор не является компонентом структуры
Вы пытаетесь сослаться на идентификатор, как на компонент
структуры, в то время как он не является компонентом структуры.
Проверьте объявления.
идентификатор is not a parameter
идентификатор не является параметром
В разделе объявления параметров определения функции по образ-
цу старых версий идентификатор объявлен, но не перечислен в списке
параметров. Либо уберите объявление, либо добавьте идентификатор в
качестве параметра.
идентификатор is not legal here
идентификатор здесь недопустим
Спецификатор типа "идентификатор" здесь недопустим, поскольку
он противоречит или дублирует другой спецификатор типа в данном об-
ъявлении, либо потому, что "идентификатор" используется в качестве
имени typedef, тогда как имени typedef в данном контексте быть не
может.
идентификатор is virtual and cannot be explicitly initialized
идентификатор виртуальный и не может быть явно инициализирован
Конструктор класса С++ пытается вызвать "идентификатор" конс-
труктора базового класса, однако этот идентификатор относится к
виртуальному базовому классу. Виртуальные базовые классы не могут
быть инициализированы явно. Компилятор неявно вызывает конструктор
базового класса по умолчанию base::base().
идентификатор must be a member function
идентификатор должен быть функцией-компонентом
Большинство функций-операций С++ может являться компонентами
классов или обычными функциями, не входящими в класс, однако
некоторые из них обязательно должны быть компонентами класса. Это
функции operator =, operator ->, operator() и преобразования
типов. Данная функция не является функцией-компонентом, но должна
являться таковой.
идентификатор must be a member function or have an argument
of class type
идентификатор должен быть функцией-компонентом или иметь аргумент
типа класса
Большинство функций-операций С++ должно иметь неявный или яв-
ный аргумент типа класса. Данная функция-операция была объявлена
вне класса и не имеет явного аргумента типа класса.
идентификатор must be a previously defined class or struct
идентификатор должен быть ранее объявленным классом или структурой
Вы пытаетесь объявить идентификатор как базовый класс, тогда
как он либо не является классом, либо не был еще полностью опреде-
лен. Исправьте имя или реорганизуйте объявления.
идентификатор must be a previoustly defined enumeration tag
идентификатор должен быть ранее определенным тегом перечислимого
типа
Данное объявление пытается обратиться к идентификатору, как к
тегу типа enum, тогда как он не был объявлен в качестве такового.
Исправьте имя или реорганизуйте объявления.
идентификатор must be a previoustly defined structure tag
идентификатор должен быть ранее определенным тегом структуры
Данное объявление пытается обратиться к идентификатору, как к
тегу структуры, тогда как он не был объявлен в качестве такового.
Исправьте имя или реорганизуйте объявления.
идентификатор specifies multiple or duplicate access
идентификатор задает многократный или дублирующийся доступ
Базовый класс должен быть объявлен как public или private, но
не то и другое одновременно. Спецификатор доступа не должен зада-
ваться для базового класса более одного раза.
компонент is not accessible
компонент недоступен
Вы пытаетесь сослаться на "компонент" класса С++, который
имеет атрибут доступа private или protected и недоступен из
данной функции. Это иногда случается при попытке вызова доступной
перегружаемой функции-компонента (или конструктора), когда
заданные при этом аргументы совпадают с аргументами недоступной
функции. Перед проверкой доступности всегда выполняется проверка
разрешения перегрузки. Если проблема именно в этом, то для выбора
желаемой доступной функции попытайтесь явно выполнить приведение
типа для одного или более параметров.
спецификатор has already been included
спецификатор уже был включен
Данный спецификатор типа встречается в объявлении более одно-
го раза. Удалите или измените один из них
= expected
= ожидается
Ожидалась операция присвоения для инициализации переменной.
, expected
, ожидается
В списке объявлений, инициализации или параметров ожидается
запятая.
{ epected
{ ожидается
В качестве начала блока или инициализации ожидается левая фи-
гурная скобка.
( expected
( ожидается
Перед списком параметров ожидается левая круглая скобка.
} expected
} ожидается
В конце блока или инициализации ожидается правая фигурная
скобка.
) expected
) ожидается
В конце списка параметров ожидается правая круглая скобка.
: expected alter private/protected/publlc
: ожидается после private/protected/public
При использовании резервированных слов
private/protected/public для того, чтобы начать соответствующий
раздел класса С++, за ними должно следовать двоеточие.
::requires a preceding identifier in this context
в данном контексте перед :: требуется идентификатор
В С++ в объявлении встретилось двойное двоеточие без предшес-
твующего квалифицирующего имени класса. Неквалифицированное двой-
ное двоеточие может использоваться только в выражениях для обозна-
чения глобального контекста, а не в объявлениях.
.* operands do not match
операнды .* не соответствуют
Вы не объявили операнд правой части операции С++ (.*) как
указатель на компонент класса, заданный левым операндом.
# operator not followed by macro argument name
за операцией # не следует имя аргумента макроса
В макро определении символ # может быть использован для обоз-
начения преобразования аргумента макроса в строку. За # должно
следовать имя аргумента макроса.
Access can only be changed to public or procted
Тип доступа может быть изменен только на public или protected
Производный класс С++ может модифицировать права доступа
члена базового класса, но только на public или protected.
Компонент базового класса нельзя сделать private.
Access declarations cannot grant or reduce access
Объявления доступа не могут повысить или понизить права доступа
Производный класс С++ может модифицировать права доступа чле-
на базового класса, но только путем восстановления прав базового
класса. Он не может повысить или понизить права доступа.
Access specifier спецификатор found in a union
Спецификатор доступа встретился в объединении
Спецификаторы доступа С++ (public, private или protected) не
могут находиться в объединениях.
Ambiquity between функция1 and функция2
Неоднозначность между функцией1 и функцией2
С переданными параметрами могут использоваться обе названные
перегруженные функции. Такая неоднозначность недопустима.
Ambiquous conversion functions: тип1 and тип2
Неоднозначность функций преобразования: тип1 и тип2
Компилятор нашел более одного способа преобразования данного
типа в желаемый. Такая неоднозначность недопустима.
Array bounds missing ]
В задании границ массива отсутствует ]
В исходном файле в объявлении границ массива отсутствует зак-
рывающая квадратная скобка.
Array must have at least one element
Массив должен иметь хотя бы один элемент
ANSI C и С++ требуют, чтобы массив определялся хотя бы с од-
ним элементом (объекты нулевого размера недопустимы). Существует
старый программистский прием, состоящий в том, чтобы объявить эле-
мент структуры типа массива нулевого размера, а уже затем при помощи
malloc распределить фактически требуемую память. Этот прием
по-прежнему можно использовать, но теперь вы обязаны объявлять
массив как имеющий (как минимум) один элемент. Разумеется, объяв-
ления (в противоположность определениям) массивов неизвестного
размера допустимы.
Например,
char ray[] /* определение массива неизвестного размера
недопустимо */
char ray[0] /* определение массива нулевого размера
недопустимо */
Array of references is not allowed
Массив ссылок недопустим
Массив ссылок недопустим, поскольку указатели на ссылки недо-
пустимы, а имена массивов встраиваются в указатели.
Array size too lange
Размер массива слишком велик
Объявленный массив превышает 64К.
Assembler statement too long
Слишком длинный ассемблерный оператор
Операторы встроенного ассемблирования не могут превышать по
длине 480 байтов.
Attempting to return a reference to local name идентификатор
Попытка вернуть ссылку на локальное имя идентификатор
Данная функция С++ возвращает значение типа ссылки, и вы пы-
таетесь вернуть ссылку на локальную (динамическую) переменную. Это
недопустимо, поскольку такая переменная при выходе из функции раз-
рушается. Вы можете вернуть ссылку на любую статическую или гло-
бальную переменную, либо изменить функцию таким образом, чтобы
возвращалась не ссылка, а само значение.
Bad file name format in inciude directive
Неправильный формат имени файла в директиве включения
Имена включаемых файлов должны заключаться в кавычки
("имя_файла.h") или в угловые скобки (<имя_файла.h>). Перед именем
файла отсутствовала открывающая кавычка или угловая скобка. Если
использовался макрос, то результирующий текст расширения неверен;
т.е., он не взят в кавычки.
Bad ifdef directive synfax
Неверный синтаксис директивы ifdef
Директива ifdef должна содержать в качестве тела директивы
единственный идентификатор (и ничего более).
Bad ifndef directive synfax
Неверный синтаксис директивы ifndef
Директива ifndef должна содержать в качестве тела директивы
единственный идентификатор (и ничего более).
Bad return type for a type conversion operator
Неверный тип возврата в операции преобразования типа
Данная функция-компонент преобразования типа С++ задает тип
возврата, отличный от типа самой функции. Объявление функции
преобразования operator T может не задавать типа возврата вообще.
Bad syntax for pure function definition
Неверный синтаксис определения "чистой" функции
Чистые виртуальные функции задаются добавлением в определение
символов "=0". Вы написали что-либо похожее, но не совпадающее с
требуемым в точности.
Bad undef directive syntax
Неверный синтаксис директивы undef
Директива #undef должна содержать в качестве тела директивы
единственный идентификатор (и ничего более).
Base class класс is included more than once
Базовый класс включен более одного раза
Класс С++ может быть производным от любого числа базовых
классов, но непосредственно от одного и того же класса он может
быть производным только один раз.
Base class класс is initialized more than once
Базовый класс инициализируется более одного раза
В конструкторе класса С++ список инициализации, следующий за
заголовком конструктора, включает указанный базовый класс более
одного раза.
Base class cannot be declared protected
Базовый класс не может быть объявлен как protected
Базовый класс С++ может быть public или private, но не
protected.
Bit field cannot be static
Битовое поле не может быть static
Только обычные данные-компоненты классов С++ могут быть
объявлены как static, но не битовые поля.
Bit fields must be signed or unsigned int
Битовые поля должны быть signed или unsigned int
Битовое поле должно быть объявлено с интегральным типом
signed или unsigned. В ANSI C битовые поля могут быть только
signed или unsigned int (но не char или long).
Bit fields must contain at least one bit
Битовые поля должны содержать как минимум один бит
Вы не можете объявить именованное битовое поле длиной 0 (или
менее 0) битов. Можно объявить битовое поле нулевой длины без име-
ни, по соглашению используемое для принудительной установки вырав-
нивания битового поля по границе байта (или по границе слова, если
выбрана опция выравнивания -a).
Bit field too large
Битовое поле слишком велико
Данная ошибка возникает при попытке определения битового поля
длиной свыше 16 битов.
Body already defined for this function
Тело этой функции уже определено
Тело функции с этим же именем и типом уже встречалось выше.
Тело функции может входить в программу только один раз.
Call of non-function
Вызов не-функции
Вызываемое имя не было объявлено как функция. Эта ошибка
обычно возникает при неправильном объявлении или опечатке в имени
функции.
Cannot assign идентификатор1 to идентификатор2
Присвоение идентификатора1 идентификатору2 невозможно
Обе стороны операции присвоения (=) (или составной операции
присвоения типа +=) должны быть совместимыми и не должны являться
массивами. Правая сторона данной операции присвоения имеет тип
идентификатор1 и не может быть присвоена объекту слева, имеющему
тип идентификатор2.
Cannot call 'main' from within the program
Вызвать 'main' из программы невозможно
С++ не позволяет рекурсивные вызовы 'main'.
Cannot cast from идентификатор1 to идентификатор2
Приведение между идентификатор1 и идентификатор2 невозможно
Приведение типа идентификатор1 к типу идентификатор2 здесь
запрещено. В С указатель может быть приведен к интегральному типу
или к другому типу указателя. Интегральный тип может быть приведен
к любому интегральному типу, типу с плавающей точкой и указателю.
Тип с плавающей точкой может быть приведен к интегральному или
другому типу с плавающей точкой. Структуры и массивы не позволяют
выполнение для них приведений типа. Также невозможны приведения для
типа void.
В С++ проверяется наличие преобразований и конструкторов,
определяемых пользователем, и в случае их отсутствия применяются
правила приоритета (за исключением указателей на компоненты
класса). Из интегральных типов только для константы со значением
ноль допускается приведение к типу указателя компонента. Указатель
компонента допускает приведение к интегральному типу или
аналогичному указателю компонента. Последний будет указывать на
компонент данных, если на него был установлен исходный указатель,
и на компонент-функцию, если на нее был установлен исходный;
квалифицирующий класс типа, к которому выполняется приведение,
должен быть тем же, что и базовый класс оригинала.
Cannot create a varlable for abstract class класс
Создание переменной для абстрактного класса "класс" невозможно
Абстрактные классы - с чистыми виртуальными функциями - не
могут использоваться непосредственно, допускается лишь создание
производных от них классов.
Cannot define a pointer or reference to a reference
Определение указателя или ссылки на ссылку невозможно
Иметь указатель на ссылку или ссылку на ссылку нельзя.
Cannot find класс::класс (класс&) to copy a vector
Не найден класс::класс (класс&) для копирования вектора
Если класс С++ класс1 содержит вектор (массив) класса класс2
и вы хотите сконструировать объект типа класс1 из другого объекта
типа класс1, то должен быть конструктор класс2::класс2(класс2&)
такой, чтобы могли быть сконструированы элементы вектора. Данный
конструктор принимает только один параметр (являющийся ссылкой на
его класс) и называется конструктором ссылки.
Обычно компилятор создает конструктор ссылки автоматически.
Однако, если вы определили конструктор для класса класс2, имеющего
параметр типа класс2. и дополнительные параметры со значениями по
умолчанию, то данный конструктор ссылки не может существовать и не
может быть создан компилятором. (Вследствие того, что
класс2::класс2(класс2&) и класс2::класс2(класс2&, int = 1) не раз-
личаются компилятором). Вы обязаны переопределить данный конструк-
тор таким образом, чтобы не все параметры имели значения по умол-
чанию. Затем вы можете определить конструктор ссылки или позволить
компилятору создать собственный.
Cannot find идентификатор::идентификатор() to initialize a vector
Не найден идентификатор::идентификатор() для инициализации вектора
Если класс С++ класс1 содержит вектор (массив) класса класс2
и вы желаете сконструировать объект типа класс1, но при этом не из
другого объекта типа класс1, то для конструирования элементов век-
тора должен быть использован конструктор класс2::класс2(). Такой
конструктор без параметров называется конструктором по умолчанию.
Если вы не определили для класса2 никакого конструктора, компиля-
тор создаст конструктор по умолчанию автоматически; в противном
случае будет использован ваш конструктор.
Cannot find класс::класс() to initialize base class
Не найден класс::класс для инициализации базового класса
При конструировании производного класса С++ класс2 сначала
должен быть сконструирован каждый базовый класс класс1. Если конс-
труктор для класса2 не задает конструктор для класса1 (как часть
заголовка класса2), то для базового класса должен быть задан конс-
труктор класс1::класс1(). Такой конструктор без параметров называ-
ется конструктором по умолчанию. Если вы не определили для класса1
никакого конструктора, компилятор создаст конструктор по умолчанию
автоматически; в противном случае будет использован ваш конструк-
тор.
Cannot find класс::класс() to initialize field идентификатор
Не найден класс::класс() для инициализации поля идентификатор
Если класс С++ класс1 содержит компонент класса класс2 и вы
желаете сконструировать объект типа класс1, но при этом не из
другого объекта типа класс1, то для конструирования этого
компонента должен быть использован конструктор класс2::класс2().
Такой конструктор без параметров называется конструктором по
умолчанию. Если вы не определили для класса2 никакого
конструктора, компилятор создаст конструктор по умолчанию
автоматически; в противном случае будет использован ваш
конструктор.
Cannot find класс::operator=(класс&) to2 0copy a vector
Не найден класс::operator=(класс&) для копирования вектора
Если класс С++ класс1 содержит вектор (массив) класса класс2
и вы желаете скопировать класс типа класс1, то должна использо-
ваться операция присвоения класс2::operator=(класс2&), позволяющая
копирование элементов вектора. Обычно компилятор вставляет такую
операцию автоматически. Однако, если вы определили operator= для
класса2, но эта операция не принимает параметр класс2&, то компи-
лятор не будет генерировать операцию автоматически - вы должны на-
писать свою.
Cannot have a near membar in a far class
Компонент near в классе far недопустим
Все компоненты класса С++ far должны быть far. Данный
компонент должен принадлежать классу, объявленному (или имеющему
по умолчанию) атрибут near.
Cannot initialize a fieid
Инициализация поля невозможна
Отдельные поля структур, объединений и классов С++ могут не
иметь инициализаторов. Структура или объединение могут быть иници-
ализированы как единое целое при помощи инициализаторов в фигурных
скобках. Класс С++ может быть инициализирован только при помощи
конструктора.
Cannot initialize тип1 with тип2
Тип1 не может быть инициализирован типом2
Вы пытаетесь инициализировать объект типа тип1 значением типа
тип2, что недопустимо. Правила инициализации те же, что и для
присвоения.
Cannot modify a const object
Модификация объекта - константы невозможна
Недопустимая операция с объектом, объявленным константой,
например, попытка присвоения такому объекту.
Cannot overioad 'main'
Перегрузка 'main' невозможна
main - это единственная функция, которая не может быть перег-
ружена.
Cannot specify base classes except when defining the class
Задавать базовые классы допустимо только при определении класса
При задании класса С++ базовые классы, производным от которых
является данный класс, могут задаваться только в точке определения
класса. При объявлении тега класса, например class c; задавать ба-
зовые классы недопустимо.
Case outside of switch
Оператор case вне оператора switch
Компилятор встретил оператор case вне оператора switch. Это
часто случается при несоответствии числа правых и левых фигурных
скобок.
Case statement missing :
В операторе case отсутствует :
Оператор case должен содержать выражение типа константы, за
которым следует двоеточие. Либо в выражении оператора case отсутс-
твует двоеточие, либо перед двоеточием находится лишнее символи-
ческое имя.
Character constant too long
Слишком длинная символьная константа
Символьные константы могут иметь длину только в один или два
символа.
Class класс has a constructor and cannot be hidden
Класс имеет конструктор и не может быть скрытым
С имеет разные пространства имен для тегов структур и обычных
имен, также как и С++. Это распространяется и на классы с конст-
рукторами, поскольку объявления конструкторов похожи на объявления
функций, и возможность их перепутать может привести к аварийному
завершению.
Classes cannot be initialized with {}
Классы не могут быть инициализированы при помощи {}
Обычные структуры С могут инициализироваться набором значений
в фигурных скобках. Классы С++ могут быть инициализированы только
конструкторами, если класс имеет конструкторы, компоненты private,
функции или базовые классы, являющиеся виртуальными.
Class member компонент declared outside its class
Компонент класса объявлен вне своего класса
Функции компоненты класса С++ могут быть объявлены только
внутри объявления класса. В отличие от функций, не являющихся
компонентами класса, они не могут иметь несколько объявлений или
быть объявлены в других местах.
Compound statement missing }
В составном операторе отсутствует }
Компилятор дошел до конца исходного файла, но не обнаружил
закрывающей фигурной скобки. Это обычно бывает при несовпадающем
количестве правых и левых скобок.
Next
Используются технологии
uCoz