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