1       Вступление
1.1     История создания и развития.
1.2     Что такое C--?
1.3     Как установить C--.

2.      Управление компиляцией.
2.1     Параметры командной строки компилятора C--.
2.1.1   /ON - Оптимизация числовых выражений.
2.1.2   /DE - Временное расширение разрядности переменной.
2.1.3   /ARGC - Альтернативный обработчик командной строки.
2.1.4   /OST - слияние одинаковых строковых констант.
2.1.5   /D - установка идентификатора в TRUE из командной строки.
2.1.6   /IA - упрощенный ввод ассемблерных инструкций.
2.1.7   /CRI - пропуск повторно включаемого файла.
2.1.8   /IND - импорт имен функций из DLL
2.1.9   /WS - задать имя stub файла для программ под windows.
2.1.10  /WBSS - разместить не инициализированные данные в отдельной секции.
2.1.11  /DBG - создание отладочной информации.
2.1.12  /J0 /J1 /J2
2.1.13  /LST - Создание ассемблерного листинга.
2.1.14  /ENV - Сохранение адреса переменных окружения.
2.1.15  /CPA - Очистка post-области данных.
2.1.16  /W - вывод предупреждений.
2.1.17  /NW - Выборочное отключение типов предупреждений.
2.1.18  /WSI - короткая таблица импорта.
2.2     Директивы транслятора.
2.2.1   ?ifdef/?ifndef
2.2.2   ?initallvar
2.2.3   ?usestartup
2.2.4   ?startusevar
2.2.5   ?atexit
2.2.6   ?startuptomain
2.2.7   ?undef
2.2.8   ?align и ?aligncode
2.2.9   ?pragma

3.      Константы.
3.1     Числовые константы.
3.2     Символьные константы.
3.3     Строковые константы.
3.3.1   Суффиксы строковых констант.
3.4     Постоянные выражения.

4.      Выражения.
4.1     Типы выражений.
4.2     Выражения типа EAX/AX/AL.
4.3     Выражения использующие получатель при вычислении выражения.
4.4     Не - EAX/AX/AL выражения.
4.5     Условные выражения.
4.5.1   Простые условные выражения.
4.5.2   Сложные условные выражения.
4.6     Изменение типа выражения при присваивании.
4.7     Вычисление в регистры EAX/AX/AL со знаком.

5.      Идентификаторы.
5.1     Формат идентификатора.
5.2     Зарезервированные идентификаторы.
5.3     Универсальные регистры для 16 и 32-битного режима.
5.4     Предопределенные идентификаторы.

6.      Переменные.
6.1     Типы переменных.
6.2     Объявление переменных.
6.3     Глобальные переменные.
6.4     Локальные переменные.
6.5     Динамические переменные и структуры.
6.6     Присваивание одного значения нескольким переменным.
6.7     Переменные типа float.
6.7.1   Формат переменных типа float.
6.7.2   Константы с плавающей точкой.
6.7.3   Диапазон допустимых значений.
6.7.4   Математические операции.
6.7.5   Преобразования типов.
6.7.6   Операции сравнения.
6.7.7   Сравнение переменных типа float с 32-битным регистром.
6.8     Указатели.

7.      Адресация.
7.1     Относительная адресация.
7.2     Абсолютная адресация.

8.      Работа с блоками данных.
8.1     Структуры.
8.1.1   Что такое структуры.
8.1.2   Синтаксис.
8.1.3   Инициализация структур при объявлении.
8.1.4   Инициализация структуры при выполнении программы.
8.1.5   Операции с элементами структур.
8.1.6   Вложенные структуры.
8.1.7   Отображение тега структуры на блок памяти.
8.1.8   Битовые поля структур.
8.2     Объединения.
8.3     Команды 'FROM' и 'EXTRACT'.

9.      Операторы.
9.1     Условные инструкции.
9.2     Циклы 'do{} while'.
9.3     Циклы 'loop', 'LOOPNZ', 'loopnz'.
9.4     Цикл 'while', 'WHILE'.
9.5     Цикл 'for', 'FOR'.
9.6     Оператор переключатель 'switch'.
9.7     Оператор перехода 'goto', 'GOTO'.
9.8     Оператор разрыва 'break', 'BREAK'.
9.9     Оператор продолжения 'continue', 'CONTINUE'.
9.10    Логическое объединение условий.
9.11    Переход через циклы.
9.12    Инвертирование флага проверки условий.
9.13    Вычисление выражения, а затем проверка условия.
9.14    Проверка битов при операции сравнения.
9.15    Оператор перестановки.
9.16    Оператор отрицания.
9.17    Оператор инверсии.
9.18    Специальные условные выражения.
9.19    Символ '$' - вставляет текущий адрес программы.
9.20    Ключевое слово 'static' и оператор '::'.
9.21    Оператор sizeof.
9.22    Метки перехода.

10.     Ассемблер.
10.1    Поддержка команд ассемблера.
10.2    Ключевое слово 'asm'.
10.3    Префикс 'dup' - повторение инструкций DB/DW/DD.
10.4    Инструкции процессора Pentium III.

11.     Функции.
11.1    Типы функций и макрокоманд.
11.2    Стековые функции.
11.3    Регистровые функции.
11.4    Динамические функции.
11.4.1  Установка динамической функции в определенное место программы.
11.5    inline-функции.
11.5.1  Другое применение inline.
11.6    Функции обработки прерываний.
11.7    Замена 'return' на 'goto'.
11.8    Возвращаемые значения.
11.9    Объявление параметров в регистровых функциях.
11.10   Объявление параметров в стековых функциях.
11.11   Использование макрокоманд.
11.12   Передача параметров в стековые функции через регистры.
11.13   Вызов функций с адресом в регистре и переменной.
11.14   Встоенные в компилятор функции.
11.14.1 Функции 'ABORT', 'ATEXIT' и 'EXIT'.
11.14.2 Функции inp/inportb, inport, inportd, outp/outportb, outport и
        outportd.
11.14.3 Функции для работы с вещественными числами.
11.15   Классы.
11.15.1 Объявление функций в структурах.
11.15.2 Наследование.
11.15.3 Наследование функций.

12.     Типы выходных файлов.
12.1    Выходные файлы типа COM.
12.2    Выходные файлы типа EXE.
12.3    Выходной файл *.EXE с моделью памяти tiny.
12.4    Объектный выходной файл OBJ.
12.5    COM файл symbiosis.
12.5.1  СИМБИОЗ - что это такое?
12.5.2  Как это делать.
12.5.3  Использование.
12.5.4  Злоупотребления.
12.6    SYS - драйверы устройств.
12.7    Компиляция кода расширителей ROM-BIOS.
12.8    32-битные файлы.
12.8.1  32-битный код под DOS.
12.8.2  32-битный код под Windows.
12.8.3  Вызов API функций по ординалам.
12.8.4  Создание DLL под Windows.
12.8.5  Инициализация DLL при загрузке.
12.8.6  Компиляция ресурсов.
12.9    Выходные файлы для MeOS.
12.10   32-битные бинарные файлы.

13.     Приложения.
13.1    Поиск включаемых файлов.
13.2    Регистры, которые должны быть сохранены.
13.3    C--.ini файл.
13.4    startup.h-- файл.
13.5    mainlib.ldp файл.
13.6    C-- символы.


1. Вступление.
===============

  1.1 История создания и развития.
  ---------------------------------

      Автором языка SPHINX C-- является Peter Cellik (CANADA). Последняя
  авторская версия SPHINX C-- v0.203 от 28.Oct.96. К сожалению автор
  отказался от дальнейшего развития языка. С 1998 года, уже почти умерший
  проект, подхватил Михаил Шекер (Россия). Изначально компилятор был freeware
  (и даже greenware, как его называл Peter Cellik). Таким статус компилятора
  остался и поныне.

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

      При развитии компилятора, было стремление придерживаться следующих
  принципов:

      1. Максимально возможная совместимость синтаксиса с последней версией
  компилятора написанного Peter Cellik. Это давало возможность с минимальными
  затратами (а чаще всего без всяких затрат) адаптировать программы,
  написанные для 0.203 версии компилятора, к последней на этот момент версии
  компилятора.

      2. Сблизить синтаксис компилятора со стандартным языком C. Это могло
  значительно облегчить перенос программ написанных на C.

      3. Также прилагались усилия, для того, чтобы человек знающий только
  ассемблер мог бы с минимальными затратами освоить C--.

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

      Если у Вас есть предложения и идеи по улучшению компилятора - пишите.
  Мой e-mail sheker@mail.ru . Я с удовольствием выслушаю Ваши предложения, но
  не гарантирую, что все они будут реализованы. Если реализовывать все
  поступающие предложения, то компилятор превратится в свалку. Но если Ваше
  предложение будет ценным (на мой взгляд, так что Вам придется свое
  предложение хорошо аргументировать) и его будет возможным реализовать, оно
  без сомнения найдет место в компиляторе.


  1.2 Что такое C--?
  -------------------

      C-- был разработан, для того чтобы строить маленькие и быстрые
  программы. Это наиболее подходит для создания резидентных программ (TSR),
  программ, требующих обработку прерываний или программ у которых ограничены
  ресурсы.

      C-- занимает промежуточное положение между си и ассемблером. В связи с
  этим промежуточным положением, Вам, для того чтобы писать программы на C--,
  необходимо знать и ассемблер и си. Если Вам надоело возиться с огромными
  ассемблерными листингами, а излишняя строгость языка C Вас угнетает, то этот
  язык для ВАС.

      Сейчас компилятор C-- может создавать 32-битные программы под Windows
  (EXE-файлы формата PE) и 32-битные программы под DOS (LE-формат). Имеет
  встроенный компилятор ресурсов и дизассемблер для генерации листинга
  откомпилированного файла. Поддерживает ассемблерные инструкции процессора
  Pentium III и ассемблерные инструкции FPU. Компилятор может генерировать
  отладочную информацию совместимую с отладчиками фирмы Borland. Компилятор
  может создавать объектные файлы (obj), но только для DOS программ.

      C-- разработан только для использования на компьютерах с процессорами
  совместимыми с семейством 80x86. Компилятор может работать только с
  операционными системами DOS и семейством Windows.


  1.3 Как установить C--.
  ------------------------

      Компилятору C-- для работы нужны совсем незначительные ресурсы:
  процессор 386 или лучше, чуть более 1 Мб дискового пространства и 4Мб
  оперативной памяти. Компилятор может быть установлен на компьютеры с
  операционной системой Windows 95 или лучше. Компилятор также может работать
  в среде чистого DOS. В основном пакете компилятора находится 32-битная DOS
  версия компилятора. На сайте http://sheker.chat.ru или
  http://c--sphinx.narod.ru можно найти и консольную версию компилятора.
  Консольная версия компилятора может работать только в среде Windows, но
  она, в отличие от DOS версии, может работать с длинными именами исходных
  файлов.

      Установить компилятор C-- на Ваш компьютер очень просто. Предположим,
  что Вы решили установить C-- на диск C. Создайте на диске C директорию
  (папку) с именем 'C--' или с другим, удобным и понятным для Вас именем
  (например, ДОСовской командой: MD C-- или другим доступным Вам способом).
  Затем с сайта http://sheker.chat.ru или http://c--sphinx.narod.ru скачайте
  файлы full_c--.zip и ful_c--2.zip и разархивируйте их в этой директории.
  Затем в файле autoexec.bat можно прописать путь к директории с
  компилятором. И все. Компилятор готов к работе. Если Вы добавляли путь к
  компилятору в файл autoexec.bat, то Вам придется перегрузить операционную
  систему.

      Переменная окружения для компилятора C-- задается либо из командной
  строки либо из командного файла (лучше всего ее прописать в autoexec.bat).
  Эта переменная должна указывать компилятору, где находятся его библиотечные
  файлы. Пример:

    set C--=c:\c--\lib

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


2. Управление компиляцией.
===========================

  2.1 Параметры командной строки компилятора C--.
  ------------------------------------------------

      Формат командной строки вызова компилятора C--:

  C-- [Параметры] [ИМЯ INI ФАЙЛА] [ИМЯ ИСХОДНОГО ФАЙЛА]

      Имя исходного файла можно задавать без расширения. Компилятор ищет
  файл с расширением c--, cmm, c.

      Параметры выделяются предшествующим символом '/' или '-'.
  Инвертировать функцию опции можно завершающим символом '-'.

  Список поддерживаемых параметров:

  /0          использовать только команды 8086/8088 процессора (установлено
              по умолчанию при компиляции 16-битных программ).
  /1          использовать команды 80186 процессора.
  /2          использовать команды и оптимизацию для 80286 процессора.
  /3          использовать команды и оптимизацию для 80386 процессора.
              (установлено по умолчанию для 32-битных программ).
  /4          использовать команды и оптимизацию для 80486 процессора.
  /5          использовать команды и оптимизацию для Pentium процессора.
  /6          использовать команды и оптимизацию для Pentium MMX процессора.
  /7          использовать команды и оптимизацию для Pentium Pro процессора.
  /8          использовать команды и оптимизацию для Pentium II процессора.
  /9          использовать команды и оптимизацию для Pentium III процессора
              (пока не реализовано из-за отсутствии информации).
  /A          выравнивание данных на четный адрес
              по умолчанию разрешено, поддерживает инверсию
  /AC         выравнивание адреса начала циклов
              по умолчанию отключено, поддерживает инверсию
              имеет смысл только на процессорах Pentium+
  /AL=##      установить значение байта заполнения при выравнивании данных
              по умолчанию 0.
  /AP         выравнивание адреса начала функций.
              по умолчанию отключено, поддерживает инверсию
              имеет смысл только на процессорах Pentium и лучше
  /ARGC       вставить блок разбора командной строки
              по умолчанию отключено, поддерживает инверсию
  /AS         выравнивание в структурах.
              по умолчанию отключено, поддерживает инверсию
  /AT         вставить блок поддержки фунции ATEXIT
              по умолчанию отключено, поддерживает инверсию
  /C          вставить блок игнорирования CTRL<C>
              по умолчанию отключен, поддерживает инверсию
              имеет смысл только под DOS программы
  /CRI        проверять включаемые файлы на повторную загрузку
              по умолчанию включено, поддерживает инверсию
  /CPA        очистка post-области данных
  /D32        создать EXE файл (32 битный код под DOS)
              по умолчанию COM
  /D=<idname> определить идентификатор для условной компиляции
              по умолчанию нет
  /DBG        генерировать отладочную информацию
              по умолчанию нет
  /DE         временное расширение разрядности после умножения
              по умолчанию отключено, поддерживает инверсию
  /DLL        создать DLL для Windows32
              по умолчанию COM
  /ENV        сохранение адреса переменных окружения
  /EXE        создать EXE файл для DOS (модель SMALL)
              по умолчанию COM
  /HELP /H /? справка, эта информация
  /IA         имена ассемблерных инструкций являются идентификаторами
              по умолчанию отключено, поддерживает инверсию
  /IND=<name> импорт имен из файла name.
  /IP=<path>  задать путь поиска включаемых файлов
              по умолчанию нет
  /IV         инициализировать все переменные
              по умолчанию отключено, поддерживает инверсию
  /J0         не делать начальный jump на main()
              по умолчанию отключено, поддерживает инверсию
              В COM-файлах не создает jmp на main. В остальных не создается
              блок начальной инициализации программы, а управление
              передается сразу на main.
  /J1         делать короткий jump на main()
              по умолчанию нет
              имеет смысл только в COM-файлах
  /J2         делать jump на main()
              по умолчанию да, поддерживает инверсию
              имеет смысл только в COM-файлах
  /LAI        список поддерживаемых ассемблерных инструкций
  /LRS        загружать числовые константы через стек.
              по умолчанию да, поддерживает инверсию
  /LST        создать ассемблерный листинг
  /ME         показать мой адрес и имя
  /MEOS       создать исполняемый файл для MeOS
              по умолчанию COM
  /MER=##     установить максимальное число ошибок
              по умолчанию 16
  /MIF=<file> определить имя главного компилируемого файла
  /NS         запретить подключать stub файлов
              по умолчанию нет, поддерживает инверсию
  /NW=##      выборочное отключение предупреждений
  /OBJ        создать OBJ файл
              только 16 битный код.
              по умолчанию COM
  /OC         оптимизировать по размеру кода
              по умолчанию нет, поддерживает инверсию
  /ON         оптимизация чисел
              по умолчанию нет, поддерживает инверсию
  /OS         оптимизация по скорости выполнения
              по умолчанию да, поддерживает инверсию
  /OST        оптимизация строковых идентификаторов
              по умолчанию отключено, поддерживает инверсию
  /P          вставить блок разборки командной строки
              по умолчанию нет, поддерживает инверсию
  /R          вставить блок уменьшающий размер доступной памяти.
              по умолчанию да, поддерживает инверсию
              имеет смысл только в DOS-файлах
  /S=#####    установить размер стека
              по умолчанию 2048
  /SA=####    начальное смещение адреса запуска программы
              имеет смысл только в COM-файлах, по умолчанию 0x100
  /SOBJ       создать ведомый OBJ файл
              по умолчанию COM
  /STM        перенести блок startup кода в функцию main
              по умолчанию нет, поддерживает инверсию
              имеет смысл только в COM-файлах
  /SUV=####   начальный адрес не инициализированных переменных, при
              использовании ими startup кода.
              имеет смысл только в COM-файлах, по умолчанию равен /SA
  /SYM        надстройка для COM файла
              по умолчанию COM
  /SYS        создать драйвер устройств (SYS)
              по умолчанию COM
  /TEXE       создать EXE файл для DOS (модель TINY)
              по умолчанию COM
  /UL         использовать lea при оптимизации сложения регистров.
              по умолчанию да, поддерживает инверсию
  /UST        использовать startup код для переменных.
              имеет смысл только в COM-файлах
              по умолчанию нет, поддерживает инверсию
  /W          разрешить предупреждения
              по умолчанию нет, поддерживает инверсию
  /W32        создать EXE файл для Windows32 GUI
              по умолчанию COM
  /W32C       создать EXE файл для Windows32 console
              по умолчанию COM
  /WBSS       помещать не инициализированные данные в отдельную секцию.
              по умолчанию для /w32 разрешено, для остальных запрещено.
              поддерживает инверсию
  /WF=<file>  перенаправить вывод предупреждений в файл.
              по умолчанию нет
  /WFA        использовать быстрые вызовы API функций
              по умолчанию да, поддерживает инверсию
              только под windows
  /WFU        создавать таблицу перемещений (для Windows32)
              по умолчанию нет, поддерживает инверсию
              только под windows
              для DLL устанавливается в да
  /WIB=#####  установить адрес image base
              по умолчанию 0x400000
  /WMB        создавать Windows-файл с единым блоком
              по умолчанию да, поддерживает инверсию
              только под windows
              для DLL устанавливается в нет
  /WORDS      выдать список зарезервированных идентификаторов
  /WS=name    указывает имя файла используемого в качестве stub под windows.
  /X          запретить вставлять в код SPHINXC-- сигнатуру
              по умолчанию разрешено, поддерживает инверсию
              отключается если есть J0

      Примечание: выражение "поддерживает инверсию" означает, что для данной
  опции можно использовать и противоположное значение с помощью символа '-'
  после опции. Пример:

  /WFA-

       Параметры командной строки можно писать как большими, так и
  маленькими буквами.

    2.1.1 /ON - Оптимизация числовых выражений.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        При включении в командную строку опции /ON или в файл C--.INI строчки
    ON, компилятор будет анализировать операции над числами и где это
    можно, сокращать число операций. Например:

     Строка до оптимизации  | После оптимизации
    -----------------------------------------------
      AX = var + 7 - 3;     | AX = var + 4;
      AX = var * 2 * 5;     | AX = var * 10;
      AX = var * 2 / 4;     | AX = var / 2;
      AX = var * 10 / 2;    | AX = var * 5;
      AX = var / 2 / 3;     | AX = var / 6;
      AX = var / 4 * 8;     | AX = var * 2;
      AX = var / 16 * 16;   | AX = var;

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

    var = var / 16 * 16;

    но после оптимизации будет

    var = var;

    т.е. выравнивание не будет  произведено. Этого можно избежать, если
    разбить это выражение на два:

    var = var / 16;
    var = var * 16;

    тогда оптимизация не будет произведена. Но для получения более
    компактного кода лучше будет записать так:

    AX = var;
    AX = AX / 16;
    AX = AX * 16;
    var = AX;


    2.1.2 /DE - Временное расширение разрядности переменной.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Как известно, после умножения может произойти переполнение, т.е
    разрядность результата может превысить разрядность исходных операндов и
    произойдет искажение результата. Частично решить эту проблему Вам поможет
    опция командной строки /DE или строка DE в файле C--.INI. После команды
    умножения компилятор будет просматривать остаток строки и если обнаружит,
    что расширение разрядности может быть востребовано (востребовать
    расширенную разрядность могут операции деления и вычисления остатка), то
    будут приняты меры по ее сохранению. Например:

      a = b*c+d/e; //здесь будет включена поддержка расширения разрядности
      a = b*c+d*e; //здесь поддержки расширения разрядности не будет.

        Однако применение этой опции может иметь и негативные последствия.
    Покажу это на примере:

    пусть имеется выражение

      a = b * c / d;

    если значения переменных b = 0xC000, c = 0x1000, d=0x10, после запуска
    такая программа зависнет с сообщением о том, что произошло переполнение
    при делении.


    2.1.3 /ARGC - Альтернативный обработчик командной строки.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Отличие этого обработчика командной строки от 'parsecommandline'
    заключается в том, что при вызове PARAMSTR(0); Вы получите адрес строки в
    которой указан путь и имя запущенной программы. Следующие вызовы этой
    функции с увеличивающимся параметром будут возвращать адреса слов
    командной строки. А вызов функции PARAMCOUNT вернет Вам число слов в
    командной строке плюс один.

        Альтернативный обработчик командной строки включается директивой
    '?argc TRUE' или из командной строки компилятора ключом '/argc' или
    строчкой 'argc' в файле C--.INI.


    2.1.4 /OST - слияние одинаковых строковых констант.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

      char var="test";  //эта строка не будет участвовать в процессе
                        //оптимизации.

      void proc(){
        WRITESTR("test");	// эта строка будет участвовать в оптимизации.
        AX="test";          // переменной AX будет присвоен адрес строки,
                            // которая была вставлена в код программы в
                            // предыдущей строке.
      }

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

        Включается этот режим оптимизации либо с командной строки '/ost', либо
    директивой '#pragma option ost', либо строкой в файле c--.ini - 'ost'.
    Отключить, включенный ранее, этот режим можно директивой '#pragma option
    ost-'.


    2.1.5 /D - установка идентификатора в TRUE из командной строки.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

        Идентификатор вводится с командной строки ключом /d=idname.


    2.1.6 /IA - упрощенный ввод ассемблерных инструкций.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Стало возможным использовать ассемблерные инструкции без префикса '$'
    и вне блока asm. Этот режим включается: с командной строки опцией '/ia';
    в файле конфигурации строкой 'ia' или директивой '#pragma option ia'.

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


    2.1.7 /CRI - пропуск повторно включаемого файла.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

        Но иногда (очень редко) возникает потребность сделать повторное
    включение файла. Для этого в компиляторе есть опция командной строки
    '/cri-', которая запрещает компилятору делать проверку на повторное
    включение. Соответственно, для c--.ini файла, это можно сделать строкой
    'cri-' или директивой в компилируемом файле - '#pragma option cri-'.


    2.1.8 /IND - импорт имен функций из DLL.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Если Вы хотите в своей программе использовать DLL, для которой нет
    заголовочного файла с описанием функций, то компилятор может
    импортировать имена из этой DLL. Для этого Вам надо указать имя этой
    библиотеки либо через опцию командной строки '/ind=name.dll', либо в
    файле INI строкой 'ind=name.dll', либо через директиву '#pragma option
    ind=name.dll'.

        К недостатком такого способа получения имен можно отнести то, что при
    компиляции программы библиотека, из которой импортируются имена,
    обязательно должна присутствовать в компьютере. Также, если имена в
    библиотеке написаны без суффикса '@number', компилятор не будет
    контролировать число параметров передаваемых функции. И, к сожалению,
    компилятор умеет импортировать имена из библиотек имеющих только формат
    PE-файла.


    2.1.9 /WS - задать имя stub файла для программ под windows.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

        Вы можете вместо стандартного stub использовать свой. Для этого Вам
    необходимо указать имя 16-битного EXE-файла либо через опцию командной
    строки '/ws=filename', либо строкой в INI-файле 'ws=filename', либо
    директивой '#pragma option ws=filename'.

        Таким образом, у Вас появилась возможность создавать программы,
    работающие и под DOS и под windows.


    2.1.10 /WBSS - разместить не инициализированные данные в отдельной секции.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Секция .bss создается автоматически при компиляции программ с ключом
    '/w32'. Если Вы хотите иметь эту секцию и при компиляции программ с
    ключами '/w32c' или '/dll' Вам необходимо добавить либо в командной
    строке опцию '/wbss', либо строку wbss в INI-файле, либо директиву
    '#pragma option wbss'.

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


    2.1.11 /DBG - создание отладочной информации.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Если при компиляции программы в командную строку добавить ключ '/dbg',
    или в файл конфигурации c--.ini добавить строку 'dbg', то компилятор после
    окончания компиляции создаст файл с отладочной информацией. Этот файл
    имеет имя главного модуля и имеет расширение *.tds.

        Отладочная информация создаваемая компилятором C-- совместима с
    отладочной информацией создаваемой компиляторами фирмы Borland. Но, пока,
    эта информация реализована еще не в полном объеме. Создаваемой сейчас
    отладочной информации достаточно для проведения простейшей отладки
    программы.

        Для 16-битных программ под DOS для отладки надо использовать Turbo
    Debugger из пакета Borland C v4.5 или лучше (файл td.exe).

        Для программ под Windows надо использовать 32-битный отладчик из этого
    же пакета (файл td32.exe).

        Для 32-битных программ, использующих расширитель DOS применять для
    отладки Turbo Debugger невозможно. Но, может быть я не знаю, как это
    делать. Если Вы знаете, как создавать 32-битные программы с
    DOS-расширителем компиляторами фирмы Borland с включением в них отладочной
    информации, то расскажите мне. А я попробую применить это для C--.


    2.1.12 /J0 /J1 /J2
    ~~~~~~~~~~~~~~~~~~~

        Синонимом ключей /J0 /J1 /J2 является директива #jumptomain с
    параметрами 'NONE', 'SHORT' и 'NEAR' соответственно.

        Директива #jumptomain выполняет немного различные функции в
    зависимости от типа выходного файла.

        Компиляция файла типа *.com и *.exe модель памяти tiny:

      #jumptomain NONE (-j0) - в этом случае по окончании кода начальной
    инициализации программы не генерируется jmp на функцию main. Эту
    директиву следует использовать в случае, если до функции main нет других
    не динамических функций и инициализированных переменных.

      #jumptomain SHORT (-j1) - в этом случае по окончании кода начальной
    инициализации генерируется короткий jmp на функцию main. Эту директиву
    следует использовать, если до функции main находится не более 128 байт
    кода и данных.

      #jumptomain NEAR (-j2) - это состояние устанавливается по умолчанию. При
    этом генерируется близкий jmp на функцию main.

        Компиляция файлов *.exe (ключи -exe -d32 -w32 -w32c):

      #jumptomain NONE (-j0) - в этом случае код начальной инициализации
    программы не генерируется и управление при запуске передается сразу на
    функцию main.

      Во всех остальных случаях генерируется код начальной инициализации и
    управление на функцию main передается инструкцией call.

        Компиляция файлов *.dll:

      #jumptomain NONE (-j0) - в этом случае код начальной инициализации
    программы не генерируется и управление при запуске передается сразу на
    функцию main.

      Во всех остальных случаях генерируется код заглушки и управление на
    функцию main не передается. Фактически функция main в этом случае не
    нужна.

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

    dword main ( dword hInstDLL, reason, reserv )
    {
      ...
    }


    2.1.13 /LST - Создание ассемблерного листинга.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        С помощью дополнительной опции командной строки -lst Вы можете
    получить вместе с исполнительным файлом и его ассемблерный листинг.
    Листинг будет помещен в файл одноименный с исполнительным файлом и
    имеющим расширение *.lst.

        Ассемблерный листинг создается независимой от компилятора частью кода
    с использованием информации накапливаемой при компиляции программы.


    2.1.14 /ENV - Сохранение адреса переменных окружения.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Если при компиляции программы Вы в командную строку добавите опцию
    '-ENV' или в файл c--.ini строка 'ENV', то компилятор добавит в вашу
    программу переменную 'environ', в которой при загрузке будет сохранятся
    адрес переменных окружения запускаемой программы. Для программ под
    Windows это будет полный адрес, а для остальных в этой переменной будет
    сохраняться только адрес сегмента.


    2.1.15 /CPA - Очистка post-области данных.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

        Если Вам необходимо, чтобы неинициализированные переменные при
    загрузке программы всегда содержали одно и тоже значение (ноль) -
    включите в командную строку опцию -CPA.


    2.1.16 /W - вывод предупреждений.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

        По умолчанию предупреждения выводятся на экран. Но их бывает так
    много, что они могут не поместиться на экране. Поэтому в компиляторе есть
    опция, по которой все предупреждения выводятся в файл. Имя этого файла
    задается в той же опции. Поместив в свой c--.ini файл пару вот этих строк:

    w
    wf=warning

        Вы будете получать в файле 'warning' предупреждения.


    2.1.17 /NW - Выборочное отключение типов предупреждений.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        Сейчас компилятор может выдавать 12 типов предупреждений и, иногда их
    бывает так много, что становится трудно в них ориентироваться. Теперь
    можно выборочно запрещать выдачу предупреждений. Для этого в командной
    строке (или в файле C--.INI) можно установить опцию '/nw=number', где
    number - число от 1 до 12. Этим цифрам соответствуют следующие типы
    предупреждений:

      1 - Optimize numerical expressions
      2 - ... has been used by compiler
      3 - Short operator '...' may be used
      4 - String '...' repeated
      5 - Expansion variable
      6 - Signed value returned
      7 - '...' defined above, therefore skipped.
      8 - Variable/structure/function '...' possible not used
      9 - Non-initialized variable may have been used
      10 - Return flag was destroyed
      11 - Code may not be executable
      12 - Don't use local/parametric values in inline functions


    2.1.18 /WSI - короткая таблица импорта.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Таблица импорта обычно состоит в свою очередь из четырех таблиц. Две
    таблицы LookUp Table и Import Address Table абсолютно одинаковы.

        Опцией командной строки /WSI Вы можете заставить компилятор
    генерировать только одну из этих двух одинаковых таблиц (генерируется
    только Import Address Table). Тем самым у Вас получится более компактная
    таблица импорта, что приведет, в некоторых случаях, к созданию более
    компактного выходного файла.


  2.2 Директивы транслятора.
  ---------------------------

      C-- не содержит препроцессор. Тем не менее, есть несколько функций
  очень похожих на функции C препроцессора.

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

  ? align [val]                  Выровнять данные программы на чётный по
                                 умолчанию или на адрес кратный величине val.

  ? aligncode [val]              Выровнять код программы на чётный по
                                 умолчанию или на адрес кратный величине val.
			         Заполнение производится кодом 0x90.

  ? aligner (aligner value)      определить значение байта вставки.

  ? alignword (TRUE or FALSE)    разрешает или запрещает выравнивание на
                                 чётный адрес переменных типа word и int,
                                 значение по умолчанию TRUE.

  ? argc (TRUE or FALSE)         Включить или отключить альтернативный
                                 обработчик командной строки.

  ? atexit                       Вставляет в startup код поддержки функции
                                 ATEXIT().

  ? code32 (TRUE/FALSE)          разрешает/запрещает генерацию 32-битного
                                 кода.

  ? codesize                     оптимизация размера кода (в ущерб скорости).

  ? compilerversion min-vers     указывает, компилятор какой версии необходим
                                 для компиляции данной программы.

  ? ctrl_c (TRUE or FALSE )      разрешает или запрещает игнорирование
                                 нажатия CTRL-C.

  ? dataseg (value)              указывает компилятору сегментный адрес ОЗУ
                                 для переменных при компиляции ROM-BIOS.

  ? define (identifier) (token)  определяет идентификатор.

  ? DOSrequired (номер)          устанавливает минимальную требуемую версию
                                 DOS:  старший байт - номер версии,
                                 младший байт - номер модификации:
                                  0x0101 для версии 1.1 DOS
                                  0x0315 для версии 3.21 DOS
                                  0x0303 для версии 3.3 DOS
                                  0x0600 для версии 6.0 DOS
                                  0x0602 для версии 6.2 DOS и т.д.

  ? dosstring (TRUE/FALSE)       указывает компилятору, что в качестве
                                 терминатора строки надо использовать символ
                                 '$'

  ? else                         генерирует альтернативный код если ?ifdef или
                                 ?ifndef принимают значение FALSE (пример
                                 использования смотрите в файле FPU.H--)

  ? endif                        указывает на конец действия директив ifdef и
                                 ifndef

  ? fastcallapi (FALSE/TRUE)     запретить/разрешить генерацию быстрого вызова
                                 API-функций (по умолчанию разрешено).
                                 Директива работает при компиляции программ
                                 под Windows.

  ? fixuptable (TRUE/FALSE)      разрешить/запретить создание FixUp таблицы
                                 (по умолчанию запрещено). Директива работает
                                 при компиляции программ под Windows.

  ? ifdef (identifier)           если идентификатор определен, то возвращает
                                 TRUE иначе FALSE

  ? imagebase value              задает адрес Image Base. По умолчанию этот
                                 адрес  равен 0x400000. Директива работает при
                                 компиляции программ под Windows.

  ? ifndef (identifier)          если идентификатор определен, то возвращает
                                 FALSE иначе TRUE

  ? include ("filename")         включает другой файл.

  ? includepath ("path")         указание компилятору, в какой директории надо
                                 искать включаемые файлы

  ? initallvar                   инициализирует 0 все неинициализированные
                                 переменные.

  ? jumptomain (NONE, SHORT, NEAR or FALSE)
                                 устанавливает тип перехода к main(),
                                 значение по умолчанию - NEAR.

  ? maxerrors (number)           максимальное количество найденных ошибок,
                                 превысив которое транслятор прекращает
                                 работу, значение по умолчанию - 16.

  ? movedatarom  (TRUE/FALSE)    указывает компилятору о необходимости
                                 переноса данных из ПЗУ в ОЗУ.

  ? parsecommandline (TRUE or FALSE)
                                 включает в программу блок кода для
                                 синтаксического анализа командной строки
                                 значение по умолчанию FALSE.

  ? pragma                       может объявлять несколько других директив

  ? print (number or string)     выводит на экран строку или число.

  ? printhex (number)            выводит на экран число в шестнадцатеричном
                                 коде.

  ? randombyte                   вставляет в код программы байт случайного
                                 значения.

  ? resize (TRUE or FALSE)       включает функцию изменения после запуска
                                 размера выделенного программе блока памяти
                                 на минимально требуемый объём,
                                 значение по умолчанию TRUE.

  ? resizemessage (string)       сообщение, выводимое на экран перед
                                 аварийным прерыванием выполнения программы,
                                 если изменение размера выделенного программе
                                 блока памяти не выполнено.

  ? setdinproc                   по этой директиве компилятор немедленно
                                 вставляет в код компилируемой программы все
                                 вызывавшиеся ранее динамические функции.

  ? sizerom  (value)             указывает компилятору размер ПЗУ.

  ? speed                        оптимизация быстродействия (значение
                                 по умолчанию) в ущерб размеру кода.

  ? stack (number)               определяет размер стека программы в байтах.

  ? startaddress (number)        устанавливает стартовый адрес начала кода,
                                 значение по умолчанию 0x100.

  ? startuptomain                в com-файлах размещает startup-код в
                                 функции main().

  ? startusevar (number)         указывает адрес, с которого разрешено
                                 использовать ячейки памяти под
                                 неинициализированные переменные.

  ? sysattribute (значение)      эта директива передает компилятору атрибут
                                 создаваемого драйвера. По умолчанию
                                 устанавливается значение 0x2000.
                                 Действует только с ключом /SYS.

  ? sysname <текстовая строка>   эта директива передает компилятору имя
                                 будущего драйвера. По умолчанию
                                 присваивается имя "NO_NAME". Длина имени не
                                 более 8 символов.  Действует только с ключом
                                 /SYS.

  ? syscommand <command_0>,<command_1>, ...<command_n>; - эта директива
                                 является обязательной при создании
                                 драйверов. По этой директиве компилятору
                                 передается список имен функций обработки
                                 команд драйвера. Действует только с ключом
                                 /SYS.

  ? undef                        уничтожает константы объявленные директивой
                                 ? define

  ? use8086                      ограничивается при генерации объектного кода
                                 командами 8088/8086 (значение по умолчанию).

  ? use8088                      ограничивается при генерации объектного кода
                                 командами 8088/8086 (значение по умолчанию).

  ? use80186                     допускает при генерации объектного кода
                                 команды и оптимизацию для процессора 80186.

  ? use80286                     допускает при генерации объектного кода
                                 команды и оптимизацию для процессора 80286.

  ? use80386                     допускает при генерации объектного кода
                                 команды и оптимизацию для процессора 80386.

  ? use80486                     допускает при генерации объектного кода
                                 команды и оптимизацию для процессора 80486.

  ? usePentium                   допускает при генерации объектного кода
                                 команды и оптимизацию для процессора Pentium.

  ? useMMX                       допускает при генерации объектного кода
                                 команды и оптимизацию для процессора Pentium
                                 MMX.

  ? usestartup                   разрешает компилятору использовать ячейки
                                 памяти, занимаемые кодом начальной
                                 инициализации программы.

  ? warning (TRUE or FALSE)      эта директива разрешает или запрещает выдачу
                                 предупреждений. Директива действует только в
                                 пределах текущего файла и не влияет на
                                 включаемые файлы.

  ? winmonoblock FALSE           запрещает размещение таблиц файла формата PE
                                 в одну секцию.


    2.2.1 ?ifdef/?ifndef
    ~~~~~~~~~~~~~~~~~~~~~

        Ранее директива '?ifdef' срабатывала на наличие константы независимо
    от значения ее величины, а директива ?ifndef срабатывала на отсутствие
    константы в компилируемом файле. Теперь ?indef срабатывает лишь на
    константу отличную от FALSE, а ?ifndef срабатывает как на отсутствие
    константы в компилируемом файле, так и на константу имеющую значение
    FALSE.

        Для директив ?ifdef/?ifndef зарезервированы константы codesize и
    speed, которые принимают значение TRUE или FALSE в зависимости от режима
    оптимизации. Это будет полезным для создания более гибких библиотек.

        Есть возможность проверки типа CPU для которого ведется компиляция.
    Допустимые варианты синтаксиса:

    ?ifdef cpu > 1	 //если программа компилируется для CPU выше 80186
    ?ifndef cpu >= 2 // -------//------------- не больше или равно 80286
    ?ifdef cpu == 3  // -------//------------- равно 80386
    ?ifdef cpu != 0  // -------//------------- не равен 8086
    ?ifdef cpu < 3   // -------//------------- хуже 80386
    ?ifdef cpu <= 2  // -------//------------- хуже или равен 80286

        Эта директива позволит Вам писать одну функцию для различных типов
    CPU.


    2.2.2 ?initallvar
    ~~~~~~~~~~~~~~~~~~

        Директивой '?initallvar TRUE' включается режим при котором всем
    неинициализированным переменным будет присвоено нулевое значение и они
    будут располагаться в том месте, где были объявлены. Т.е. практически
    исчезнут неинициализированные переменные. Это может быть полезным при
    написании драйверов и резидентных программ.

        Параметр FALSE этой директивы отключает этот режим.
        По умолчанию эта директива установлена в FALSE.


    2.2.3 ?usestartup
    ~~~~~~~~~~~~~~~~~~

        Директива '?usestartup' разрешает компилятору использовать ячейки кода
    начальной инициализации программы (startup) для последующего размещения в
    них неинициализированных переменных. Это может быть полезным для получения
    более компактного кода, как обычных программ, так и в особенности
    резидентных.

        Эту директиву применяют только для генерации *.COM файлов.


    2.2.4 ?startusevar
    ~~~~~~~~~~~~~~~~~~~

        Директивой '?startusevar' можно указать начальный адрес с которого
    компилятор будет распределять память для неинициализированных переменных.
    Например, получив директиву '?startusevar 0x53' компилятор будет
    располагать неинициализированные переменные, начиная с адреса 0x53. Это
    может быть полезным для получения более компактного кода как для
    резидентных, так и для обычных программ.

        Эту директиву применяют только для генерации *.COM файлов.


    2.2.5 ?atexit
    ~~~~~~~~~~~~~~

        Директива '?atexit' добавляет в startup программы код поддержки
    функции ATEXIT, резервирует место для хранения 16 адресов функций и
    изменяет код функций ABORT и EXIT.

        Функция ATEXIT регистрирует функцию, адрес которой передается ей в
    качестве параметра, как функцию завершения программы. Эта функция
    будет вызвана в момент завершения программы функциями ABORT или EXIT
    или инструкцией RET из main.

        Всего можно зарегистрировать до 16 функций. Функции вызываются в
    порядке обратном порядку их регистрации.


    2.2.6 ?startuptomain
    ~~~~~~~~~~~~~~~~~~~~~

        По этой директиве компилятор в начале файла делает 'jmp' на начало
    функции main(). Перед началом компиляции этой функции компилятор начнет
    компиляцию startup кода и лишь затем будет продолжена компиляция функции
    main(). Тем самым startup код окажется не в начале файла, как это
    происходит обычно, а в теле функции main(). Это будет полезным при
    компиляции резидентных программ (TSR).

        Директива ?startuptomain работает только при компиляции com-файлов.


    2.2.7 ?undef
    ~~~~~~~~~~~~~

        Эта директива уничтожает константы объявленные директивой ?define. Её
    можно применять для изменения в процессе компиляции значения какой-нибудь
    константы.


    2.2.8 ?align и ?aligncode
    ~~~~~~~~~~~~~~~~~~~~~~~~~~

        В C-- существует директива '?align', которая делает однократное
    выравнивание данных на четный адрес. Но если к этой директиве добавить
    число, то выравнивание будет произведено на адрес кратный этому числу.
    Например директива '?align 4' дополнит сегмент данных до адреса кратного
    4. При выравнивании будут вставляться байты, значения которых определяются
    директивой '?aligner', по умолчанию это значение равно нулю. Директива
    '?align' производит выравнивание только в сегменте данных. В тех моделях
    памяти, в которых сегмент данных и кода совпадают эту директиву можно
    применять и для выравнивания начала функций.

        Директива '?aligncode [value]' делает выравнивание в сегменте кода на
    адрес кратный значению value, по умолчанию на четный адрес. Значение байта
    заполнения в этой директиве является число 0x90 - код инструкции NOP.
    Значение байта заполнения для этой директивы изменить нельзя. Т.о. эту
    директиву можно применять и внутри исполняемого кода. Например, если Вы
    хотите получить быстрый код на 486 процессоре, то рекомендуется делать
    выравнивание начала функций и циклов на адрес кратный 16.


    2.2.9 ?pragma
    ~~~~~~~~~~~~~~

        Директива #pragma это многофункциональнальная директива, которая в
    свою очередь имеет свои директивы:

      option
        Директива option позволяет включить в Ваш код опции командной строки
    компилятора. Некоторые опции не могут быть использованы в этой директиве;
    другие должны помещаться в самом начале исходного текста. Пример:

      #pragma option w32c

        Эта директива объявляет компилятору, что надо создать консольный
    32-битный файл под windows.

      startup
       Директивой startup можно указать функцию, которая будет выполнена перед
    запуском функции 'main'. Эта директива имеет такой формат:

      #pragma startup procname

        Количество раз, которое можно применять эту директиву в одной
    программе не ограничено, но реально можно использовать лишь несколько
    тысяч раз.

      line
        Директива line выводит на экран номер текущей строки и имя файла.
    Дополнительно может выводиться содержимое строки находящееся после слова
    'line'. Пример:

      #pragma line information

        Встретив эту директиву, компилятор выведет на экран номер строки и имя
    файла. Также будет выведено сообщение справа от слова 'line', если оно
    есть.

      resource
        Эта директива может принимать значения 'start' и 'end'. Эти два
    значения выделяют начало и конец блока ресурсов, если вы используете его
    непосредственно в исходном коде файла, а не в отдельном файле. Пример:

    #pragma resource start

    MyMenu MENU DISCARDABLE
    BEGIN    POPUP "Files",HELP
        BEGIN
            MENUITEM "Open",                        ID_OPEN
            MENUITEM "Save",                        ID_SAVE
            MENUITEM SEPARATOR
            MENUITEM "Exit",                        ID_EXIT
        END
        MENUITEM "Other",                           65535
    END

    #pragma resource end


3. Константы.
==============

  3.1 Числовые константы.
  ------------------------

      Представление числовых констант в виде десятичных чисел (чисел с
  основанием 10) и шестнадцатеричных чисел (основание счисления 16) полностью
  аналогично языку C.

      При двоичном представлении чисел (основание 2) число должно начинаться
  с символов '0b', за которыми без пробела идёт последовательность нулей и
  единиц.

      При восьмеричном представлении чисел (основание 8) число должно
  начинаться с символов '0o', за которыми без пробела идёт последовательность
  цифр.

      Вещественное число отличается от целого по наличию в нем точки.
  Начинаться вещественное число должно либо цифрой от 0 до 9, либо знаком
  минус. Необязательной частью вещественного числа является показатель
  степени. Показатель степени отделяется от числа символом 'e' или 'E'.
  Пробелы недопустимы.

  Примеры:
    0b11111111 // двоичное представление числа 255
    0x00F // шестнадцатеричное представление числа 15
    0o10 // восьмеричное представление числа 8
    1.234567E-20 // вещественное число

      C-- вместе с традиционным C-стилем шестнадцатеричных чисел понимает и
  числа записанные в стиле ассемблера. Для тех, кто вдруг не знает, сообщаю,
  что шестнадцатеричные числа в ассемблере имеют на конце символ 'h' или 'H'.
  Если первый символ шестнадцатеричного числа больше '9', то перед ним
  обязательно должен быть записан символ '0'. Примеры:

    1234h
    0A000H

      К числовым константам можно писать суффиксы 'L', 'U' и 'F'. Фактически
  эти суффиксы в C-- не играют никакой роли, компилятор их просто
  проглатывает. Пример:

  #define DEF  1L
  #define DEF2 2Lu
  #define DEF3 3.0F

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


  3.2 Символьные константы.
  --------------------------

      Одиночные символьные константы, как и в C, должны заключаться в
  одиночные кавычки (').

      Также как и в C, для обозначения специальных символов служит обратная
  наклонная черта вправо (\) с последующим за ней ключевым символом (или
  несколькими символами). Поддерживаются следующие специальные символы:

    '\a ' /* звуковой сигнал */
    '\b ' /* забой */
    '\f ' /* перевод  страницы */
    '\l ' /* перевод строки */
    '\n ' /* возврат каретки*/
    '\r ' /* возврат каретки*/
    '\t ' /* табуляция */
    '\x?? ' /* символ ASCII, соответствующий байтовому представлению,
               состоящему из двух шестнадцатеричных цифр, расположенных
               на месте знаков вопроса */
    ' \??? ' /* символ ASCII, соответствующий байтовому представлению,
               состоящему из трёх десятичных цифр, расположенных
               на месте знаков вопроса */

      Любой другой символ после обратной наклонной черты вправо будет принят
  как простой символ.

      Символ "Одиночная кавычка" (') может быть введён при помощи конструкции
  '\''

      Символ NULL может быть введён как ''

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

         'ab'
         'the'
         'this is large'

      Никакого ограничения на число символов в символьной константе не
  накладывается, но различаются только последние 4 символа. Это - максимум,
  который может быть сохранен в 32-разрядной переменной. Например, константы
  'this is large' и 'arge' - одинаковы.

      C-- обрабатывает все символьные константы как числовые значения ASCII
  символов. Для многобуквенных символьных констант первый символ
  соответствует старшим разрядам, таким образом, значение для 'ab' будет
  закодировано как 'a'*256+'b'.


  3.3 Строковые константы.
  -------------------------

      Строковые константы, как и в C, заключаются в двойные кавычки (").
  Специальные символы внутри строк обозначаются так же, как и в символьных
  константах. Все специальные символы имеют то же значение, что и в
  символьных константах за исключением \n, который имеет значение "новая
  строка" и заменяет собой пару символов "возврат каретки" и "перевод
  строки".

      В настоящее время наибольшая длина строковой константы - 2048 символов,
  включая символ-ограничитель 0, таким образом, максимум 2047 значащих
  символов.


    3.3.1 Суффиксы строковых констант.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

        суффикс|терминатор
       ------------------
         'z'   |  0
         '$'   |  $
         'n'   | нет

        Суффикс пишется сразу за символом двойной кавычки без пробелов.
    Пример:

    AX = "test"n; // эта строка не будет иметь терминатора
    BX = "test 2"$; // а у этой строки будет терминатором символ '$'

        Консольная версия компилятора поддерживает еще два суффикса, при
    наличии которых компилятор осуществляет перекодировку строковых констант.

     суффикс|перекодировка
    -----------------------
      'o'   |из OEM в Windows
      'w'   |из Windows в OEM


  3.4 Постоянные выражения.
  --------------------------

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

      Подобно всем выражениям в C--, постоянные выражения всегда вычисляются
  слева направо, невзирая на правила арифметики! Это совершенно отлично от
  других языков, и при написании выражений надо быть осторожным и помнить,
  что 2+3*2=10 а не 8.

      Некоторые примеры постоянных выражений:
  45 & 1 + 3 // равняется 4
  14 - 1 / 2 // равняется 6 (помните целочисленные значения)
  1 * 2 * 3 / 2 + 4 // равняется 7
      Примеры с применением вещественных чисел:
  3.23*1.53+2.0E2 // равняется 204.9419


4. Выражения.
==============

  4.1 Типы выражений.
  --------------------

      Имеются три типа выражений в C--, не считая постоянных выражений. Это
  выражения типа EAX/AX/AL, выражения типа неEAX/AX/AL и условные выражения.
  Все C-- выражения вычисляются слева направо, независимо от старшинства
  входящих в выражение математических операций.


  4.2 Выражения типа EAX/AX/AL.
  ------------------------------

      Этот тип выражений применяется в случае, когда его результат может быть
  сохранён в переменной в памяти или в регистре EAX или AX или AL.

      Если результат может быть сохранён в переменных типа byte или char,
  используется нотация AL.

      Если результат может быть сохранён в переменных типа word или int,
  используется нотация AX.

      Если результат может быть сохранён в переменных типа dword, long или
  float, используется нотация EAX.


  4.3 Выражения использующие получатель при вычислении выражения.
  ----------------------------------------------------------------

      Если в правой части выражения используется переменная являющаяся
  одновременно и приемником, то такие выражения дают различные результаты в
  зависимости от того является приемник регистром или переменной памяти. Это
  связано с тем, что при вычислении выражения в переменную памяти, вычисление
  производится сначала в регистр EAX/AX/AL, и лишь после окончания вычисления
  результат будет записан в приемник. Если же приемником является регистр, то
  его значение будет меняться после каждой операции вычисления. Пример:

  int var;
    var = BX = 2;
    var = 3 + var; // результатом будет 5
    BX = 3 + BX;   // результатом будет 6


  4.4 Не - EAX/AX/AL выражения.
  ------------------------------

      Этот тип выражений применяется в случае, когда его результат должен
  быть сохранён в любом другом регистре, отличном от аккумулятора EAX, AX
  или AL. В процессе вычисления выражения этого типа меняется только
  содержимое указанного регистра-получателя, все другие регистры будут
  сохранены. Если регистром-получателем служит байтовый регистр, а при
  вычислении используются величины размером в слово, одновременно с записью в
  младший байт может быть разрушено содержимое старшего байта
  регистра-получателя.

      Это обстоятельство накладывает некоторые ограничения на операции и
  операнды, допустимые в выражениях типа не EAX/AX/AL. Внутри выражений
  байтового типа не допускается:

      - делать вызовы МАКРОКОМАНД,
      - делать вызовы РЕГИСТРОВЫХ функций
      - делать вызовы СТЕКОВЫХ функций

      Ранее в не-EAX/AX/AL выражениях было можно использовать лишь
  операции: сложения, вычитания, XOR, OR, AND. Теперь для 16 и 32 битных
  регистров почти все ограничения сняты. Но есть еще ограничения на регистры.
  Например, если в выражении используется сдвиг на значение переменной, а
  приемником являются регистры CX/ECX, то такое выражение компилятор не будет
  компилировать:

     CX = var * SI * 3 * var >> 3;  //вызовет сообщение об ошибке

      Примечание:  для 8 битных не-AL выражений умножать можно только на
  числа: 0, 1, 2, 4, 8, 16, 32, 64 и 128. Все эти ограничения связаны со
  стремлением не разрушать другие регистры при использовании не-EAX/AX/AL
  выражений.


  4.5 Условные выражения.
  ------------------------

      Условные выражения - выражения, результатом вычисления которых является
  логическое значение да или нет, используемое в операторе if и циклах do {}
  while, while, for.

      Имеются два типа условных выражений, простые и сложные.

      Возможно логическое объединение условий.


    4.5.1 Простые условные выражения.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Простые условные выражения - одиночная лексема или выражение, которое
    примет значение да, если расчетное значение отлично от нуля, или значение
    нет, если расчетное значение равно нулю.


    4.5.2 Сложные условные выражения.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Сложные условные выражения имеют следующую форму:

         (левая_часть оператор_отношения правая_часть)

    Где:
       левая_часть - любое выражение типа AL/AX/EAX или постоянное выражение.
                     Тип выражения определяется по типу первой лексемы
                     (регистра или переменной); значение типа по умолчанию -
                     word для 16-битных программ и dword для 32-битных. Если
                     желателен другой тип, перед выражением ставится
                     соответствующее ключевое слово, определяющее его тип:
                     byte, char, int, long, dword или float

       оператор_отношения - любой из операторов отношения:
                     ==, !=, <>, <, >, <=, или >=.

       правая_часть - любой одиночный регистр, одиночная переменная или
                     постоянное выражение.

    Примеры правильных сложных условных выражений:

         (X + y > z)
         (int CX*DX < = 12*3)
         (byte first*second+hold == cnumber)

    Примеры недопустимых сложных условных выражений:

         (x+y >= x-y) // правая часть не является одиночной лексемой или
                         постоянным выражением.
         (Z = y) // вместо == ошибочно поставлен =


  4.6 Изменение типа выражения при присваивании.
  -----------------------------------------------

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

  int i, a;
  long b;
  char c;

    i = a * b + c ;

      Значения переменных a, b, и c в этом примере перед вычислением будут
  преобразованы к типу int (типу переменной i). Но если записать это
  выражение вот так:

    i = long a * b + c ;

      то  переменные  a,  b,  и  c  в  этом  примере  перед  вычислением будут
  преобразованы к типу  long, а конечный  результат будет преобразован  к типу
  переменной i - int.


  4.7 Вычисление в регистры EAX/AX/AL со знаком.
  -----------------------------------------------

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

  Например:

    int a,b,c;
    AX = a * b / c ;

  При этом компилятор генерировал без знаковые инструкции div и mul, так как
  регистры считаются без знаковыми переменными. Если написать вот так:

    AX = int a * b / c ;

  то компилятор сгенерирует инструкции idiv и imul.

       Обращаю ваше внимание, что для регистра AL можно использовать только
  модификатор char, для AX соответственно только int, а для EAX - long. Для
  остальных регистров подобное делать нельзя.


5. Идентификаторы.
===================

  5.1 Формат идентификатора.
  ---------------------------

      Идентификаторы в C-- должны начинаться или с символа подчеркивания '_'
  или заглавных или строчных букв. Следующие символы могут быть любой
  комбинацией символов подчеркивания, заглавных или строчных букв или чисел
  (от 0 до 9). Общая длина идентификатора не может превышать 64 символа.
  Символы с кодом больше 0x7A (код символа 'z') недопустимы.

  Примеры допустимых идентификаторов:

  _DOG
  Loony12
  HowdYBoys_AND_Girls
  WOW___
  X

  Примеры недопустимых идентификаторов:

  12bogus                                 /* не может начинаться с числа */
  WowisthisalongidentifieryupitsureisnotOyoulengthismorethat64chars
   /*длина идентификатора превышает 64 */
  Y_es sir                                /* пробелы недопустимы */
  The-end                                 /* дефисы недопустимы */


  5.2 Зарезервированные идентификаторы.
  --------------------------------------

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

  BREAK  CASE    CONTINUE  ELSE    EXTRACT  FALSE  FOR
  FROM   GOTO    IF        LOOPNZ  RETURN   SWITCH TRUE
  WHILE

  CARRYFLAG    MINUSFLAG  NOTCARRYFLAG  NOTOVERFLOW
  NOTZEROFLAG  OVERFLOW   PLUSFLAG      ZEROFLAG

  __CODEPTR__ __COMPILER__ __DATAPTR__ __DATESTR__ __DATE__    __DAY__
  __HOUR__    __LINE__     __MINUTE__  __MONTH__   __POSTPTR__ __SECOND__
  __TIME__    __VER1__     __VER2__    __WEEKDAY__ __YEAR__

  _export  asm     break   byte      case     cdecl   char       continue
  default  do      dword   else      enum     extern  far        fastcall
  float    for     goto    if        inline   int     interrupt  long
  loop     loopnz  pascal  return    short    signed  sizeof     static
  stdcall  struct  switch  union     unsigned void    while      word
  new      delete

  ESCHAR  ESBYTE  ESINT  ESWORD  ESLONG  ESDWORD  ESFLOAT
  CSCHAR  CSBYTE  CSINT  CSWORD  CSLONG  CSDWORD  CSFLOAT
  SSCHAR  SSBYTE  SSINT  SSWORD  SSLONG  SSDWORD  SSFLOAT
  DSCHAR  DSBYTE  DSINT  DSWORD  DSLONG  DSDWORD  DSFLOAT
  FSCHAR  FSBYTE  FSINT  FSWORD  FSLONG  FSDWORD  FSFLOAT
  GSCHAR  GSBYTE  GSINT  GSWORD  GSLONG  GSDWORD  GSFLOAT

  AX   CX   DX   BX   SP   BP   SI   DI
  EAX  ECX  EDX  EBX  ESP  EBP  ESI  EDI
  AL   CL   DL   BL   AH   CH   DH   BH
  ES   CS   SS   DS   FS   GS

  ST(0)  ST(1)  ST(2)  ST(3)  ST(4)  ST(5)  ST(6)  ST(7)  ST
  st(0)  st(1)  st(2)  st(3)  st(4)  st(5)  st(6)  st(7)  st

      Этот список может быть получен из C-- транслятора в любое время,
  запуском его с опцией /WORDS из командной строки.

      Если Вы пользуетесь при компиляции опцией командной строки /ia, которая
  позволяет использовать ассемблерные инструкции не заключая их в блоки asm и
  без префикса '$', то все имена ассемблерных инструкций становятся
  зарезервированными словами. Причем имена ассемблерных инструкций компилятор
  различает независимо от того, написаны они маленькими или большими буквами.

     Список имен поддерживаемых компилятором ассемблерных инструкции можно
  получить запустив компилятор с опцией /LAI.

     Кроме этого в ассемблерных инструкциях становятся зарезервированными
  следующие идентификаторы:

  ax   cx   dx   bx   sp   bp   si   di
  eax  ecx  edx  ebx  esp  ebp  esi  edi
  al   cl   dl   bl   ah   ch   dh   bh
  es   cs   ss   ds   fs   gs

  DR0   DR1   DR2   DR3   DR4   DR5   DR6   DR7
  CR0   CR1   CR2   CR3   CR4   CR5   CR6   CR7
  TR0   TR1   TR2   TR3   TR4   TR5   TR6   TR7
  MM0   MM1   MM2   MM3   MM4   MM5   MM6   MM7
  XMM0  XMM1  XMM2  XMM3  XMM4  XMM5  XMM6  XMM7

  dr0   dr1   dr2   dr3   dr4   dr5   dr6   dr7
  cr0   cr1   cr2   cr3   cr4   cr5   cr6   cr7
  tr0   tr1   tr2   tr3   tr4   tr5   tr6   tr7
  mm0   mm1   mm2   mm3   mm4   mm5   mm6   mm7
  xmm0  xmm1  xmm2  xmm3  xmm4  xmm5  xmm6  xmm7


  5.3 Универсальные регистры для 16 и 32-битного режима.
  -------------------------------------------------------

      При создании библиотечных функций очень часто приходится писать
  варианты функций для работы в 16-битном и 32-битном режимах, которые
  отличаются друг от друга лишь использованием в них либо 16-битных либо
  32-битных регистров соответственно. Но можно писать лишь одну функцию,
  используя в ней новый синтаксис регистров. Если компилятор встретит вот
  такой синтаксис:

    (E)AX=0;

      то компилятор будет использовать при компиляции 16-битного кода регистр
  AX, а при компиляции 32-битного кода регистр EAX.

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


  5.4 Предопределенные идентификаторы.
  -------------------------------------

      Идентификаторы, определяемые компилятором в зависимости от режима
  компиляции:

  __TLS__     идет компиляция под windows (w32, w32c, dll).
  __DLL__     идет компиляция dll.
  __CONSOLE__ идет компиляция консольного приложения windows
  __WIN32__   идет компиляция GUI-шного приложения
  __FLAT__    компилируется 32-битный код.
  __MSDOS__   компилируется 16-битный код.
  __TINY__    используется модель памяти tiny в 16-битном режиме
  __SMALL__   используется модель памяти small в 16-битном режиме
  __DOS32__   компилируется 32-битный код под DOS (d32)
  __COM__     компилируется com-файл
  __SYS__     компилируется sys-файл
  __ROM__     компилируется rom-файл
  __OBJ__     компилируется obj-файл
  __TEXE__    компилируется exe-файл модели tiny
  __EXE__     компилируется exe-файл модели small
  __MEOS__    компилируется исполняемый файл для MenuetOS
  codesize    компиляция ведется с оптимизацией на размер кода
  speed       компиляция ведется с оптимизацией на быстродействие кода
  cpu         определяет тип процессора для которого ведется компиляция:
  	    0 - 8086
  	    1 - 80186
  	    2 - 80286
  	    3 - 80386
  	    4 - 80486
  	    5 - Pentium
  	    6 - Pentium MMX
  	    7 - Pentium II

      Эти идентификаторы могут быть проверены директивами #ifdef или #ifndef.
  Идентификатор 'cpu' может быть использован лишь с операторами проверки
  условий:

  #ifdef cpu > 3  //если тип процессора больше 80386


6. Переменные.
===============

  6.1 Типы переменных.
  ---------------------

      В C-- имеется семь типов переменных (именованных областей памяти), это:
  byte, word, dword, char, int, long, float.

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

   NAME   | SIZE  |        VALUE RANGE          |        VALUE RANGE
   тип    |размер |   диапазон представления    |   диапазон представления
          |в байт.|    в десятичной системе     | в шестнадцатеричной системе
  ---------------------------------------------------------------------------
  byte    |   1   |           0 to 255          |        0x00 to 0xFF
  word    |   2   |           0 to 65535        |      0x0000 to 0xFFFF
  dword   |   4   |           0 to 4294967295   |  0x00000000 to 0xFFFFFFFF
  char    |   1   |        -128 to 127          |        0x80 to 0x7F
  int     |   2   |      -32768 to 32767        |      0x8000 to 0x7FFF
  long    |   4   | -2147483648 to 2147483647   |  0x80000000 to 0x7FFFFFFF
  float   |   4   |    -3,37E38 to +3,37E38     |  0xFF7FFFFF to 0x7FFFFFFF

      Примечание:  для работы с типами float, dword и long используются
  32-разрядные целочисленные команды, следовательно, для их выполнения нужно
  иметь процессор не хуже 80386, что сейчас не является большой проблемой.

      Для совместимости со стандартом, принятом в языке C, введены
  новые зарезервированные слова: 'short', 'signed', 'unsigned'. Для типа 'int'
  в 32-битном режиме изменена разрядность. Вот таблица всех вариантов новых
  типов данных:

  ---------------------------------------------------------
  |   полный тип     |допустимые сокращения|старые аналоги|
  ---------------------------------------------------------
  |signed char       |char                 |  char        |
  |signed int        |signed, int          |  int/long    |
  |signed short int  |short, signed short  |  int         |
  |signed long int   |long, signed long    |  long        |
  |unsigned char     |---                  |  byte        |
  |unsigned int      |unsigned             |  word/dword  |
  |unsigned short int|unsigned short       |  word        |
  |unsigned long int |unsigned long        |  dword       |
  ---------------------------------------------------------

      Старые типы 'byte', 'word' и 'dword' поддерживаются по прежнему и имеют
  функционально прежнее значение. Изменения коснулись лишь типа 'int'. Он в
  16-битном режиме, также как и тип 'unsigned int', имеет 16-битный размер, а
  в 32-битном режиме эти оба типа имеют размер в 32-бита. На первый взгляд
  такие свойства типа 'int' вносят некоторую путаницу, но это дает большой
  выигрыш при использовании этого типа в библиотечных файлах, которые могут
  быть использованы при компиляции 16-битных и 32-битных программ.


  6.2 Объявление переменных.
  ---------------------------

      Синтаксис для объявления переменных следующий:

  variable-type identifier;

  где 'variable-type' - 'char', 'byte', 'int', 'word', 'long', 'dword' или
  'float'.

      Одновременно могут быть объявлены несколько идентификаторов одного типа:

  variable-type identifier1, identifier2, ... , identifierN;

      Одномерные массивы могут быть объявлены следующим образом:

  variable-type identifier[elements];

  где elements -  постоянное выражение для  количества переменных этого  типа,
  объединённых в массив.

      Инициализированные массивы можно объявлять без указания числа
  элементов. При этом будет создан массив по фактическому числу элементов.

  variable-type identifier[] = { const1, const2 };

      Переменные при объявлении могут быть проинициализированы следующим
  образом:

  variable-type identifier = value;

      Некоторые примеры глобальных объявлений:
  byte i,j;    /* объявляет две переменные типа byte с именами i и j */
  word see[10] /* объявляет массив с именем see, состоящий из 10
                  элементов типа word */
  int h,x[27]  /* объявляет, переменную типа int с именем h,
                  и массив с именем x, состоящий из 27 элементов типа int */
  long size=0; /* объявлена переменная типа long с именем size и ей присвоено
                  значение 0. */


  6.3 Глобальные переменные.
  ---------------------------

      Глобальные переменные - это переменные, область действия которых
  распространяется на всю программу. В C-- использовать глобальные переменные
  можно в функциях, расположенных ниже места её объявления. Т.е. если Вы
  пишите функцию, в которой используете переменную var, а саму переменную
  объявляете ниже текста функции, то компилятор выдаст ошибку. Это связано с
  тем, что компилятор может знать тип переменной только после их объявления.
  Но для таких переменных можно использовать взятие их адреса, так как адрес
  переменной не зависит от его типа. Пример:

  void Proc(){
    gvar = 0; /* компилятор выдаст сообщение об ошибке, т.к. он еще не знает
                 типа переменной gvar */
    AX = #gvar; /* несмотря на то, что компилятор не знает и адреса этой
                   переменной такое выражение будет откомпилировано */
  }
  int gvar;

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

  void Proc(){
    DSINT[#gvar] = 0; /* компилятор успешно откомпилирует это выражение т.к.
                         ему теперь известен тип переменной gvar */
  }
  int gvar;

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


  6.4 Локальные переменные.
  --------------------------

      Локальные переменные - это переменные область действия которых
  распространяется лишь в пределах одной функции. Объявлять локальные
  переменные, в отличии от современных версий C, можно между именем функции и
  первой открывающейся фигурной скобкой. Пример:

  void PROC ()
  int i;  //объявлена локальная переменная типа int с именем i
  {
      for ( i=0; i<10; i++ ) WRITE(1);
  }

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

      К локальным переменным можно отнести и параметры стековых функций. Под
  них также отводится память в стеке.

      Можно инициализировать локальные переменные при их объявлении. Но есть
  некоторые ограничения. Нельзя инициализировать массивы и многомерные
  структуры. Инициализировать можно одним значением, т.е нельзя при
  инициализации локальных переменных пользоваться перечислением заключенным в
  фигурные скобки и операторами FROM и EXTRACT.

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

      Локальные переменные можно объявлять и в начале блока функции. Но
  только до начала тела функции. Пример:

  void proc(){
  int locproc;  // объявление локальной переменной
    locproc=0;  // а теперь пошло тело функции
  int locproc;  // а на это объявление переменной компилятор выдаст сообщение
                // об ошибке, т.к. уже началось тело функции
  }


  6.5 Динамические переменные и структуры.
  -----------------------------------------

      Наряду с уже известными Вам динамическими функциями в C-- есть
  возможность использовать динамически и переменные и структуры. Динамические
  переменные и структуры обозначаются также как и динамические функции -
  символом двоеточия перед началом их объявления. И также как и динамическая
  функция, динамическая переменная или структура будет вставлена в код, лишь
  в том случае, если она будет использована в программе.

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

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

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


  6.6 Присваивание одного значения нескольким переменным.
  --------------------------------------------------------

      Если Вам необходимо присвоить нескольким переменным одинаковые значения:

    var1=0;
    var2=0;
    var3=0;

      то теперь это можно записать более коротко:

    var1=var2=var3=0;

      При использовании такой записи генерируется более компактный и более
  быстрый код.


  6.7 Переменные типа float.
  ---------------------------

    6.7.1 Формат переменных типа float.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        Для представления значений с плавающей точкой в язык C-- введен тип
    float. Этому типу соответствует действительное число одинарной точности
    FPU.

        Формат представления данных с плавающей точкой включает три поля:
    знака, мантиссы и порядка. Знак определяется старшим значащим разрядом.
    Поле мантиссы содержит значащие биты числа, а поле порядка содержит
    степень 2 и определяет масштабирующий множитель для мантиссы.

    31 30.....23 22........0
    |  |      |  |         |
    |  |      |  -------------- - поле мантиссы
    |  ------------------------ - поле порядка
    --------------------------- - бит знака


    6.7.2 Константы с плавающей точкой.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Компилятор отличает вещественное число от целого по наличию в нем
    точки. Начинаться вещественное число должно либо цифрой от 0 до 9, либо
    знаком минус. Необязательной частью вещественного числа является
    показатель степени. Показатель степени отделяется от числа символом e или
    E. Пробелы недопустимы. Вот примеры допустимого синтаксиса:

     0.98
     -15.75
     3.14e2
     1.234567E-20


    6.7.3 Диапазон допустимых значений.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Вещественное число типа float может находиться в диапазоне от 3.37E38
    до -3.37E38. Минимально близкое к нулю значение равняется 1.17E-38 и
    -1.17E-38. Записывать вещественное число одинарной точности более чем 8
    цифрами не имеет смысла. Показатель степени может принимать значения от
    +38 до -38.


    6.7.4 Математические операции.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Компилятор поддерживает 4 основных действия над переменными типа
    float: сложение, вычитание, умножение и деление. Поддерживается также
    инкремент (var++ - увеличение на 1), декремент (var-- - уменьшение на 1),
    смена знака (-var) и обмен значениями (var1><var2). Остальные
    математические операции будут реализованы либо уже реализованы во внешних
    библиотеках. При вычислении значения переменной float можно использовать
    и переменные других типов, они будут автоматически преобразованы в тип
    float.

        ВНИМАНИЕ! Составные математические  операции выполняются в том
    порядке, в котором они записаны, невзирая на правила арифметики.


    6.7.5 Преобразования типов.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

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


    6.7.6 Операции сравнения.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~

        Если при операции сравнения левым операндом является переменная или
    выражение типа float, а правым является целочисленное значение, то
    целочисленное значение будет преобразовано в вещественный тип. Если же
    левым операндом является целочисленное выражение или переменная, а правым
    операндом значение типа float, то из правого операнда будет выделена целая
    часть, которая и примет участие в сравнении.


    6.7.7 Сравнение переменных типа float с 32-битным регистром.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        В регистрах могут содержаться знаковые, без знаковые и вещественные
    данные. По умолчанию считается, что в регистре находится без знаковое целое
    число. При сравнении переменных типа float с 32-битным регистром можно
    указывать тип данных содержащихся в регистре.  Для этой цели можно
    использовать модификаторы: 'signed', 'unsigned', 'float'. Примеры:

    float f=1.0;

    void PROC()
    {
      IF( f < signed ECX)	//в регистре ECX находится знаковое число
      IF( unsigned EBX > f) //в регистре EBX находится без знаковое число
      IF( f == float EAX )  //в EAX находится число формата float
    }

        ВНИМАНИЕ!  При операции сравнения с участием переменой типа float,
    содержимое регистра AX будет разрушено.


  6.8 Указатели.
  ---------------

      В C-- сейчас указатели реализованы не в полном объёме. Поэтому многие
  вещи, которые возможны в обычных языках C, здесь будут недоступны.

      Пример применения указателей в C--:

  char *string[4]={"string1", "string2", "string3", 0}; //массив указателей
  char *str="string4";

  main()
  int i;
  char *tstr;
  {
	FOR(i=0; string[i]!=0; i++){
		WRITESTR(string[i]);
		WRITELN();
	}
	FOR(tstr=str;byte *tstr!=0; tstr++){
		WRITE(byte *tstr);
	}
  }

      Указатели можно использовать при передаче параметров функциям, а в
  самих функциях в качестве как локальных, так и параметрических переменных.
  Указатели можно также использовать в структурах. Можно использовать
  указатели на указатели. Введена поддержка указателей на функции:

  void (*proc)();  //объявление указателя на функцию

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

      При инициализации указателей компилятор не контролирует то, чем
  инициализируется указатель. Т.е. Вы можете указателю на 'char' присвоить
  указатель на 'int' или указателю на функцию присвоить адрес переменной.
  Это может вызвать ошибку в работе программы.


7. Адресация.
==============

  7.1 Относительная адресация.
  -----------------------------
      Изначально индексный доступ к элементам в массивах любого типа в
  компиляторе осуществлялся побайтно, независимо от объявленного типа данных.
  Индексы ограничены форматом поля 'RM' процессора 8086, таким образом,
  доступны только следующие форматы индексов (где индекс - значение
  16-разрядной константы или постоянного выражения):

        variable[index]
        variable[index+BX+SI]
        variable[index+BX+DI]
        variable[index+BP+SI]
        variable[index+BP+DI]
        variable[index+SI]
        variable[index+DI]
        variable[index+BP]
        variable[index+BX]

      Начиная с версии 0.210, появилась возможность использовать в качестве
  индекса переменных типа 'char' 'byte' 'int' 'word' 'long' 'dword'. При этом
  доступ к элементам массива осуществляется в зависимости от объявленного типа
  массива.

      Также начиная с версии 0.210 появилась возможность использовать в
  качестве индексных и базовых регистров при относительной адресации любые
  32-битные регистры.

      Если Вы для адресации к элементам массива будете использовать регистры и
  числовые константы, из которых можно получить поле 'RM' для инструкций 8086
  процессора или комбинацию полей 'RM' 'BASE' и 'SIB' для 80386 процессора, то
  компилятор будет использовать эти регистры для генерации инструкции с этими
  полями. В результате Вы получите относительную побайтную адресацию к
  элементам массива.

      Если же из этих регистров невозможно получить поля 'RM', 'BASE', 'SIB',
  или для адресации будет использована переменная, то компилятор сначала
  вычислит это выражение в регистр (E)SI или другой, подходящий регистр, а
  затем умножит содержимое этого регистра на разрядность Вашего массива. Таким
  образом, в этом случае вы будете иметь поэлементную адресацию в массиве.
  Пример:

    AX = var [ 5 ];
    AX = var [ BX + 5 ];
    AX = var [ BX + CX ];
    AX = var [ i ];

      Компилятор сгенерирует следующий код:
  test.c-- 7: AX=var[5];
  0100 A12501                   mov     ax,[125h]

  test.c-- 8: AX=var[BX+5];
  0103 8B872501                 mov     ax,[bx+125h]

  test.c-- 9: AX=var[BX+CX];
  0107 89DE                     mov     si,bx
  0109 01CE                     add     si,cx
  010B 01F6                     add     si,si
  010D 8B842001                 mov     ax,[si+120h]

  test.c-- 10: AX=var[i];
  0111 8B362201                 mov     si,[122h]
  0115 01F6                     add     si,si
  0117 8B842001                 mov     ax,[si+120h]

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

      Такой двойственный подход реализован с целью сохранения совместимости
  новых возможностей с предыдущими.

      Несмотря на кажущуюся для неискушенного пользователя путаницу, этот
  механизм легко понять и запомнить по следующему простому правилу: если Вы
  используете в качестве индекса только цифровое значение или регистр BX, SI,
  DI, BP или любой 32-битный регистр, то компилятор сгенерирует код с
  побайтной адресацией. Если же в качестве индекса будет использована
  переменная, то компилятор сгенерирует код с поэлементной адресацией. Если
  же Вы хорошо знакомы с ассемблером, то Вам не составит большого труда
  понять в каких случаях Вы получите побайтную, а в каких поэлементную
  адресацию.

      Иногда требуется иметь побайтный доступ к элементам массива используя в
  качестве индекса переменную. Например

    AX=var[i];

      Для этого выражения будет сгенерирована поэлементная адресация, а нам
  нужна побайтовая. Для этого можно написать так:

    SI=i;
    AX=var[SI];

      Но можно это записать короче:

    AX=DSWORD[#var+i];

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

      Важно всегда помнить о двойственном подходе компилятора к вычислению
  адреса в массиве. Еще раз кратко:  если Вы в массиве адресуетесь используя
  числовую константу или регистры BX,DI,SI,BP компилятор использует эти
  значения без изменения.  Во всех других случаях будет коррекция значения в
  зависимости от типа массива.


  7.2 Абсолютная адресация.
  --------------------------

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

      Вычисленный индекс будет абсолютен в сегменте, регистр которого указан.
  Можно указывать любой из регистров DS, CS, SS и ES. На процессорах 80386 и
  более новых можно указывать также регистры FS и GS.

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

             // адресация в сегменте данных
         DSBYTE  [смещение] // адресует байт в сегменте DS
         DSWORD  [смещение] // адресует слово в сегменте DS
         DSCHAR  [смещение] // адресует char в сегменте DS
         DSINT   [смещение] // адресует int в сегменте DS
         DSDWORD [смещение] // адресует dword в сегменте DS
         DSLONG  [смещение] // адресует long в сегменте DS
	 DSFLOAT [смещение] // адресует float в сегменте DS

             // адресация в сегменте кода
         CSBYTE  [смещение] // адресует байт в сегменте CS
         CSWORD  [смещение] // адресует слово в сегменте CS
         CSCHAR  [смещение] // адресует char в сегменте CS
         CSINT   [смещение] // адресует int в сегменте CS
         CSDWORD [смещение] // адресует dword в сегменте CS
         CSLONG  [смещение] // адресует long в сегменте CS
	 CSFLOAT [смещение] // адресует float в сегменте CS

             // адресация в сегменте стека
         SSBYTE  [смещение] // адресует байт в сегменте SS
         SSWORD  [смещение] // адресует слово в сегменте SS
         SSCHAR  [смещение] // адресует char в сегменте SS
         SSINT   [смещение] // адресует int в сегменте SS
         SSDWORD [смещение] // адресует dword в сегменте SS
         SSLONG  [смещение] // адресует long в сегменте SS
	 SSFLOAT [смещение] // адресует float в сегменте SS

             // адресация в дополнительном сегменте данных
         ESBYTE  [смещение] // адресует байт в сегменте ES
         ESWORD  [смещение] // адресует слово в сегменте ES
         ESCHAR  [смещение] // адресует char в сегменте ES
         ESINT   [смещение] // адресует int в сегменте ES
         ESDWORD [смещение] // адресует dword в сегменте ES
         ESLONG  [смещение] // адресует long в сегменте ES
	 ESFLOAT [смещение] // адресует float в сегменте ES

             // адресация в дополнительном сегменте 2 (80386) +
         FSBYTE  [смещение] // адресует байт в сегменте FS
         FSWORD  [смещение] // адресует слово в сегменте FS
         FSCHAR  [смещение] // адресует char в сегменте FS
         FSINT   [смещение] // адресует int в сегменте FS
         FSDWORD [смещение] // адресует dword в сегменте FS
         FSLONG  [смещение] // адресует long в сегменте FS
	 FSFLOAT [смещение] // адресует float в сегменте FS

             // адресация в дополнительном сегменте 3 (80386) +
         GSBYTE  [смещение] // адресуют байт в сегменте GS
         GSWORD  [смещение] // адресуют слово в сегменте GS
         GSCHAR  [смещение] // адресуют char в сегменте GS
         GSINT   [смещение] // адресуют int в сегменте GS
         GSDWORD [смещение] // адресуют dword в сегменте GS
         GSLONG  [смещение] // адресуют long в сегменте GS
	 GSFLOAT [смещение] // адресует float в сегменте GS

  Примеры:
     Загрузить в AL байт из ячейки с шестнадцатеричным адресом 0000:0417
                 ES = 0x0000;
                 AL = ESBYTE [0x417];

     Переместить слово из ячейки с шестнадцатеричным адресом 2233:4455
     в ячейку с шестнадцатеричным адресом A000:0002
                 $PUSH DS
                 DS = 0x2233;
                 ES = 0xA000;
                 ESWORD [0x0002] = DSWORD [0x4455];
                 $POP DS

     Сохранить вычисленное значение выражения X + 2, имеющее
     тип int в ячейке с шестнадцатеричным адресом FFFF:1234
                 ES = 0xFFFF;
                 ESINT [0x1234] = X + 2;

     Сохранить BX в сегменте стека по смещению 42:
                 SSWORD [42] = BX;


8. Работа с блоками данных.
============================

  8.1 Структуры.
  ---------------

    8.1.1 Что такое структуры.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~

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


    8.1.2 Синтаксис.
    ~~~~~~~~~~~~~~~~~

    struct [<тег>] { <список-объявлений-элементов> }
      <описатель>[,<описатель>...];
    struct <тег> <описатель> [,<описатель>];

        Объявление структуры начинается с ключевого слова struct и имеет две
    формы записи.

        В первой форме типы и имена элементов структуры специфицируются в
    списке-объявлений-элементов. Необязательный в данном случае <тег> - это
    идентификатор, который именует структурный тип, определенный данным
    списком объявлений элементов. <описатель> специфицирует либо переменную
    структурного типа, либо массив структур данного типа.

        Вторая синтаксическая форма объявления использует тег структуры для
    ссылки на структурный тип, определенный где-то в другом месте программы.

        Список объявлений элементов представляет собой последовательность из
    одной или более объявлений переменных. Каждая переменная, объявленная в
    этом списке, называется элементом структуры.

        Элементы структуры запоминаются в памяти последовательно в том
    порядке, в котором они объявляются. Выравнивание элементов внутри
    структуры по умолчанию не производится. Но существует опция, включение
    которой в командную строку позволяет иметь выравнивание и внутри
    структуры. Сама структура выравнивается на четный адрес если включено
    выравнивание.

      Примеры объявлений структур:

    struct test
    {
      int a;
      char b[8];
      long c;
    } rr, ff[4];

        В этом примере объявлены структура с именем rr и массив из 4 структур
    с именем ff. Всему набору переменных присвоено название (тег) test. Этот
    тег можно использовать для объявления других структур. Например:

   struct test dd;

        Здесь объявлена структура с именем dd, имеющая набор элементов
    описанных в теге test.

        При объявлении структур с ранее объявленным тегом ключевое слово
   'struct' можно не писать. Т.е можно написать вот так:

      test dd;


    8.1.3 Инициализация структур при объявлении.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        После объявления структуры ее элементы могут принимать произвольные
    значения. Что бы этого не было надо структуры проинициализировать.
    Инициализировать структуры при их объявлении можно только глобальные. C--
    поддерживает несколько способов инициализации структур при их объявлении:

      1. Одним значением:

       struct test dd=2;

    В этом примере всем элементам структуры dd присваивается значение 2.

      2. Массивом значений:

       struct test dd={1,2,,6};

    В этом примере первому элементу структуры dd присваивается значение 1,
    второму - 2, четвертому - 6. Пропущенным и не доинициализированным
    значениям будет присвоено 0 значение.

      3. Командой FROM:

       struct test dd=FROM "file.dat";

    В этом примере на место где расположена структура dd при компиляции будет
    загружено содержимое файла <file.dat>. Если размер файла больше чем размер
    структуры, то лишние байты будут загружены в код программы, но они не
    будут востребованы. Если размер файла меньше чем размер структуры, то
    недостающие байты структуры будут заполнены нулями.

      4. Командой EXTRACT:

       struct test dd=EXTRACT "file.dat", 24, 10;

    В этом примере на место где расположена структура dd при компиляции будет
    загружен фрагмент из файла <file.dat> длиной 10 байт со смещения 24.
    Недостающие байты будут заполнены нулями.


    8.1.4 Инициализация структуры при выполнении программы.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

    void proc()
    struct test aa[5],rr;
    int i;
    {
      aa[0]=0x12345678;
      aa[i]=int 0x12345678;
      aa=long 0x12345678;
      rr=i;

    В первом примере память, занимаемая первой структурой массива из 5
    структур, будет заполнена байтом 0x78 (по умолчанию).

    Во втором примере память, занимаемая (i+1)-вой структурой массива из 5
    структур, будет заполнена словом 0x5678.

    В третьем примере память, занимаемая всем массивом из 5 структур, будет
    заполнена длинным словом 0x12345678.

    В четвертом примере память, занимаемая структурой rr, будет заполнена
    содержимым переменной i.

        Можно также копировать содержимое одной структуры в другую. Например:

      rr=aa[2];

    Будет скопировано содержимое третьей структуры массива структур aa в
    структуру rr.


    8.1.5 Операции с элементами структур.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        С элементами структур можно выполнять все те операции, которые
    доступны для переменных соответствующего типа. Например:  Объявлена
    структура:

    struct test
    {
      int a;
      char b[8];
      long c;
    } rr[3];
    Пример допустимого синтаксиса:
        rr.a = rr.b[i] * rr[1].c + i ;

    Примечание:
        При операциях с элементами массива структур и с индексированными
    элементами, в которых в качестве индекса или номера структуры используется
    переменная, компилятор может использовать регистры SI и DI, а в некоторых
    ситуациях (например: rr[i].b[j] >< rr[i+1].b[j+2] ) будет задействован и
    регистр DX.

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

    struct AA       //объявление тега структуры
    {
      word a[3];    // первый элемент структуры
      char b;       // второй элемент структуры
      long c;       // третий элемент структуры
    };

    struct BB	//тег второй структуры
    {
      word aa;	// первый элемент
      AA bb;	// второй элемент - вложенная структура
    }ss;		// объявляем структуру с тегом BB

    void proc()
    {
      AX=#ss.bb.b; // получить адрес элемента 'b' структуры 'bb' в структуре 'ss'
      AX=#BB.bb.b; // получить смещение этого же элемента в теге 'BB'
      AX=sizeof(ss.bb);	// получить размер элемента 'bb' в структуре 'ss'
      AX=sizeof(BB.bb);	// получить размер элемента 'bb' в теге 'BB'
    }


    8.1.6 Вложенные структуры.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

    struct RGB
    {
      byte Red;
      byte Green;
      byte Blue;
      byte Reserved;
    };

    struct BMPINFO
    {
      struct BMPHEADER header; //описание этой структуры пропущено
      struct RGB color[256];
    }info;

        Предположим Вам нужно получить содержимое переменной Red десятого
    элемента color. Это можно будет записать так:

      AL=info.color[10].Red;

        Но существует одно ограничение использования вложенных структур в C--.
    Это невозможность использования переменной в качестве индекса более одного
    раза при обращении к многоэкземплярным структурам. Поясним это на примере:

    struct ABC
    {
      int a;
      int b;
      int c;
    };

    struct
    {
      struct ABC first[4];  //4 экземпляра структуры ABC
      int d;
    }second[4];

    int i,j;

    void proc()
    {
      AX=second[i].first[j].a; //такая запись вызовет сообщение об ошибка, так
                               //как переменная использовалась в двух местах
      AX=second[2].first[j].a; //а этот синтаксис допустим.
      AX=second[i].first[3].a;
    }


    8.1.7 Отображение тега структуры на блок памяти.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Отображение тега структуры на блок памяти является альтернативой
    указателям на структуры.

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

        Объяснить, как использовать отображение тега структуры на память,
    наверное, будет проще на примере:

    struct AA       //объявление тега структуры
    {
      word a[3];    // первый элемент структуры
      char b;       // второй элемент структуры
      long c;       // третий элемент структуры
    };

    byte buf[256];  //блок памяти, на который будет отображен тег структуры

    void proc1()
    {
     ...
     proc2 ( #buf );  // вызов функции с передачей ей в качестве параметра
                      // адреса блока памяти
     ...
    }

    long proc2 (unsigned int pointer_to_mem)
    {
    int i;
      BX=pointer_to_mem;  // в BX загрузим адрес блока памяти
      FOR(i=0; i<3; i++){ // в массив элемента 'a' записать -1
        BX.AA.a[i]=-1;
      }
      BX.AA.b=0;
      ES:BX.AA.c=EAX;
      return BX.AA.c;  // вернуть содержимое элемента 'c'
    }

        В 16-битном режиме для хранения адреса структуры можно использовать
    регистры: BX,DI,SI,BP. Но лучше для этого использовать регистр BX.
    Регистры DI и SI может использовать компилятор при вычислении адреса
    многоэлементных объектов. Регистр BP компилятор использует для работы с
    локальными и параметрическими переменными. В 32-битном режиме можно
    использовать любой кроме ESP и EBP регистр, а регистры EDI и ESI надо
    использовать осторожно.


    8.1.8 Битовые поля структур.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

        Объявление битового поля имеет следующий синтаксис:

    <тип> [<идентификатор>]:<константа>;

    или на примере:

    int var:5;  //объявление битового поля размером 5 бит с именем 'var'

        Битовое поле состоит из некоторого числа битов, которое задается
    числовым выражением <константа>. Его значение должно быть целым
    положительным числом и его значение не должно превышать числа разрядов,
    соответствующие <типу> определяемого битового поля. В C-- битовые поля
    могут содержать только без знаковые значения. Нельзя использовать массивы
    битовых полей, указатели на битовые поля.

        <идентификатор> именует битовое поле. Его наличие необязательно.
    Неименованное битовое поле означает пропуск соответствующего числа битов
    перед размещением следующего элемента структуры. Неименованное битовое
    поле, для которого указан нулевой размер, имеет специальное назначение:
    оно гарантирует, что память для следующего битового поля будет начинаться
    на границе того типа, который задан для неименованного битового поля.
    Т.е.  будет произведено выравнивание битового поля на 8/16/32 бита.

        В C-- все битовые поля упаковываются одно за другим независимо от
    границ типа идентификаторов. Если последующее поле не является битовым
    полем, то оставшиеся до границы байта биты не будут использованы.
    Максимальный размер битового поля равен 32 бита для типа dword/long, 16
    бит для типа word/int и 8 бит для типа byte/char. Битовые поля можно
    объединять, т.е. использовать их в операторе 'union'. 'sizeof'
    примененный к битовому полю вернет размер этого поля в битах. При
    использовании битового поля, его содержимое будет расширятся в регистр
    как без знаковое целое число.


  8.2 Объединения.
  -----------------
      Объединения позволяют в разные моменты времени хранить в одном объекте
  значения различного типа.

      Память, которая выделяется под объединение, определяется размером
  наиболее длинного из элементов объединения. Все элементы объединения
  размещаются в одной и той же области памяти с одного и того же адреса.
  Значение текущего элемента объединения теряется, когда другому элементу
  объединения присваивается значение.

      В C-- реализованы так называемые анонимные объединения. Т.е.
  объединениям не присваивается имя, а обращение к элементам объединения
  происходит как к обычной переменной. Пример:

  union
  {
    dword regEAX;
    word  regAX;
    byte  regAL;
  };  // объявили, что 3 переменные расположены по одному и тому же
      // физическому адресу

  void test()
  {
  	regEAX = 0x2C;
  	BL = regAL;	//в регистре BL окажется значение 0x2C
  }

      Объединять можно переменные различных типов, массивы, строковые
  переменные и структуры. Объединения могут быть глобальными и локальными, а
  также располагаться внутри структур (пока в объединениях внутри структур
  нельзя использовать структуры). Глобальные объединения могут быть
  инициализированными и неинициализированными. Чтобы получить
  инициализированное объединение достаточно проинициализировать лишь первый
  элемент объединения. Если же первый элемент объединения не инициализирован,
  а следующие элементы инициализированы, то это вызовет сообщение компилятора
  об ошибке.


  8.3 Команды 'FROM' и 'EXTRACT'.
  --------------------------------

      В C-- есть очень оригинальные команды, которых нет в других языках. Это
  'FROM' и 'EXTRACT'.

      Команда 'FROM' имеет синтаксис:

  <тип_переменной> <имя_переменной> = FROM <имя_файла>;

      Встретив эту команду при компиляции, компилятор загрузит в выходной
  файл содержимое файла <имя_файла>, а <имя_переменной> будет идентификатором
  начала загруженного кода. Вот пример использования этой команды из файла
  tinydraw.c--:

  byte palette[PALSIZE] = FROM "TINYDRAW.PAL";  // buffer for palette

     Команда 'EXTRACT' имеет синтаксис:

  <тип_переменной> <имя_переменной> = EXTRACT <имя_файла>, <начало>, <длина>;

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

  byte LIT128 = EXTRACT "8X16.FNT", 16*128, 16;
  byte LIT130 = EXTRACT "8X16.FNT", 16*130, 16;


9. Операторы.
==============

  9.1 Условные инструкции.
  -------------------------

      Условные инструкции, при помощи которых осуществляется ветвление, такие
  же как в C.

      C-- имеет две инструкции ветвления. 'if' и 'IF'.

      'if' делает близкий условный переход, а 'IF' делает короткий
  (8-разрядный) условный переход. 'IF' выполняется быстрее и может экономить
  до 3 байт в размере кода, но может осуществлять переходы только в пределах
  127 байтов кода.

      Условные инструкции, как и в C, могут сопровождаться, как одиночной
  командой, так и блоком из нескольких команд, заключенных в фигурные скобки
  { и }. Условные инструкции имеют те же ограничения, что и условные
  выражения.

      Если за инструкцией 'IF' следует больше чем 127 байтов кода, транслятор
  выдаст следующее сообщение об ошибке:

          IF jump distance too far, use if.

  Это можно просто исправить, заменив в этом месте инструкцию 'IF' на 'if'.

      Команды 'else' и 'ELSE' используются точно так же, как в языке C.
  Отличие их в том, что 'ELSE' имеет ограничение адреса перехода 127 байт,
  такое же как 'IF'. 'else' генерирует код на 1 байт длиннее, чем 'ELSE'.

      Команды 'IF' и 'else', а также 'if' и 'ELSE' могут свободно смешиваться
  как в следующем примере:

          if( x == 2 )
              WRITESTR("Two");
          ELSE{ WRITESTR("not two.");
                printmorestuff();
              }

      Если за инструкцией 'ELSE' следует больше чем 127 байтов кода,
  транслятор выдаст следующее сообщение об ошибке:

          ELSE jump distance too far, use else.

      Это можно просто исправить, заменив в этом месте инструкцию 'ELSE' на
  'else'.


  9.2 Циклы 'do{} while'.
  ------------------------

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

      Истинность условного выражения проверяется после выполнения тела  цикла,
  поэтому блок кода будет выполнен, по крайней мере, один раз.

   Пример 'do {} while' цикла, в котором тело будет исполнено пять раз:

           count = 0;
           do {
              count++;
              WRITEWORD(count);
              WRITELN();
              } while (count < 5);

  Условное выражение в 'do {} while' инструкции должно соответствовать тем же
  правилам, что и в инструкциях 'IF' и 'if'.


  9.3 Циклы 'loop', 'LOOPNZ', 'loopnz'.
  --------------------------------------

      Циклы 'loop' повторяют блок кода, пока определённая переменная или
  регистр, выполняющие роль счётчика цикла, содержат значение, отличное от
  нуля. В конце выполнения блока кода, составляющего тело цикла, указанная
  переменная или регистр - уменьшается на 1, а затем проверяется на равенство
  нулю. Если переменная (или регистр) не равна нулю, тело цикла будет
  выполнено снова, и процесс повторится.

      Пример использования цикла 'loop' в котором в качестве счётчика цикла
  использована переменная:

          count = 5;
          loop( count )
              {WRITEWORD(count);
              WRITELN();
              }

      Наибольший эффект даёт использование регистра CX для циклов с небольшим
  телом, поскольку в этом случае компилятором генерируется цикл с применением
  машинной команды LOOP.

      Если перед стартом счётчик циклов содержит нулевое значение, команды
  тела цикла будут выполнены максимальное число раз для диапазона переменной
  (256 раз для 8-битного счётчика (переменной типа byte или char), 65536 для
  16-битного счётчика (переменной типа word или int), и 4294967296 для
  32-битного счётчика (переменной типа dword или long).

   В следующем примере цикл будет выполнен 256 раз:

           BH = 0;
           loop (BH)
              {
              }

      Если в команде не указано никакого счётчика цикла, цикл будет
  продолжаться бесконечно.

      Следующий пример будет непрерывно выводить символ звёздочки (*) на
  экран:

          loop()
              WRITE('*');

      Программист, если хочет, может использовать или изменять значение
  переменной счётчика цикла внутри цикла.

      Например, следующий цикл выполнится только 3 раза:

           CX = 1000;
           loop( CX )
              {
              IF( CX > 3 )
                  CX = 3;
              }

      Цикл можно также прервать оператором разрыва 'BREAK' или 'break'. Вот
  тот же пример с использованием 'BREAK':

           CX = 1000;
           loop( CX )
              {
              IF( CX > 3 )
                  BREAK;
              }

      Циклы 'LOOPNZ/loopnz' отличаются от цикла 'loop', тем, что перед входом
  в цикл проверяется равенство нулю аргумента цикла. Если аргумент равен
  нулю, то тело цикла ни разу не выполнится (в цикле 'loop' в этом случае
  тело цикла выполнится максимальное число раз). Цикл 'LOOPNZ' получается
  максимально эффективным при оптимизации на размер кода, если в качестве
  параметра-счетчика используется регистр CX/ECX. При этом компилятор
  использует ассемблерные инструкции JCXZ/JECXZ и LOOP.


  9.4 Цикл 'while', 'WHILE'.
  ---------------------------

       Синтаксис:
    while(<выражение>)
         <оператор>

      Цикл выполняется до тех пор, пока значение <выражения> не станет
  ложным. Вначале вычисляется <выражение>. Если <выражение> изначально ложно,
  то тело оператора 'while' вообще не выполняется и управление сразу
  передается на следующий оператор программы.

      Цикл 'WHILE' аналогичен циклу 'while', но при этом генерируется код на
  3 байта короче. Размер сгенерированного кода в цикле 'WHILE' должен быть
  меньше 127 байт.

    Примеры:
  	while ( i < 20 ){
  		WRITEWORD(i);
  		i++;
  	}

  	WHILE (i < 20 ) @WRITEWORD(i);	//цикл либо будет бесконечным либо не
                                          //выполнится ни разу


  9.5 Цикл 'for', 'FOR'.
  -----------------------

       Синтаксис:
    for ([<начальное выражение>]; [<условие>]; [<приращение>])
      <оператор>

      Цикл 'for' выполняется до тех пор, пока значение <условия> не станет
  ложным. Если <условие> изначально ложно, то тело оператора 'for' вообще не
  выполняется и управление сразу передается на следующий оператор программы.
  <Начальное выражение> и <приращение> обычно используются для инициализации
  и модификации параметров цикла.

      Первым шагом при выполнении 'for' является вычисление <начального
  выражения>, если оно имеется. Затем вычисляется <условие> и производится
  его оценка следующим образом:

      1) Если <условие> истинно, то выполняется тело оператора. Затем
  вычисляется <приращение> (если оно есть), и процесс повторяется.

      2) Если <условие> опущено, то его значение принимается за истину. В
  этом случае цикл for представляет бесконечный цикл, который может
  завершиться только при выполнении в его теле операторов 'break', 'goto',
  'return'.

      3) Если <условие> ложно, то выполнение цикла 'for' заканчивается и
  управление передается следующему оператору.

      Цикл 'FOR' аналогичен циклу 'for', но при этом генерируется код на 3
  байта короче. Размер сгенерированного кода в цикле 'FOR' должен быть меньше
  127 байт.

    Примеры:
  	for(i=0;i<5;i++){
  		WRITESTR("СТРОКА ");
  		WRITEWORD(i);
  		WRITELN();
  	}

      Число <начальных выражений> и число <приращений> не ограничено. Каждый
  оператор в <начальных выражениях> и <приращениях> должен разделяться
  запятой. Пример:

        for ( a=1, b=2 ; a<5 ; a++, b+=a ) {...

      Также есть возможность логического объединения <условий>. Объединять
  можно до 32 условий. Каждое объединяемое условие должно быть заключено в
  скобки. Пример:

        for ( a=0 ; (a>=0) && (a<10) ; a++ ){...


  9.6 Оператор переключатель 'switch'.
  -------------------------------------

      Синтаксис:
    switch(<выражение>){
      case <константа>:
        <оператор>
  	...
      case <константа>:
        <оператор>
  	...
      ...
      default:
        <оператор>
    }
      Оператор переключатель 'switch' предназначен для выбора одного из
  нескольких альтернативных путей выполнения программы. Выполнение начинается
  с вычисления значения <выражения>. После этого управление передается одному
  из <операторов> тела переключателя. В теле переключателя содержатся
  конструкции: case<константа>:, которые синтаксически представляют собой
  метки операторов. Оператор, получающий управление, - это тот оператор,
  значение константы которого совпадают со значением <выражения>
  переключателя. Значение <константы> должно быть уникальным.

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

    Оператор, следующий за ключевым словом 'default', выполняется, если ни
  одна из констант не равна значению <выражения>. Если 'default' опущено, то
  ни один оператор в теле переключателя не выполняется, и управление
  передается на оператор, следующий за switch.

      Для выхода из тела переключателя обычно используется оператор разрыва
  'break' (BREAK).

    Пример:
    switch (i){
      case 'A':
        WRITE(i);
        i++;
        BREAK;
      case 32:
        WRITE('_');
        i++;
        BREAK;
      default:
        WRITE('i');
    }

      Оператор 'switch' сейчас в компиляторе может реализовываться тремя
  способами: двухтабличным, табличным и методом последовательных проверок.

      Табличный метод является самым быстрым, а при большом числе операторов
  'case' и при незначительной разнице между максимальным и минимальным
  значениями 'case' он еще может быть и более компактным. Но у него есть и
  недостатки: в 16-битном режиме компилятор всегда использует регистр BX, а в
  32-битном режиме, если операндом 'switch' является регистр, то его значение
  будет разрушено.

      В методе последовательных проверок блок сравнений находится в начале
  тела оператора 'switch', это позволяет избавиться от 1-2 лишних 'jmp'. Но
  компилятор не может определить, какой тип перехода использовать при
  проверке значений 'case'. Это будет Вашей заботой. Если размер кода от
  начала тела оператора 'switch' до места расположения оператора 'case'
  меньше 128 байт, можно использовать короткий переход. В этом случае Вы
  можете указать оператор 'CASE', что приведет к генерации более компактного
  кода. Компилятор в предупреждениях будет Вам подсказывать о возможности
  использования операторов 'CASE'. Использование оператора 'CASE' в случаях,
  когда размер блока кода более 128 байт приведет к выдаче компилятором
  сообщения об ошибке.

     При двухтабличном методе создаются две таблицы - таблица адресов входа в
  тело оператора 'switch/SWITCH' и таблица значений 'case'. Генерируется
  функция сравнения входного значения со значениями во второй таблице. Если
  есть совпадение, то делается переход по адресу из второй таблицы. Этот
  метод является самым медленным, но при большом числе значений 'case' (более
  15) он становится самым компактным.

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

      Для оператора 'switch' введена также и короткая его форма - 'SWITCH'.
  Ее можно применять в случае, если размер блока кода между началом тела
  оператора и оператором 'default' (если он отсутствует, то концом тела
  оператора 'switch') меньше 128 байт. О возможности использования короткой
  формы компилятор будет сообщать в предупреждениях.

      Для оператора case/CASE, который может использоваться только в теле
  блока оператора switch/SWITCH, можно указывать диапазон значений. Сначала
  надо указывать меньшее значение, затем после многоточия большее. Пример:

  switch(AX){
    case 1...5:
      WRITESTR("Range AX from 1 to 5");
      BREAK;
  };

      Раньше Вам бы пришлось писать более громоздкую конструкцию:

  switch(AX){
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
      WRITESTR("Range AX from 1 to 5");
      BREAK;
  };

      Кроме того, что новый формат записи более компактен и более читабелен,
  но еще при этом компилятор создает более компактный и быстрый код.


  9.7 Оператор перехода 'goto', 'GOTO'.
  --------------------------------------

       Синтаксис:
       goto <метка>;
  	.
  	.
  	.
  <метка>:

      Оператор перехода 'goto' передает управление на <оператор> помеченный
  <меткой>. Аналогом в ассемблере оператору goto является команда jmp near.
  Аналогом в ассемблере оператору 'GOTO' является команда jmp short.


  9.8 Оператор разрыва 'break', 'BREAK'.
  ---------------------------------------

      Оператор разрыва 'break' прерывает выполнение операторов 'do-while',
  'for', 'switch', 'while', 'loop', 'loopnz', 'LOOPNZ'. Он может содержаться
  только в теле этих операторов. Управление передается оператору, следующему
  за прерванным циклом.

      Оператор 'BREAK' аналогичен 'break', но при этом генерируется код на 1
  байт короче. Размер сгенерированного кода от места где применяется 'BREAK'
  до конца цикла должен быть меньше 127 байт.

    Примеры:
  	FOR (i=0; ; i++){
  		FOR(j=0; j<WIDTH; j++){
  			IF(i==5)BREAK;
  		}
  		IF(i==10)BREAK;
  	}


  9.9 Оператор продолжения 'continue', 'CONTINUE'.
  -------------------------------------------------

      Оператор продолжения 'continue' передает управление на следующую
  итерацию в циклах 'do-while', 'for', 'while', 'loop', 'loopnz'. В циклах
  'do-while', 'while', 'loop' следующая итерация начинается с вычисления
  условного выражения. Для цикла 'for' следующая итерация начинается с
  вычисления выражения приращения, а затем происходит вычисление условного
  выражения.

    Оператор 'CONTINUE' аналогичен 'continue', но при этом генерируется код на
  1 байт короче. Размер сгенерированного кода от места где применяется
  'CONTINUE' до начала итерации должен быть меньше 127 байт.


  9.10 Логическое объединение условий.
  -------------------------------------

      Существует возможность логического объединения сравнений в условиях
  'IF' и 'if', циклах 'do{}while', 'while{}', 'WHILE{}', 'for{}' и 'FOR{}'.
  Каждое сравнение при их логическом объединении должно быть заключено в
  скобки.  Объединять можно не более 32 сравнений.

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

      Пример:

  	   if ( (a>3) && (b>4) || (c<8) ){

  Т.е. если произвести расшифровку этого условия, то получится следующее:
  условие выполнится если a>3 и b>4 или a>3 и c<8.


  9.11 Переход через циклы.
  --------------------------

     Для операторов 'BREAK', 'break', 'CONTINUE', 'continue' введена
  поддержка числового параметра, определяющего, сколько циклов надо
  пропустить, прежде чем будет выполнен этот оператор. Например, мы имеем три
  вложенных цикла:

  do{
     loop(CX){
        for(BX=0;BX<10;BX++){
  	 break;	  //стандартный оператор
  	 break 0; //break с параметром - пропустить 0 циклов
  	 break 1; //break с параметром - пропустить 1 цикл
  	 break 2; //break с параметром - пропустить 2 цикла
        }
  LABL0:
     }
  LABL1:
  }while (DX!=0);
  LABL2:

      В третьем цикле находится группа различных вариантов оператора 'break'.
  Первым стоит стандартный оператор 'break', при выполнении которого
  управление будет передаваться за пределы третьего цикла - на метку 'LABL0'.
  Вторым идет оператор 'break 0', при выполнении которого будет пропущено 0
  циклов и управление будет передано опять же на метку 'LABL0'. Таким
  образом, запись 'break' и 'break 0' являются синонимами. Третьим идет
  оператор 'break 1', при выполнении которого будет пропущен один цикл и
  управление будет передано за пределы второго цикла на метку 'LABL1'. Ну и
  наконец, последним идет оператор 'break 2', при выполнении которого
  компилятор пропустит два цикла и передаст управление за пределы третьего,
  на метку 'LABL2'. Метки в этом примере проставлены для удобства объяснения.
  Ну и я надеюсь, Вам понятно, что значение параметра не может превышать
  числа циклов находящихся перед текущим. Так для одиночного цикла этот
  параметр может принимать максимальное и единственное значение - 0.


  9.12 Инвертирование флага проверки условий.
  --------------------------------------------

      Инвертирование флага проверки условий в операциях сравнения 'if/IF'
  'for/FOR' 'while/WHILE' происходит с помощью символа '!' - 'not'.

     Выражения

    IF ( NOTCARRYFLAG )...   и  IF ( ! CARRYFLAG )...
    IF ( proc() == 0 )...    и IF ( ! proc() ) ...

  являются синонимами.


  9.13 Вычисление выражения, а затем проверка условия.
  -----------------------------------------------------

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

    IF (i=a+2 != 0 )...
    IF ( i++ )...
    IF ( a-- )...
    IF ( i+=4 == 0 )...

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


  9.14 Проверка битов при операции сравнения.
  --------------------------------------------

      Если в левой части выражения сравнения написано: BX & 5, то при
  вычислении выражения содержимое регистра BX будет изменено инструкцией
  'and'. Но иногда возникает необходимость в проверке битов без изменения
  содержимого регистра BX. Для этих целей надо использовать инструкцию
  'test'. Как же указать компилятору, в каких ситуациях использовать
  инструкцию 'and', а в каких 'test'? В стандартных языках C для этого
  используется механизм приоритетов - если выражение заключено в скобки, то
  производится его вычисление, если нет, то производится проверка. Но C-- не
  поддерживает приоритетов. Для разрешения этой проблемы в C-- решено
  использовать непосредственно саму инструкцию 'test'. Вот допустимые
  варианты синтаксиса:

  IF ( $test AX,5 )
  IF ( ! $test AX,5)
  IF ( asm test AX,5)
  IF ( ! asm { test AX,5 } )


  9.15 Оператор перестановки.
  ----------------------------

      В C-- есть оператор, который не встречается в других языках, это
  оператор перестановки. Оператор перестановки меняет местами содержимое двух
  переменных. Символьное обозначение этого оператора ><. Переменные с обеих
  сторон оператора перестановки должны иметь одинаковый размер, 8 бит и 8
  бит, 16 бит и 16 бит, или 32 бита и 32 бита.

    Вот некоторые примеры:

      AX >< BX; // сохраняет значение BX в AX и значение AX в BX
      CH >< BL; // меняет местами содержимое регистров CH и BL
      dog >< cat; /* меняет местами значения переменной dog и переменной cat*/
      counter >< CX; // меняет местами значения переменной counter
                     // и содержимое регистра CX

      Если перестановка осуществляется между двумя 8-разрядными переменными в
  памяти, будет разрушено содержимое регистра AL. Если перестановка - между
  двумя 16-разрядными переменными в памяти, будет разрушено содержимое
  регистра AX. Если перестановка - между двумя 32-разрядными переменными в
  памяти, будет разрушено содержимое EAX. В любом другом случае, например,
  между переменной в памяти и регистром, значения всех регистров будут
  сохранены.


  9.16 Оператор отрицания.
  -------------------------

      C-- поддерживает быстрый синтаксис смены знака переменной - оператор
  отрицания. Поставив '-' (знак минус) перед идентификатором переменной
  памяти или регистра и ';' (точку с запятой) после идентификатора, вы
  смените знак переменной памяти или регистра.

  Вот некоторые примеры:

           -AX; // результат тот же, что и при 'AX = -AX;' ,но быстрее.
           -tree; // то же самое, что 'tree = -tree;' ,но быстрее.
           -BH; // меняет знак BH.


  9.17 Оператор инверсии.
  ------------------------

      C-- поддерживает быстрый синтаксис выполнения логической инверсии
  значения переменной - оператор инверсии. Поставив '!' (восклицательный знак)
  перед идентификатором переменной памяти или регистром и ';' (точку с
  запятой) после идентификатора, вы выполните логическую (выполнится
  ассемблерная команда NOT) инверсию текущего значения переменной. Вот
  некоторые примеры:

          !AX; // то же самое, что ' AX ^ = 0xFFFF; ' но быстрее.
          !node; // заменяет значение 'node' его логической инверсией.
          !CL; // то же самое, что ' CL ^ = 0xFF; ' но быстрее.


  9.18 Специальные условные выражения.
  -------------------------------------

      C-- поддерживает восемь специальных условных выражений:

           CARRYFLAG
           NOTCARRYFLAG
           OVERFLOW
           NOTOVERFLOW
           ZEROFLAG
           NOTZEROFLAG
           MINUSFLAG
           PLUSFLAG

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

           IF( CARRYFLAG )
           {
           // здесь вы чего-то делаете
           }

      Если Вы желаете непрерывно выполнять блок кода до тех пор, пока не
  установится флаг переполнения, Вам следует использовать нечто подобное
  следующему куску кода:

          do {
              // здесь вы опять чего-то делаете
              } while( NOTOVERFLOW );


  9.19 Символ '$' - вставляет текущий адрес программы.
  -----------------------------------------------------

      Символ '$', кроме того, что является признаком последующей ассемблерной
  инструкции, в языке C--, как и в языке Assembler может указывать текущий
  адрес (смещение) компилируемой программы. Но в C-- он имел ограниченные
  возможности. Он мог быть использован лишь как аргумент в операторах
  GOTO/goto и ассемблерных инструкциях DW/DD/JMP.

      Этот символ может находиться в любом месте вычисляемого числового
  выражения и может быть применен в любом месте совместно с другими числовыми
  выражениями.

  Примеры применения:

  DW #main-$	//записать расстояние от функции main до текущего места
  GOTO $+2;	//перейти по адресу на 2 больше, чем текущий адрес


  9.20 Ключевое слово 'static' и оператор '::'.
  ----------------------------------------------

      Если перед объявлением глобальной переменной, структуры или функции
  указать слово 'static', то эти переменная, структура или функция будут
  доступны только в том файле, в котором они были объявлены. Т.е. если Вы
  включите этот файл в другой директивой 'include', то переменные объявленные
  во включаемом файле со словом 'static' не будут доступны в основном файле,
  и Вы можете в основном файле объявить другие переменные с такими же
  именами.

      Если Вы примените слово 'static' при объявлении локальной переменной в
  функции, то память для этой переменной будет выделена не в стеке, а в
  области данных программы. Но эта переменная будет доступна только внутри
  функции, в которой она была объявлена. Применение 'static' к локальным
  переменным дает возможность сохранять значение переменной для следующего
  входа в функцию.

      Слово 'static' можно применять к любому глобальному объекту
  (переменной, структуре, функции). Для локального использования это слово
  можно применять только к переменным.

      Если в Вашей программе есть глобальная и локальная переменная с
  одинаковыми именами, то в функции, в которой объявлена эта локальная
  переменная, Вы не имели доступа к одноименной глобальной переменной.
  Применив перед именем переменной оператор '::', Вы получите доступ к
  глобальной переменной.  Пример:

  int var;  //объявляем глобальную переменную

  void proc()
  int var;  //объявляем локальную переменную с именем уже существующей
            //глобальной переменной
  {
    (E)AX=var;	//имеем доступ только к локальной переменной
    (E)AX=::var;  //а так можно получить доступ к глобальной переменной
  }


  9.21 Оператор sizeof.
  ----------------------

      Операция sizeof определяет размер памяти, который соответствует объекту
  или типу. Операция sizeof имеет следующий вид:

   sizeof (<имя типа>)

      Результатом операции sizeof является размер памяти в байтах,
  соответствующий заданному объекту или типу.

      В C-- оператор sizeof можно применять к переменным, регистрам, типам
  переменных, структурам, функциям, текстовым строкам и файлам.

      Если операция sizeof применяется к типу структуры, то результатом
  является размер тега данной структуры.

      Если операция sizeof применяется к текстовой строке, то результатом
  операции является размер строки плюс завершающий нуль. Например:

   sizeof ("Test")

  результатом этой операции будет число 5. Если Вы напишите такую
  конструкцию:

  char a="Test";

   sizeof(a)

  то результатом будет 5 - размер памяти, отведенный для переменной a.

      При использовании оператора 'sizeof' с именем структуры вставляет
  фактический размер памяти, занимаемый структурой. Это особенно важно, если
  Вы объявили массив структур.

      Оператор 'sizeof' можно применять и к имени определенной ранее функции.
  Результатом будет размер этой функции. Но для динамических функций всегда
  будет ноль.

      Операцию sizeof можно применять и к файлам. Это бывает очень полезным
  при использовании оператора FROM, но может применяться и в других случаях.
  Пример применения оператора sizeof к файлам:

   sizeof ( file "filename.dat" )

  Результатом этой операции будет размер файла "filename.dat".


  9.22 Метки перехода.
  ---------------------

      Метки перехода применяются для указания начальных точек участков кода,
  используемых командами перехода встроенного ассемблера и операторами
  'goto'/'GOTO'.

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

      Метки определяются идентификатором, оканчивающимися двоеточием. Если
  идентификатор содержит хотя бы один символ строчных букв (букв нижнего
  регистра, маленьких букв), это глобальная метка перехода, в противном
  случае, это локальная метка перехода.

      Глобальные метки перехода не должны использоваться внутри динамических
  функций; там можно использовать только локальные метки. Это важно помнить,
  поскольку, из-за применения такого средства как макрокоманды, динамическая
  функция может присутствовать в нескольких местах кода, что будет означать,
  что метке соответствует больше чем один адрес.

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


10. Ассемблер.
===============

  10.1 Поддержка команд ассемблера.
  ----------------------------------

      Встроенный в C-- ассемблер поддерживает все инструкции 8088/8086,
  80286, 80386, 80486, Pentium, Pentium II и Pentium III процессоров.

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


  10.2 Ключевое слово 'asm'.
  ---------------------------

      Ключевое слово 'asm' является синонимом к '$' - префикс ассемблерной
  команды. После слова asm можно писать блок ассемблерных команд.  Пример:

  	asm {
  		.
  		.
  		push AX
  labl:
  		push BX
  		mov AX,0x1234
  		jmp short labl
  		.
  		.
  		.
  	}

    Метки внутри блока ассемблерных команд допустимы.


  10.3 Префикс 'dup' - повторение инструкций DB/DW/DD.
  -----------------------------------------------------

      Для ассемблерных инструкции DB, DW, DD введена возможность использовать
  префикс повторений 'dup'. Применение этого префикса имеет следующий
  синтаксис:

    $DW NUMREP dup VALTOREP

  NUMREP - число повторов инструкции DW.
  VALTOREP - величина, которая будет повторена NUMREP раз.

      В отличие от аналога этого префикса из ассемблера повторяемую величину
  заключать в скобки нельзя.


  10.4 Инструкции процессора Pentium III.
  ----------------------------------------

      В компилятор добавлена поддержка 19 новых инструкций MMX расширения

  MASKMOVQ   mmx,mmx
  MOVNTQ     m64,mmx
  PAVGB      mmx,mmx/m64
  PAVGW      mmx,mmx/m64
  PEXTRW     r32,mmx,i8
  PINSRW     mmx,r32/m16,i8
  PMAXUB     mmx,mmx/m64
  PMAXSW     mmx,mmx/m64
  PMINUB     mmx,mmx/m64
  PMINSW     mmx,mmx/m64
  PMOVMSKB   r32,mmx
  PMULHUW    mmx,mmx/m64
  PREFETCHT0 mem
  PREFETCHT1 mem
  PREFETCHT2 mem
  PREFETCHNTA mem
  SFENCE
  PSADBW     mmx,mmx/m64
  PSHUFW     mmx,mmx/m64,i8

      и 46 инструкций SSE расширения.

  ADDPS      xmm,m128/xmm
  ADDSS      xmm,xmm/m32
  ANDNPS     xmm,xmm/m128
  ANDPS      xmm,xmm/m128
  COMISS     xmm,xmm/m32
  DIVPS      xmm,m128/xmm
  DIVSS      xmm,xmm/m32
  MAXPS      xmm,m128/xmm
  MAXSS      xmm,xmm/m32
  MINPS      xmm,m128/xmm
  MINSS      xmm,xmm/m32
  MULPS      xmm,m128/xmm
  MULSS      xmm,xmm/m32
  ORPS       xmm,xmm/m128
  RCPPS      xmm,xmm/m128
  RCPSS      xmm,xmm/m32
  RSQRTPS    xmm,xmm/m128
  RSQRTSS    xmm,xmm/m32
  SQRTPS     xmm,m128/xmm
  SQRTSS     xmm,xmm/m32
  SUBPS      xmm,m128/xmm
  SUBSS      xmm,xmm/m32
  UCOMISS    xmm,xmm/m32
  UNPCKHPS   xmm,xmm/m128
  UNPCKLPS   xmm,xmm/m128
  XORPS      xmm,xmm/m128
  CMPPS      xmm,xmm/m128,i8
  CMPSS      xmm,xmm/m32,i8
  SHUFPS     xmm,xmm/m128,i8
  CVTPI2PS   xmm,m64/mmx
  CVTSI2SS   xmm,m32/r32
  CVTPS2PI   mmx,m128/xmm
  CVTTPS2PI  mmx,xmm/m128
  CVTSS2SI   r32,xmm/m128
  CVTTSS2SI  r32,xmm/m128
  LDMXCSR    m32
  STMXCSR    m32
  MOVHLPS    xmm,xmm
  MOVLHPS    xmm,xmm
  MOVMSKPS   r32,xmm
  MOVNTPS    m128,xmm
  MOVAPS     m128/xmm,xmm/m128
  MOVSS      xmm/m32,xmm/m32
  MOVUPS     xmm/m128,m128/xmm
  MOVHPS     xmm/m64,m64/xmm
  MOVLPS     xmm/m64,m64/xmm

      Многие из этих инструкций могут использовать в качестве операнда
  64-битные и 128-битные ячейки памяти. Компилятор C-- сейчас может работать
  только с 32-битными переменными. Поэтому для инструкций использующих в
  качестве операнда ячейки памяти размером больше 32-бит можно использовать
  переменные любых типов. Компилятор не будет выдавать на это сообщений об
  ошибке, будет использован адрес этой переменной, а сама инструкция будет
  использовать нужное ей число битов памяти, начиная с адреса указанной
  переменной. Например:

      Для инструкции 'movaps' один из операндов может быть 128-битной
  ячейкой памяти. Для этой инструкции допустимы следующий синтаксис:

  byte  var8_128[16];
  word  var16_128[8];
  dword var32_128[4];

  void proc()
  {
  asm{
    movaps var8_128,xmm0 //в массив из 16 байт будет записано содержимое XMM0
    movaps xmm1,var16_128	//в XMM1 будет записано содержимое 8 слов
    movaps var32_128,xmm1 //в массив из 4 двойных слов будет записано XMM1
  }
  }


11. Функции.
===============

  11.1 Типы функций и макрокоманд.
  ---------------------------------

      Сейчас C-- поддерживает 4 типа вызова функций: cdecl, pascal, stdcall
  и fastcall. Вот краткие характеристики этих типов вызовов функций:

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

  pascal  Этот тип вызова предполагает, что параметры передаются в том
  ------- порядке, в котором они записаны в программе. Освобождение стека от
  параметров производит сама вызываемая функция. Этот тип вызова является
  более компактным, чем cdecl.

  stdcall  Этот тип вызова является гибридом первых двух. Параметры
  -------- передаются функции в порядке обратном, тому в котором они записаны
  в программе. Освобождение стека от параметров производится в самой
  вызываемой функции.

  fastcall  Этот тип вызова функций предполагает что передача параметров
  --------- функции производится через регистры, тем самым отпадает
  необходимость освобождения стека от параметров. Для этого типа вызова
  функций существуют ограничения по числу передаваемых параметров. Для C это
  три параметра, а для C-- шесть. В C-- параметры передаются по умолчанию в
  следующем порядке: 1-й - AX/EAX, 2-й - BX/EBX, 3 - CX/ECX, 4 - DX/EDX, 5 -
  DI/EDI, 6 - SI/ESI. Параметры типов char или byte могут передаваться в
  количестве не более 4 или только в первых 4 регистрах: 1 - AL, 2 - BL, 3 -
  CL, 4 - DL. Этот порядок регистров может быть изменён, если явно указать
  его либо при объявлении функции, либо при её определении. Функции типа
  fastcall иногда еще называют регистровыми.

      В C-- по умолчанию, если имя функции написано большими буквами, то
  считается, что эта функция имеет тип вызова fastcall. Если же в имени
  функции есть хотя бы одна маленькая буква, то по умолчанию считается, что
  эта функция имеет тип вызова pascal, за исключением программ
  компилируемых с ключом /w32 /w32c или /DLL. В них по умолчанию применяется
  тип вызова функций stdcall. Если же Вы хотите изменить тип вызова функции
  из по умолчанию на любой другой, то эту функцию надо обязательно объявить
  с указанием типа желаемого вызова.

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


  11.2 Стековые функции.
  -----------------------

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

      Параметры для стековых функций, если они есть, могут иметь любой тип
  byte, char, word, int, dword, long или float.

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

      В списке параметров для каждого параметра указывается его тип.
  Параметры одного типа, идущие подряд, разделяются запятыми. Формальные
  параметры разного типа в объявлении функции разделяются символом ';'.

      В следующем примере стековая функция возвращает сумму всех своих
  параметров (имеющих различные типы) как величину типа word:

  	word add_them_all (int a,b,c; byte d,e; word x,y)
  	{
  	return( a+b+c+d+e+x+y );
  	}

      Ранее C-- делал вызовы стековых функций лишь в стиле pascal.
  Преимуществом этого способа вызова функций является компактность и более
  простой механизм генерации кода. К недостаткам, а соответственно и
  преимуществам си-стиля, можно отнести жесткую привязанность паскалевских
  функций к числу и типу передаваемых параметров (попробуйте при вызове
  функции в стиле pascal опустить один параметр и получите 100% зависание).
  Напомню некоторые технические детали обоих типов вызовов функций.

  Кадр стека C-- для близких функций стека в стиле pascal:
       АДРЕС
        ...
      BP + FFFE предпоследний байт локальных переменных
      BP + FFFF последний байт локальных переменных
      BP + 0000 Сохраненный BP
      BP + 0002 RET адрес
      BP + 0004 последнее слово передаваемых функции параметров (если они
                есть)
      BP + 0006 предпоследнее слово передаваемых функции параметров
       ...
      BP + nnnn первое слово передаваемых функции параметров

      Освобождение стека от переданных функции параметров происходит прямо в
  самой функции командой RET nnnn - где nnnn является размером переданных в
  стек параметров.

  Кадр стека C-- для близких функций стека в стиле си:
       АДРЕС
        ...
      BP + FFFE предпоследний байт локальных переменных
      BP + FFFF последний байт локальных переменных
      BP + 0000 Сохраненный BP
      BP + 0002 RET адрес
      BP + 0004 первое слово передаваемых функции параметров (если они
                есть)
      BP + 0006 второе слово передаваемых функции параметров
       ...
      BP + nnnn последнее слово передаваемых функции параметров

      Функции в стиле СИ заканчиваются командой RET. Освобождение стека от
  параметров происходит в том месте откуда была вызвана функция. Обычно это
  делается командой ADD SP,nnnn. Т.е. компилятор может точно знать сколько и
  каких параметров Вы передаете в данном случае функции и соответственно
  освобождает стек после завершения работы функции. Это очень удобно для
  функций, которые могут обрабатывать переменное число параметров (например,
  функции типа printf).

      Объявление функции имеет следующий вид:

    rettype modif procname();

      Первым идет необязательный тип возврата из функции. По умолчанию он для
  16-битных программ равен word, а для 32-битных dword. Затем должен идти
  также необязательный модификатор. По умолчанию все стековые функции в C--
  (за исключением режима компиляции программ под Windows, где по умолчанию
  действует стиль вызова функций stdcall) имеют стиль pascal. Далее идет имя
  функции со скобками, которые являются признаком того что Вы объявляете
  функцию, а не переменную. Завершает объявление символ точка с запятой.

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


  11.3 Регистровые функции.
  --------------------------

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

      Как уже было сказано, параметры (если они есть) для регистровой функции
  передаются через регистры. Регистровые функции могут иметь не более 6
  параметров. Если параметры имеют тип int или word, регистры по умолчанию
  используются в следующем порядке: AX, BX, CX, DX, DI, и SI.  Первые четыре
  параметра могут также иметь тип char или byte, в этом случае задействуются
  регистры AL, BL, CL и DL соответственно. Любой из шести параметров может
  иметь тип long, dword или float, тогда для него используется регистр EAX,
  EBX, ECX, EDX, EDI, или ESI.

      В следующем примере регистровая функция с именем TOGETHER возвращает
  значение типа word как результат умножения первого параметра, имеющего тип
  word, на второй параметр того же типа:

           word TOGETHER() /* AX = первый параметр, BX = второй параметр */
           {
           return (AX * BX);
           }

      В следующем примере регистровая функция с именем SHOW_NUM, которая не
  возвращает никакого значения, зато выводит на экран первый параметр
  (имеющий тип int), затем разделительный знак в виде двоеточия ':', а затем
  второй параметр (имеющий тип byte) :

           void SHOW_NUM () /* AX = первое число, BL = второе число */
           {
           $ PUSH BX
           WRITEINT (int AX);
           WRITE (':');
           $ POP BX
           WRITEWORD (BL);
           }

      Но если в функции сделать объявление порядка и типов используемых
  регистров, то возможно произвольное использование регистров. Более подробно
  об этом можно почитать в разделе об объявлениях параметров в регистровых
  функциях.

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


  11.4 Динамические функции.
  ---------------------------

      Динамические функции - функции, которые определены, но вставляются в
  код программы, только если есть вызов. Динамические функции могут
  использоваться как макрокоманды.

      Определение динамической функции начинается с символа двоеточия ':'.

  Пример динамической функции стека:

          : void setvideomode (byte mode)
          {
          AL = mode;
          AH = 0;
          $ INT 0x10
          }

  Пример динамической регистровой функции:

          : int ABS () /* AX = число, абсолютное значение которого ищется*/
          {
          IF (int AX < 0)
               -AX;
          }


    11.4.1 Установка динамической функции в определенное место программы.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

    :void proc ( int par1, par2)
    {
       ...
    }

        Мы имеем динамическую функцию, код которой был бы расположен ранее
    кода обычной функции нашей программы. Для этого перед определением этой
    функции надо написать такую строку:

    @ void proc ();

	В итоге динамическая функция будет вставлена в код программы не в
    конце ее, как обычно, а в месте, где будет расположена эта строка. Если
    динамическая функция имеет параметры, то прописывать эти параметры
    необязательно.

	В компиляторе есть еще более мощное средство, позволяющее все
    динамические объекты ( функции, переменные, структуры ) расположить в
    указанном месте, а не в конце программы, как обычно. Это директива
    #setdinproc. Встретив эту директиву, компилятор немедленно расположит все
    известные ему на этот момент динамические объекты в месте объявления этой
    директивы. Последующие динамические объекты будут располагаться как
    обычно, в конце программы, если конечно, не будет повторно применена
    директива #setdinproc.

	Это может быть применено и быть полезным при создании резидентных
    программ (TSR) и драйверов устройств.


  11.5 inline-функции.
  ---------------------

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

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

      Важно помнить, что статус директивы #inline автоматически меняется при
  смене режима оптимизации. При установке оптимизации на скорость статус
  директивы #inline устанавливается в TRUE, а при смене режима оптимизации по
  размеру кода, устанавливается в FALSE. Поэтому применяйте директиву #inline
  лишь после смены режима оптимизации.

      Директивы меняющие режим оптимизации #codesize, #speed и директива
  #inline, объявленные внутри функции распространяются только на оставшуюся
  часть функции, т.е. они становятся локальными. Для того чтобы изменения
  были глобальными эти директивы надо объявлять вне тела функции.

      Для того чтобы определить inline-функцию, надо в первой строке с именем
  функции вместо символа динамической функции (':') написать ключевое слово
  'inline'. Пример определения inline-функции:

  inline int fastcall abs(AX)
  {
      IF ( int AX < 0 ) -AX ;
  }

    11.5.1 Другое применение inline.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	Ключевое слово 'inline' имеет в функциях и другое применение. Если
    это слово расположено перед началом блока функции, то для такой функции
    не создается кадр стека и не генерируется завершающий функцию 'ret'.
    Пример:

    void PROC ()
    inline
    {
      ...
    }

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

    void proc (int par1, par2)
    inline
    {
      AX=par1; /* компилятор обратится с параметру 'par1' через регистр BP.
		  Но так как кадр стека не был создан, при выполнении этого
                  кода программа будет работать не правильно. */
       ...
    }

	Встретив такое определение функции, компилятор выдаст предупреждение
    о том, что в таких функциях использовать локальные и параметрические
    переменные нельзя.


  11.6 Функции обработки прерываний.
  -----------------------------------

      Функции обработки прерываний определяются следующим способом:

          interrupt procedure_name ()
          {
          // put code here (здесь должен быть код обработки)
          }

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

      Вот пример обработчика прерывания, который сохраняет значения всех
  регистров и загружает регистр DS:

       interrupt safe_handle ()
       {
       $ PUSH DS
       $ PUSH ES
       $ PUSHA   // для выполнения этой команды нужен процессор не хуже 80286
       DS = CS;  // здесь DS загружается для работы с моделью памяти типа tiny

       /* здесь вы делаете свою обработку */

       $ POPA   // для выполнения этой команды нужен процессор не хуже 80286
       $ POP ES
       $ POP DS
       }

      При завершении функции прерывания будет автоматически сгенерирована
  инструкция выхода из обработчика прерывания - IRET.


  11.7 Замена 'return' на 'goto'.
  --------------------------------

      В некоторых ситуациях, при компиляции программы, оператор 'return'
  будет заменяться на 'goto'. Это происходит при разрешенной оптимизации по
  размеру кода для операторов 'return', которые расположены внутри функции и,
  естественно, если размер кода для выполнения 'return' больше, чем размер
  кода для реализации 'goto'. Для динамических функций, которые используются
  как макросы, такая замена будет производится всегда. Оператор 'goto' будет
  выполнен на конец функции, там, где будет располагаться единственный выход
  из функции. В динамических функциях, используемых в качестве макросов,
  'return' в конце функции будет пропущен компилятором.

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

      Для оператора 'goto' существует его более короткий аналог - 'GOTO'.
  Для получения более компактного кода для оператора 'return' введен также
  более короткий оператор 'RETURN'. Его можно использовать, если от места
  его применения до конца функции находится не более 128 байт. Если Вы будете
  использовать 'RETURN' на большем расстоянии до конца функции, то компилятор
  выдаст сообщение об ошибке. При использовании 'return' на расстоянии меньше
  128 байт до конца кода, компилятор выдаст вам предупреждение о возможном
  использовании 'RETURN'.


  11.8 Возвращаемые значения.
  ----------------------------

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

      ---------------------------------------
      |возвращаемый тип|используемый регистр|
      ---------------------------------------
      |       byte     |      AL            |
      |       word     |      AX            |
      |       dword    |      EAX           |
      |       char     |      AL            |
      |       int      |      AX            |
      |       long     |      EAX           |
      |       float    |      EAX           |
      ---------------------------------------

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

           byte proc_one ()
           {
           return (42);
           }

           byte proc_two ()
           {
           AL = 42;
           }

      Многие DOS функции 0x21 прерывания в качестве индикатора успешного
  выполнения используют установку или сброс carry флага. Использовать флаги
  процессора при возврате из функций можно и в других случаях, когда надо
  иметь статус успешного или не успешного выполнения функции. Это позволит
  более полно использовать возможности процессора и соответственно уменьшит
  размер кода и повысит быстродействие программы.

      Наряду с флагами, при возврате из функций, по прежнему остается возврат
  различных типов и через регистр AL/AX/EAX. Если для функций объявлено, что
  она имеет тип возврата int и CARRYFLAG, то при использовании такой функции
  в операциях сравнения IF, WHILE... будет делаться проверка carry флага, а
  не сравнение регистра AX. Пример использования возврата флагов из функций:

  int CARRYFLAG FOPEN();	// объявление функции

  void proc()
  {
    IF ( FOPEN(name,0) ) Error ( "Not open file" );
  }

      Варианты допустимого синтаксиса для использования возврата флага:

  IF ( ! FOPEN() )...
  IF ( @ FOPEN() )...
  IF ( ! @ FOPEN() )...
  IF ( handl = FOPEN() )...
  IF ( handl = @ FOPEN() )...
  IF ( ! handl = FOPEN() )...
  IF ( ! handl = @ FOPEN() )...

      А вот варианты, в которых, несмотря на то, что для функции объявлен
  возврат флага, будет производиться сравнение регистра AX:

  IF ( FOPEN() == 5 )...	// производится сравнение
  IF ( FOPEN() + 2 )...   // результат функции подвергается дальнейшему
                          // вычислению, в результате которого флаги будут
  			// изменены.


  11.9 Объявление параметров в регистровых функциях.
  ---------------------------------------------------

      Ранее каждому параметру регистровой функции соответствовал строго
  определенный регистр. Например, для переменных типа int или word первый
  параметр передавался через регистр AX, 2-й - BX, 3-й - CX, 4-й - DX, 5-й -
  DI, 6-й - SI. Поэтому, если Вам было необходимо передать только один
  параметр через регистр SI, то приходилось перед ним писать пять запятых.
  Вот как, например, выглядит вызов функции STRCPY:

  void main ()
  {
    STRCPY ( , , , , #dest, #sourc ) ;
  }

      Теперь регистры могут располагаться при передаче параметров
  произвольным образом. Надо только объявить компилятору о том, какой регистр
  закреплен за каким параметром данной функции. После такого объявления
  компилятор будет сам следить за тем, через какой регистр передавать
  параметр функции, его размерностью и числом передаваемых параметров. Вот
  как будет выглядеть объявление и использование функции STRCPY:

  void STRCPY ( DI, SI ) ;	//это объявление функции

  void main ()
  {
    STRCPY ( #dest, #sourc ) ;	//а это вызов функции
  }

      Можно не делать объявления функции, а указать расположение регистров в
  заголовке функции. Но тогда такая функция должна вызываться только после её
  определения. Вот пример функции выводящей на экран несколько одинаковых
  символов:

  void PUTNCHAR(AL,CX,BL,BH)
  /* 1 параметр в AL - код символа, который будет выведен
     2 параметр в CX - число выводимых символов
     3 параметр в BL - цветовой атрибут
     4 параметр в BH - номер видеостраницы
  */
  {
    AH=9;
    $INT 0x10
  }

      При объявлении регистровой функции можно также указывать какой тип
  переменной ожидает функция (знаковый/без знаковый или вещественный). По
  умолчанию считается без знаковый тип. Однако знаковый тип указывать есть
  смысл только если параметр передается через регистр AL/AX/EAX. Через другие
  регистры переменная всегда передается как без знаковая. Пример объявления
  регистровой функции с указанием типов:

  int fastcall Exampl( word CX, int AX, DX, float ESI ) ;
   |    |        |        |         |   |   |
   |    |        |        |         |   |   |---- 4-й парам. имеет тип float и
   |    |        |        |         |   |         перед. через регистр ESI.
   |    |        |        |         |   |-------- 3-й парам. имеет по умолч.
   |    |        |        |         |             тип word и перед. через DX.
   |    |        |        |         |------------ 2-й парам. имеет тип int и
   |    |        |        |                       передается через регистр AX.
   |    |        |        |---------------------- 1-й парам. имеет тип word и
   |    |        |                                передается через регистр CX.
   |    |        |------------------------------- Имя объявляемой функции.
   |    |---------------------------------------- Модификатор, указывающий, что
   |                                              эта функ. явл. регистровой.
   |--------------------------------------------- Функция возвращает перемен.
                                                  типа int.

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

      Если Вы не объявили регистры ни при объявлении регистровой функции, ни
  в заголовке самой функции, то компилятор будет считать, что параметры в эту
  функцию передаются старым способом. Таким образом, достигается полная
  совместимость с предыдущими версиями компилятора.


  11.10 Объявление параметров в стековых функциях.
  -------------------------------------------------

      Как известно, ранее в C-- контроль за числом и типом передаваемых
  функции параметров возлагался на программиста. Поэтому возникла непростая
  задача, совместить одновременно отсутствие контроля за параметрами (для
  совместимости с предыдущими версиями) и ее наличие. В результате
  компромиссов появился вариант немного отличающийся от традиционно принятого
  в языках C.

      Главное отличие - это то, что параметры, определяемые при определении
  функции, не будут восприниматься компилятором для контроля за ними. Во всех
  языках C допускается совмещение прототипа функции и ее объявления. В C--
  для того, чтобы включился контроль за параметрами стековой функции, надо
  эту функцию обязательно объявить. Но не всякое объявление функции будет
  сигналом компилятору о включении контроля за параметрами этой функции. Если
  при объявлении в круглых скобках ничего не будет, то компилятор не будет
  отслеживать параметры, передаваемые этой функции. В C++ такое объявление
  означает, что функции не передаются никакие параметры. В C-- для этого надо
  при объявлении функции в круглых скобках обязательно написать void.
  Например:

  int proc ( void ) ;

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

      При объявлении функции имена параметров можно опускать. Как известно, в
  C-- параметры функции одного типа записываются через запятую. Для смены
  типа используют точку с запятой. При объявлении смену типа можно
  производить и после запятой:

  void ptoc ( int a, b, c; word d );
  void proc ( int, int, int, word );
  void proc ( int, int, int; word );

      Все эти примеры объявлений являются идентичными и допустимыми.

      Для контроля за функциями с переменным числом параметров был введен
  новый для C-- элемент синтаксиса - многоточие или его еще называют эллипс.
  Вот как будет выглядеть объявление функции printf:

  void cdecl printf ( word, ... );


  11.11 Использование макрокоманд.
  ---------------------------------

      Теперь любая динамическая функция может быть использована как макрос.
  Если перед вызовом динамической функции поставить символ '@', то код этой
  функции будет вставлен, а не вызван инструкцией CALL.

      При использовании стековых динамических функций в качестве макросов
  очистка стека от переданных параметров производится ассемблерной
  инструкцией 'ADD SP,SIZE_PARAMETRS' сразу после окончания кода вставленного
  макроса. Поэтому, если эта функция использовала флаги в качестве возврата,
  то они будут разрушены.


  11.12 Передача параметров в стековые функции через регистры.
  -------------------------------------------------------------

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

  int proc (int param1, param2, param3)
  {
    (E)BX = param3;
    (E)BX.TEG_STRUCT.var = proc2 (param1,papra2);
    proc3 (param1,param2);
  }

      В этом примере параметр param3 используется лишь для того, чтобы
  инициализировать регистр (E)BX, поэтому его можно сразу передать через
  регистр:

  int proc (int param1, param2, (E)BX)
  {
    (E)BX.TEG_STRUCT.var = proc2 (param1,papra2);
    proc3 (param1,param2);
  }

      Как Вы видите, функция немного упростилась.

      В принципе, порядок расположения стековых и регистровых параметров не
  принципиален. Но надо помнить, что содержимое регистров может быть легко
  разрушено, и поэтому лучше всего регистровые параметры инициализировать
  лишь после того, как были засунуты в стек все стековые параметры. Для
  функций типа 'pascal' регистровые параметры лучше располагать после
  стековых параметров. Для функций типа 'cdecl' и 'stdcall' сначала лучше
  располагать регистровые параметры.


  11.13 Вызов функций с адресом в регистре и переменной.
  -------------------------------------------------------

      В C-- допустимо делать вызов функции, адрес которой находится в
  регистре. Параметры для такого вызова передаются через стек. Тип вызова
  функции по умолчанию для программ под Windows stdcall, для остальных
  pascal. Адрес функции для 32-битных программ должен находится в 32-битном
  регистре, а для 16-битных программ в 16-битном регистре. Считается, что
  такой вызов имеет возврат типа 'unsigned int'. Пример:

    BX = # proc;
    BX (a);
    IF ( BX(b) == 0 ) AX=2;

    Вы получите следующий код:

  test.c-- 8: BX=#proc;
  0104 BB1A01                   mov     bx,11Ah

  test.c-- 9: BX(a);
  0107 FF76FC                   push    word ptr [bp-4]
  010A FFD3                     call    near bx

  test.c-- 10: IF (BX(b) == 0)AX=2;
  010C FF76FE                   push    word ptr [bp-2]
  010F FFD3                     call    near bx
  0111 85C0                     test    ax,ax
  0113 7503                     jne     118h
  0115 B80200                   mov     ax,2

      Кроме этих, используемых по умолчанию, типов, можно задавать любой
  другой тип передачи параметров:

  (E)AX fastcall (par1,par2);
  (E)AX cdecl (par1,par2);
  (E)AX stdcall (par1,par2);
  (E)AX pascal (par1,par2);

      При использовании типа вызова функций 'fastcall', будьте внимательны,
  чтобы передаваемые функции параметры не разрушили адрес вызываемой функции.
  При этом типе вызова функций параметры передаются следующим образом: 1-й -
  AX, 2-й - BX, 3-й - CX ...

      Таким же образом можно делать вызовы функций по адресу находящемуся в
  переменной. Для этих вызовов функций можно также применять модификаторы
  способов передачи параметров:

  unsigned int var;  //переменная, в которой находится адрес функции
  . . .
  var cdecl (par1,par2);  // вызов функции, адрес которой находится в
                          // переменной 'var' и передачей ей параметров в
                          // соответствии с типом 'cdecl'


  11.14 Встроенные в компилятор функции.
  ---------------------------------------

      Для некоторых функций Вы не найдете их исходные тексты в библиотеках
  компилятора. Код этих функций генерирует компилятор. Вот список этих
  функций:

  ABORT             Прекращение выполнения программы
  atan              Вычислить арктангенс числа
  atan2             Вычислить арктангенс числа
  ATEXIT            Зарегистрировать функцию выполняющуюся при выходе.
  cos               Возвращает косинус угла
  EXIT              Закончить программу с кодом ошибки
  exp               Возвращает экспоненту числа
  inp/inportb       Считать один байт из порта
  inport            Считать слово из порта
  inportd           Считать двойное слово из порта
  fabs              Возвращает абсолютное значение числа
  log               Вычисляет натуральный логарифм числа
  log10             Вычисляет десятичный логарифм числа
  outp/outportb     Записать один байт в порт
  outport           Записать слово в порт
  outportd          Записать двойное слово в порт
  sin               Возвращает синус угла
  sqrt              Извлечь квадратный корень через FPU.
  tan               Возвращает тангенс угла

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

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


    11.14.1 Функции 'ABORT', 'ATEXIT' и 'EXIT'.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Функции 'ABORT' и 'EXIT' связаны с работой директивы '#atexit' и
    функцией 'ATEXIT'. Наиболее оптимальную их реализацию и взаимную
    интеграцию может сделать только компилятор. Именно поэтому эти функции
    поддерживаются компилятором.

        Функция 'ATEXIT' - регистровая функция, которая регистрирует функцию,
    адрес которой передается ей в качестве параметра, т.е. через регистр
    (E)AX, как функцию завершения программы. При успешной регистрации
    'ATEXIT' возвращает 0. Всего можно зарегистрировать до 16 функций.

        Завершающие функции не должны иметь параметров и возврата. Эти
    функции будут выполняться в порядке обратном очередности регистрации в
    случае, если Вы будете завершать работу программы через вызовы функции
    'ABORT' или 'EXIT' или закончится работа функции 'main'. Если Вы
    завершите работу программы вызовом Функции ExitProcess под Windows или
    вызовом AH=0x4C; $int 0x21 под DOS, выход из программы произойдет без
    запуска зарегистрированных функций.

        Функция 'ABORT' и 'EXIT', если не включена директива #atexit делают
    вызов функции ExitProcess под Windows и вызов AH=0x4C; $int 0x21 под DOS.
    Функции 'ABORT' не передаются никакие параметры, и она завершает работу
    программы с кодом возврата 0. Функции 'EXIT' передается в качестве
    параметра код возврата, с которым она и завершает работу программы.


    11.14.2 Функции inp/inportb, inport, inportd, outp/outportb, outport и
                                                                    outportd
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

        Функции чтения из порта имеют такой прототип:

    byte inp ( word port );
    word inport ( word port );
    dword inportd ( word port );

        Функции записи в порт имеют такой прототип:

    void outp ( byte val; word port );
    void outport ( word val; word port );
    void outportd ( dword val; word port );

        Имена функций inp и inportb, также как и имена outp и outportb
    являются синонимами.


    11.14.3 Функции для работы с вещественными числами.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Эти функции реализуются компилятором и всегда вставляются в код как
    макросы, т.е. для них никогда не генерируется вызов функции. Кроме этого,
    если параметром одной функции является вызов другой, то результат работы
    второй функции остается в стеке FPU, а первая функция использует этот
    результат непосредственно из стека. Таким образом получаются более
    компактный код. Вот вымышленный пример:

    test.c-- 7: f = sin( sqrt(1) );
    0100 D9061C01                 fld     [11Ch]
    0104 D9FA                     fsqrt
    0106 D9FE                     fsin
    0108 D91E2001                 fstp    [120h]
    010C 9B                       fwait

        Эти функции имеют следующий прототип:

    float atan ( float val );
    float atan ( float val, val2 );
    float cos ( float val );
    float exp ( float val );
    float fabs ( float val );
    float log ( float val );
    float log10 ( float val );
    float sin ( float val );
    float sqrt ( float val );
    float tan ( float val );


  11.15 Классы.
  --------------

    11.15.1 Объявление функций в структурах.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        С введение поддержки объявления функций в структурах, структура
    становится подобной классу в C++. Т.е. такая функция становится методом
    класса. Пример:

    struct Point  // объявление класса
    {
    	int x; // элементы данных
    	int y; // класса типа Point
    	void SetX(int);  // объявление методов
    	void SetY(int);  // класса Point
    };

    void Point::SetX(int _x)  //определение функции класса Point
    {
    	IF((_x>=0)&&(_x<=MAX_X)) x=_x;
    // переменные x, y являются членами этого класса и поэтому доступ к ним из
    // функций этого же класса осуществляется напрямую.
     }

    void main()
    Point p;  //определяем структуру в стеке
    {
      p.y = p.x = 0;
      p.SetX(1);
    }

        При вызове функции являющейся методом класса ей неявным образом
    передается адрес этого класса (структуры). В самой функции этот адрес
    доступен через имя параметрической переменной 'this'. Эту переменную
    автоматически генерирует компилятор. Если в объявление функции в
    структуре указать ключевое слово 'static', то такой функции адрес класса
    не передается и переменная 'this' не генерируется.

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


    11.15.2 Наследование.
    ~~~~~~~~~~~~~~~~~~~~~~

        В C-- поддерживаются простые и множественные наследования. Объявление
    структуры с наследованием имеет следующий синтаксис:

    struct Derived : Base1, Base2, ... Basen
    {
      int x0;
    };

        Число базовых структур в производном не ограничено. При множественном
    наследовании структура может наследовать два и более экземпляра базовой
    структуры. При этом возникает неоднозначность. Пример:

    struct A
    {
      int x,y;
      . . .
    };

    struct B : A  //структура 'B' наследует 'A'
    {
      . . .

    };

    struct C : A  //структура 'C' наследует 'A'
    {
      . . .
    };

    struct D : B, C //структура 'D' наследует 'B' и 'C'
    {
      . . .
    };

    void main()
    D d;  //выделяем для структуры 'D' память в стеке и присваиваем ей имя 'd'
    {
      d.x0=0;

        В этом примере структура 'D' наследует два экземпляра структуры 'A' и
    в ней находятся два элемента с именем 'x0'. Компиляторы C++ при записи
    типа 'd.x0=0' выдают сообщение об ошибке. C-- эту запись обрабатывает,
    присваивание производится по умолчанию в элемент из последней базовой
    структуры, имеющей элемент 'x0'. Для того чтобы получить доступ ко
    второму элементу 'x0' (физически этот элемент находится в структуре
    первым), необходимо применить операцию разрешения видимости:

      d.B::x0=0;

        Из всего этого следует, что записи:

      d.x0=0;
    и
      d.C::x0=0;

         являются равнозначными.


    11.15.3 Наследование функций.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	Если в базовом классе есть функция, а в производном классе Вы эту
    функцию переопределили, то эта функция будет переопределена и в базовом
    классе. Таким образом функция определенная в базовом классе будет
    потеряна. Пример:

    struct Point  // базовый класс
    {
    	int x; // элементы данных
    	int y; // класса типа Point
    	void SetX(int);  // объявление методов
    	void SetY(int);  // класса Point
    };

    void Point::SetX(int _x)  // определение функции класса Point
    {
    	IF((_x>=0)&&(_x<=MAX_X)) x=_x;
    }

    struct Point2 : Point  // производный класс
    {
      int x2;
    }

    struct Point3 : Point  // еще один производный класс
    {
      int z;
    }

    void Point3::SetX(int _x)  // в этом производном классе переопределяем
    {                          // функцию SetX
    	IF((_x>=80)&&(_x<=MAX_X)) x=_x;
    }

	Функция SetX, определенная в базовом классе Point, теперь будет
    недоступна. Вместо кода определенного в этом классе, будет вызываться код
    функции, определенный в наследуемом классе Point3. При вызове функции
    SetX из другого производного класса Point2 будет также вызываться код
    функции, определенный в производном классе Point3. Переопределяя функцию
    таким образом, Вы замените код этой функции в базовом классе и во всех
    его наследуемых классах.

	Если Вам необходимо, чтобы код новой функции был доступен
    одновременно с кодом старой функции, то в производном классе Вам
    необходимо сделать еще одно объявление этой функции. Пример:

    struct Point  // базовый класс
    {
    	int x; // элементы данных
    	int y; // класса типа Point
    	void SetX(int);  // объявление методов
    	void SetY(int);  // класса Point
    };

    void Point::SetX(int _x)  // определение функции класса Point
    {
    	IF((_x>=0)&&(_x<=MAX_X)) x=_x;
    }

    struct Point2 : Point  // производный класс
    {
      int x2;
    }

    struct Point3 : Point  // еще один производный класс
    {
      int z;
      void SetX(int);  // в наследуемом классе делаем еще одно объявление
                       // функции SetX
    }

    void Point3::SetX(int _x)  // в этом производном классе переопределяем
    {                          // функцию SetX
    	IF((_x>=80)&&(_x<=MAX_X)) x=_x;
	EDI=this;
	EDI.Point.SetX(_x);  // делаем вызов одноименной функции из
			     // базового класса
    }

	Теперь из производного класса Point3 Вам доступны две различные
    функции с одним именем SetX. А из базового класса Point и из другого
    производного класса Point2 будет по прежнему доступен только базовый
    вариант функции SetX.


12. Типы выходных файлов.
==========================

  12.1 Выходные файлы типа COM.
  ------------------------------

      Этот тип выходного файла получается автоматически по умолчанию.

      Изначально C-- мог делать только файлы формата типа COM. В настоящее
  время появилась возможность получать файла типа EXE с моделями памяти tiny
  и small для 16-битного кода, а также 32-битные для DOS и Windows. Также
  есть возможность получения выходного файла в формате OBJ, что позволяет
  связывать программы на C-- с программами на других языках.


  12.2 Выходные файлы типа EXE.
  ------------------------------

      Этот формат файла можно получить, если компилировать с ключом командной
  строки /exe.

      Возможно также поддержка EXE-формата через выходной файл формата OBJ,
  который можно затем обработать линковщиком, не входящим в пакет C--.


  12.3 Выходной файл *.EXE с моделью памяти tiny.
  ------------------------------------------------

      Фактически код файла *.exe модели tiny ничем не отличается от кода
  *.com. В сущности, это тот же com-файл, к которому добавлен 32-байтный
  заголовок exe-файла. Единственное отличие возникает, когда Вы компилируете
  файл с директивой ?resize TRUE. В com-файле, по этой директиве, в код
  программы добавляется соответствующий код, изменяющий размер доступной
  памяти. В exe-файле для этих целей будет скорректирован заголовок
  exe-файла.

      Чтобы получить exe-файл с моделью памяти tiny, надо запустить
  компилятор с ключом в командной строке /TEXE.


  12.4 Объектный выходной файл OBJ.
  ----------------------------------

      В настоящее время C-- может только создавать OBJ-файлы, но не может их
  компоновать.

      Ранее C-- создавал obj-файлы, которые могли быть подключены к проектам
  созданным на других языках, т.е. ведомые (slave) модули. Причем из C--
  модулей для основного проекта были доступны только функции и эти функции не
  должны были использовать глобальные переменные.

      Теперь же C-- может создавать основной модуль (master), который может
  быть слинкован в самостоятельный файл.

      Для obj-файлов появилась возможность использовать внешние (extern)
  функции, переменные или структуры. Для этого достаточно их объявить как
  extern. Причем ключевое слово extern должно быть всегда первым. Пример
  объявления внешних объектов:

  extern void cdecl _printf(); // объявление внешней функции _printf имеющей
                               // тип cdecl  и тип возврата void
  extern int buts,cubs;        // объявление двух внешних переменных типа int
  extern struct IPXL ipxl;     // объявление внешней структуры ipxl имеющей тег
  			     // IPXL,  причем тег этой структуры должен быть
  			     // описан ранее.

      Появление возможности объявлять внешние объекты позволяет подключать к
  obj-модулю на C-- модули написанные на других языках или подключать к
  программе на C-- функции из библиотек на других языках. При объявлении
  внешних объектов очень важно правильно указать тип функции и ее имя. Если
  Вы будете использовать внешние функции, написанные на C то чаще всего, Вам
  нужно будет указывать модификатор cdecl, а к имени функции или переменной
  добавлять префикс '_'.

      Из основного (master) obj-файла написанного на C-- для других
  obj-модулей доступны все функции, глобальные переменные и глобальные
  структуры.

      Чтобы получить ведомый obj-модуль при компиляции надо использовать ключ
  /sobj.

      C-- может создавать obj-файлы с моделью памяти tiny и small. По
  умолчанию создаются модули с моделью tiny. Чтобы получить obj-файл с
  моделью памяти small надо запустить компилятор с ключами /obj и /exe.

      Для создания obj-файлов для 32-битного DOS в командной строке Вам
  необходимо указать ключи /d32 и /obj. Использовать полученный obj-файл мне
  удалось лишь с помощью wlink и расширителя zrdx.exe.

      Создание obj-файлов под windows не предусмотрено.


  12.5 COM файл symbiosis.
  -------------------------

    12.5.1 СИМБИОЗ - что это такое?
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Транслятор C-- имеет ключ, позволяющий добавлять компилируемую
    программу к концу уже имеющегося COM файла. Это называют COM-файл
    Symbiosis. Когда такая программа запускается, управление сначала получает
    добавленный код C--, и только после выполнения его функции main()
    управление получит первоначальный код COM-файла.

        Если добавленный вами код завершается EXIT() или ABORT(), программа
    прекратится, и первоначальный код COM-файла не будет выполнен. Это
    позволяет программе, добавленной к COM файлу, определять, будет ли
    управление передано на первоначальный код.


    12.5.2 Как это делать.
    ~~~~~~~~~~~~~~~~~~~~~~~

        Чтобы сделать это, Вы должны использовать ключ /SYM в командной
    строке компилятора, в которой указывается полное имя COM-файла, к
    которому что-то добавляется. При этом оригинал COM-файла не меняется, а
    новый файл содержит его в себе. Например, чтобы откомпилировать программу
    HELLO.C-- к концу копии C:\command.сом используют следующую команду:

             C-- /SYM C:\COMMAND.COM HELLO.C--

    Будет создан выходной файл HELLO.COM .


    12.5.3 Использование.
    ~~~~~~~~~~~~~~~~~~~~~~

        Вы можете, вероятно, придумать большое количество путей использования
    этой функции, типа:

             - Добавление защиты с использованием пароля к некоторым
	       специальным COM файлам.
             - Уменьшение памяти, доступной COM файлу при запуске.
             - Инициализация режима видео для COM файла.


    12.5.4 Злоупотребления.
    ~~~~~~~~~~~~~~~~~~~~~~~~

        Любой злоумышленник может придумать и вредные применения для этой
    функции. Наиболее очевидное из них - создание троянских коней. Я хотел бы
    указать, что это неконструктивное использование C--, и любое
    разрушительное использование симбиозов COM-файлов запрещено.


  12.6 SYS - драйверы устройств.
  -------------------------------

      Компилятор значительно облегчит Ваш труд при написании драйверов.
  Компилятор сам создаст заголовок драйвера и функции 'СТРАТЕГИЯ' и
  'ПРЕРЫВАНИЕ'. Вам остается лишь написать код обработки команд.

      Что бы откомпилировать файл драйвера устройства, надо добавить в
  командную строку ключ /SYS. Кроме того, появились новые директивы
  компилятору, которые действуют только с этим ключом. Вот они:

      ?sysattribute <значение>  - эта  директива передает  компилятору
  атрибут создаваемого драйвера. По умолчанию устанавливается значение
  0x2000.

      ?sysname <текстовая  строка> -  эта директива  передает компилятору
  имя будущего драйвера. По умолчанию присваивается имя "NO_NAME". Длина
  имени  не более 8 символов.

      ?syscommand <command_0>,<command_1>, ... <command_n>; - эта директива
  является обязательной. По этой директиве компилятору передается список имен
  функций обработки команд драйвера. Имена разделены запятыми. Список должен
  заканчиваться символом точка-с-запятой. Можно передать не более 25 команд.
  Если какая-то команда не имеет кода поддержки, то в список надо записать
  слово NONE.

      По умолчанию компилятор для драйвера не создает стек. Драйвер может
  пользоваться системным стеком. Но, говорят, что он имеет маленькую глубину.
  Если Ваши функции активно используют стек, и Вы не надеетесь на системный,
  то директивой ?stack <величина> можно заставить драйвер пользоваться своим
  стеком.

      Вашим функциям обработки команд при передаче управления в регистрах
  ES:BX будет передан адрес заголовка запроса. Регистр DS равен CS. При
  возврате управления ваши функции должны сохранить регистр DS. В регистре AX
  должен находиться код возврата. Остальные регистры могут быть использованы
  произвольным образом.

      Функцию обработки команды инициализации желательно располагать
  последней (чтобы иметь возможность отдать адресное пространство занимаемое
  этой функцией операционной системе). Перед этой функцией, если Вы в других
  функциях обработки команд используете динамические функции, обязательно
  должна быть директива ?setdinproc. Глобальные переменные должны быть
  обязательно проинициализированы.


  12.7 Компиляция кода расширителей ROM-BIOS.
  --------------------------------------------

      Расширители ROM-BIOS (BIOS видеоконтроллеров, сетевых карт...) имеют
  определенную структуру и требования. C-- теперь может облегчить Вам процесс
  создания кода ROM-BIOS. Если запустить компилятор на компиляцию с ключом
  командной строки /ROM, то компилятор создаст сигнатуру (заголовок)
  ROM-BIOS, заполнит оставшееся свободное место до указанного размера ПЗУ
  кодом заполнения, подсчитает и скорректирует контрольную сумму ПЗУ.

      Для этого режима компиляции есть несколько специфических директив:

    1.  ?sizerom value - эта директива сообщает компилятору размер ПЗУ в
  байтах. Если эта директива не указана, то компилятор сам выберет
  минимальный подходящий размер ПЗУ из ряда: 1024, 2048, 4096, 8192, 16384,
  32762 или 65536. Свободное от кода и данных место будут заполнены до конца
  размера ПЗУ байтом заполнения определяемого директивой ?aligner. По
  умолчанию он равен нулю, для РПЗУ типа 27ххх этот байт имеет смысл сделать
  равным 0xFF. Последний байт ПЗУ будет скорректирован компилятором таким
  образом, чтобы контрольная сумма равнялась нулю.

    2.  ?movedatarom TRUE/FALSE - эта директива сообщает компилятору есть ли
  необходимость копировать данные из ПЗУ в ОЗУ. По умолчанию она установлена
  в FALSE. Если эту директиву определить TRUE, то компилятор вставит в
  область инициализации код перемещающий данные из ПЗУ в ОЗУ. При этом
  регистр DS будет установлен на сегмент ОЗУ. Стек также будет переустановлен
  на этот сегмент. Таким образом, функция main получит управление с
  регистрами AX = ES = DS = SS = сегменту ОЗУ с перенесенными в него данными.
  Если эту директиву установить в FALSE, регистр DS все равно будет
  переустановлен на адрес сегмента ОЗУ, так как Ваш код будет использовать
  этот сегмент для неинициализированных глобальных переменных.
  Инициализированные переменные останутся в ПЗУ и все обращения к ним будут
  производиться через регистр CS. Так же останется не тронутым (таким, каким
  его установил главный BIOS) и стек.

    3.  ?dataseg value - этой директивой компилятору сообщается сегментный
  адрес ОЗУ, который может быть использован вашим кодом. По умолчанию он
  равен 0x70. Этот адрес вы можете узнать в любой момент, считав его из вашего
  кода по смещению 4. Например: DS = CSWORD[4];

      Некоторые замечания:

    1.  Не забывайте, что в момент инициализации ROM-BIOS, DOS еще не
  загружен, и соответственно все функции использующие вызовы DOS работать не
  будут.

    2. Нельзя завершать работу программы функциями ABORT() или EXIT() и им
  подобным. Работа расширителя ROM-BIOS должна завершаться только выходом из
  функции main().

    3. Если директива ?movedatarom установлена в FALSE, то будьте внимательны
  при работе с инициализированными переменными. Они в этом режиме доступны
  только для чтения, и адресуются через регистр CS.


  12.8 32-битные файлы.

    12.8.1 32-битный код под DOS.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Для того чтобы откомпилировать 32-битную программу под DOS надо
    запустить компилятор с ключом командной строки /d32. Но работа 32-битной
    программы под DOS-ом невозможна без расширителя DOS. Для C-- можно
    использовать DOS4GW или zrdx.exe или любой другой расширитель DOS. Чтобы
    компилятор знал, где искать stub файл и его имя, надо в файл c--.ini
    прописать строку stub=path_name_to_stub_file.  Пример:

        stub=c:\c--\zrdx.exe

        Если не добавлять в c--.ini эту строку, то компилятор сгенерирует
    32-битный exe-файл, но без расширителя DOS. Если в командной строке
    вместе с ключом /d32 указать и ключ /ns, то строка с переменной stub из
    файла c--.ini будет аннулирована, и вы получите файл без расширителя DOS.

        Для 32-битного DOS-файла можно использовать директивы компилятора
    ?parsecommandline TRUE/FALSE или его расширенный вариант ?argc
    TRUE/FALSE.  Реализована и поддержка директивы ?atexit TRUE/FALSE.

        Сейчас для 32-битных DOS-файлов используется LE-формат. Так как LE
    формат является стандартным, то теперь можно использовать почти любой
    stub, понимающий этот формат. Файлы LE формата можно сжимать программами
    типа UPX.EXE и ей подобными.

        Если Вы используете stub, который затем загружает DOS4GW.EXE, то
    начало Вашей программы должно иметь специальную сигнатуру. Компилятор
    автоматически сформирует ее, если Вы в командной строке или в c--.ini
    файле укажете ключ /DOS4GW. Такой ключ Вам необходимо будет применять,
    если Вы будете использовать в качестве stub 4gs.exe.

        Существует также поддержка блока кода использующего для перехода и
    работы в 32-битном режиме возможности DPMI сервиса. Исходный текст этого
    блока находится в файле startup.h-- и компилируется, если в командной
    строке указана опция /stub=dpmi или в файле c--.ini написать строку
    stub=dpmi.  Недостатком этого способа перехода и работы в 32-битном
    режиме являются необходимость обязательного функционирования на
    запускаемом компьютере DPMI сервиса. Так как, программа загружается как
    обычная DOS программа, и лишь в процессе работы переходит в 32-битный
    режим работы, размер программы ограничен размером свободной DOS памяти.
    Ну а преимуществом его является компактный размер исполняемого файла.


    12.8.2 32-битный код под Windows.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Для того чтобы откомпилировать программу, написанную под Windows надо
    запустить компилятор с ключом командной строки /w32.

        Если Вы в своей программе используете вызовы API-функций, то эти
    функции надо предварительно обязательно объявить. Объявление функций
    имеет следующую форму:

    extern WINAPI "DLL_name"
    {
        returncode procname1();
        returncode procname2();
        procname3();
    }

      где:
           DLL_name - имя и расширение dll-библиотеки, в которой находятся эти
                      функции.
         returncode - тип возврата из api-функций. По умолчанию он равен dword.

        Программы, написанные под Windows, имеют одну немаловажную
    особенность - все параметры в стековые функции передаются в обратном
    порядке (так называемый си-стиль), но очистка стека от параметров
    происходит в самих функциях. Получается своеобразный гибрид C и pascal
    стилей - stdcall.

        С помощю ключа /W32C компилятор создает консольный файл под Windows.

	Если при компиляции указывали опцию командной строки '/j0' или
    директиву '#jumptomain NONE', то Ваша программа будет компилироваться без
    использования кода начальной инициализации, описание которого находится в
    файле startup.h--.

	Код начальной инициализации для программ под Windows имеет следующий
    вид:

	hThisInst=GetModuleHandleA(0);
      #ifdef __CONSOLE__
	hStdOut=GetStdHandle(-11);
      #endif
        lpszArgs=GetCommandLineA();
      #ifdef __environ;
	environ=GetEnvironmentStringsA();
      #endif
	main();
	ExitProcess(EAX);

	Таким образом, в глобальных переменных hThisInst будет находится
    handl запущенного файла, а в lpszArgs адрес командной строки Вашего
    файла. Если Вы в командной строке указали опции '/p' или '/argc' или в
    начале вашего файла есть директивы '#parsecommandline TRUE' или 'argc
    TRUE', то компилятор создаст дополнительный код сделающий разборку этой
    командной строки на части. Если Вы компилируете консольную программу, то
    в вашей программе будет еще одна глобальная переменная - hStdOut. В этой
    переменной хранится handl стандартного вывода (экрана). Если Вы при
    компиляции программы указали опцию '/env', то в глобальной переменной
    environ хранится адрес переменной окружения программы.

	После завершения работы функции 'main' выполнятся функция
    'ExitProcess', которой в качестве параметра передается регистр EAX. Т.о.
    Вам для завершения работы программы будет достаточно сделать выход из
    функции 'main', предварительно загрузив в регистр EAX нужный Вам код
    возврата.

        Некоторые компиляторы создают DLL, в которых имена экспортируемых
    функций имеют такой формат:

       ProcName@8

        В этом имени после символа '@' указывается размер стека с
    параметрами, передаваемых функции.

        Объявлять такие функции нужно так:

    extern WINAPI "name.dll"
    {
       ProcName@8 ;
    }

    т.е. без круглых скобок. В программе, при обращении к такой функции, ее
    имя надо писать без суффикса @8, т.е. вот так - ProcName(param1,param2);


    12.8.3 Вызов API функций по ординалам.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

        Чтобы компилятор создал файл, использующий вызов API-функций по
    ординалам, надо сделать две вещи:

     1. Разрешить компилятору это делать. Для этого надо в опциях командной
    строки (или в файле C--.INI) указать ключ 'WO'.

     2. Сообщить компилятору - какой номер ординала соответствует какому
    имени функции. Функции, для которых не был указан ординал, будет создан
    вызов по имени. Установить соответствие имен функций ординалу можно двумя
    способами:

        a). Автоматически, с помощью опции командной строки 'IND=name.dll',
	по которой компилятор просканирует эту библиотеку и импортирует из
	нее все имена и ординалы функций. (Импорт возможет только из
	библиотек имеющих формат PE).

        b). В ручную указать в объявлении API-функций и ее ординал. Делается
	это так: после имени функции ставится точка, а за ней указывается
	номер ординала. Вот пример объявления API-функций с указанием ее
	ординала:

    extern WINAPI "user32.dll"
    {
      ............
      long  MessageBoxA.429();
      ............
    }

        В библиотеках (DLL), иногда существуют функции, для которых не
    указано их имя, но указан номер ординала. Вызов таких функций по имени не
    возможен, но можно это сделать по ординалу (если, конечно Вы знаете, для
    чего эта функция и что она делает). Для этого в объявлении API-функции
    Вам надо придумать для этой функции уникальное имя и указать реальный
    ординал. Затем в программе Вы будете обращаться к этой функции по
    вымышленному имени. Но если Вы случайно откомпилируете такой файл без
    ключа '/WO', то при запуске этой программы Вы получите сообщение, о том,
    что данного имени в библиотеке нет.

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


    12.8.4 Создание DLL под Windows.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Динамически подключаемые библиотеки позволят получать более
    компактные программы и ускорить процесс компиляции. К минусам
    использования DLL можно отнести необходимость наличия самих файлов DLL на
    запускаемом компьютере и немного увеличивается время запуска программы.

        Для того чтобы функция стала доступной для других программ надо в
    исходном тексте перед именем функции прописать ключевое слово - _export.
    Пример:

      void _export testproc()
      {
        ....
      }

        Для того чтобы создать DLL, нужно написать файл, в котором будут
    функции с ключевыми словами _export. Вспомогательные функции, которые
    могут понадобиться для работы основных экспортируемых функций, объявлять
    как _export необязательно. Затем этот файл нужно откомпилировать с ключом
    /dll.  В результате Вы получите готовую динамически подключаемую
    библиотеку.


    12.8.5 Инициализация DLL при загрузке.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

      Директивой #jumptomain NONE (-j0) управление при запуске передается
    сразу на функцию main.

      Во всех остальных случаях генерируется код заглушки и управление на
    функцию main не передается. Фактически функция main в этом случае не
    нужна.

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

    dword main ( dword hInstDLL, reason, reserv )
    {
      ...
    }


    12.8.6 Компиляция ресурсов.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

        Будет проще перечислить то, что встроенный в C-- компилятор ресурсов
    не умеет делать. Не обрабатываются операторы ресурсов: 'VERSION'
    и определяемые пользователем ресурсы. При необходимости,
    данные, вводимые с помощью этих операторов, можно ввести с помощью
    оператора 'RCDATA'. У многих операторов ресурсов есть необязательные
    параметры 'loading' и 'memory'.  Поддержка этих параметров не
    реализована. Встретив эти параметры, компилятор их просто пропустит.

        Заставить компилятор C-- обрабатывать ресурсы можно двумя способами:

        1. Включить в свой проект директивой '#include' файл с расширением
    '.rc'.  Файлы с таким расширением компилятор считает файлом с ресурсами.
    Файл ресурсов необходимо включать в Ваш проект лишь после включения
    заголовочных файлов Windows.

        2. Ресурсы можно располагать в теле исходного текста программы в
    произвольном месте. Текст ресурсов должен начинаться с директивы '#pragma
    resource start', а заканчиваться директивой '#pragma resoutce end'.
    Ресурсы могут быть разделенными на части и эти части можно располагать в
    любом удобном для Вас месте (глупо располагать ресурсы в блоке
    комментариев и потом удивляться, почему они не были откомпилированы).
    Компилятор соберет эти части и откомпилирует.

        Имена операторов можно писать как большими, так и маленькими буквами,
    но имена идентификаторов чувствительны к регистру.  В тексте ресурсов
    можно использовать директивы и комментарии.

        Ничто не мешает Вам использовать компиляторы ресурсов от других
    языков.  Главное, чтобы синтаксис файла ресурсов соответствовал выбранному
    компилятору.


  12.9 Выходные файлы для MeOS.
  ------------------------------

      Исполняемые файлы для операционной системы MenuetOS поддерживаются
  компилятором совсем недавно. Для того, чтобы откомпилировать файл для
  MenuetOS, нужно в опциях компилятору указать '/meos'. Вы получите файл без
  расширения, который потом можно будет выполнить в среде операционной
  системы MenuetOS.

      Если при компиляции файла Вы не указывали опцию '/j0' или не
  использовали директиву '#jumptomain NONE', то компилятор будет использовать
  файл начальной инициализации 'startup.h--', в котором для операционной
  системы MenuetOS создан блок инициализации и завершения программы.
  Завершать выполнение таких программ можно просто выйдя из функции 'main'.


  12.10 32-битные бинарные файлы.
  --------------------------------

      Указав в командной строке опцию /b32, Вы получите 32-битный код без
  всяких заголовков. По умолчанию, сгенерированый код буден настроен на
  нулевой стартовый адрес. Если Вам необходим другой стартовый адрес, то его
  можно указать с помощью директивы #imagebase startaddress или с помощью
  опции командной строки /wib=startaddress. Нельзя указывать стартовый адрес
  до указания типа генерируемого файла. Иначе компилятор сгенерирует код с
  нулевого адреса.

      При компиляции 32-битного бинарного файла, заголовочный файл startup.h--
  компилятором не используется.


13. Приложения.
================

  13.1 Поиск включаемых файлов.
  ------------------------------

      Поиск включаемого в вашу программу файла, имя которого объявляется
  директивой include и заключено в двойные кавычки "", производится
  компилятором по такой схеме:

  сначала делается попытка открыть файл в текущей директории. Если файла там
  нет, то далее делается попытка открыть файл в директории указанной
  директивой #includepath. Если директива не была задана или файла в этой
  директории не оказалось, то делается попытка открыть файл в директории
  указанной в командной строке командой /ip=path. Если эта команда не была
  задана или файла в указанной директории не оказалось, то делается попытка
  открыть файл в директории указанной в файле C--.INI командой ip=. Если эта
  команда не была задана или файла в указанной директории не оказалось, то
  делается попытка открыть файл в директории, на которую указывает переменная
  окружения C--. Если переменная окружения не была задана или файла в этой
  директории не оказалось, то делается последняя попытка открыть файл в
  директории, откуда был запущен компилятор.

      Если имя включаемого файла заключено в угловые скобки <>, то поиск
  этого файла производится в противоположном направлении, за исключением
  того, что поиск в текущей директории не производится.

      Для консольной версии компилятора имена главного модуля и включаемых
  файлов могут иметь длину более 8 символов.


  13.2 Регистры, которые должны быть сохранены.
  ----------------------------------------------

      Регистры, которые должны сохраняться - BP, DI, SI, DS, SS, SP, CS и IP.

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

      DI и SI сохранять не обязательно, если программист осознаёт
  последствия. DI и SI часто используются для индексации массивов, как
  например в формуле:

        dog = firehydrant(1,red) + legs[DI];

      Если DI не сохранялся в функции firehydrant, значение, присвоенное
  переменной dog, скорее всего, будет неправильным, поскольку индекс для
  массива legs был изменен. В сущности, для точного согласования все функции
  должны иметь специальное указание в комментарии на то, что в них не
  сохраняется содержимое регистров DI и/или SI.

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

      SS хранит сегмент стека и должен сохраняться. SP указывает на текущую
  позицию в стеке и тоже должен сохраняться.

      CS хранит сегмент кода программы.  Все команды выбираются с
  использованием CS и IP, следовательно их значения должны сохраняться. IP,
  как известно, указатель адреса команды, и CS и IP непосредственно не могут
  изменяться в процессорах 8086, 8088, 80286, 80386, 80486,...


  13.3 C--.ini файл.
  -------------------

      C--.ini файл предназначен для предустановки по умолчанию параметров
  компилятора.

      Сейчас компилятор поддерживает огромное число параметров командной
  строки. Правильное их использование позволит Вам получать более компактный
  код и может значительно облегчить Вам отладку программы. Но так как этих
  параметров очень много набирать их каждый раз в командной строке бывает
  утомительно и не исключена возможность пропустить какой-нибудь параметр.
  Чтобы избавить Вас от всех этих напастей и был введен c--.ini файл.

       Параметры командной строки прописываются в этом файле построчно.
  Синтаксис тот же, что и в командной строке, но без ведущего обратного слэша
  или минуса. Если файл расположен в директории, на которую указывает
  переменная окружения set c--=<path> или если эта переменная не определена,
  то в той же директории где и файл c--.exe, то эти параметры
  распространяются на все компилируемые программы. Если же файл c--.ini
  расположен в текущей директории, то параметры считываются только из этого
  файла и действуют только для текущего проекта.

      Допустимо использование комментариев. Признаком начала комментария
  является символ ';'. Все последующие символы после ';' и до конца строки
  считаются комментарием.

      Пример C--.ini файла:

  r-
  X
  3     ;это комментарий
  os

      ini-файл может иметь любое имя (но расширение должно быть обязательно
  ini). Имя этого файла с расширением должно быть передано компилятору в
  командной строке. Файл c--.ini загружается и обрабатывается автоматически
  до загрузки файла указанного в командной строке.

      Таким образом, файл *.ini можно использовать подобно make-файлу - в нем
  Вы можете указать и имя главного компилируемого модуля, и все необходимые
  для его компиляции настройки.

      Как альтернативу c--.ini файлу, параметры командной строки можно
  прописывать непосредственно в начале главного файла компилируемого проекта,
  используя директиву 'pragma option'. С одной стороны это обеспечит Вашему
  проекту независимость от настроек компилятора, если Ваш проект будет
  компилироваться на другом компьютере. Но с другой стороны некоторые
  настройки являются индивидуальными для данного компьютера (это расположение
  библиотек, имена и расположение stub-файлов). Какой вариант использовать
  решать Вам, но как говорят, и я с этим согласен, лучше пользоваться золотой
  серединой - Часть параметров прописать в c--.ini файле, а другую
  непосредственно в компилируемом файле.


  13.4 startup.h-- файл.
  -----------------------

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

      В блоке начальной инициализации программы может производится (если Вы
  это укажете с помощью опций командной строки или используя директивы),
  разбор командной строки на параметры, сохранение переменой окружения,
  поддержка работы функции ATEXIT, изменение размера доступной памяти для
  *.com файлов и многие другие подготовительные операции. Если Вы
  откомпилируете свой файл не используя никаких опций командной строки и у
  Вас будет отсутствовать c--.ini файл, а в самом компилируемом файле у Вас
  будут отсутствовать директивы, то при компиляции *.com файла в него будет
  включен блок изменяющий размер доступной памяти и сигнатура 'SPHINXC--'.

      Если Вы компилируете файл типа *.exe (кроме файла модели tiny для DOS)
  и используете директиву 'jumptomain NONE' или ключ командной строки '/j0',
  то для этого проекта файл startup.h-- компилятором не используется. Не
  используется этот файл также при компиляции *.com файлов если, кроме '/j0',
  в этом проекте не используется разбор командной строки (/p /argc), не
  применяется функция ATEXIT (/at), не используется адрес переменной
  окружения (/env), не используется очистка области post-адресов (/cpa), не
  используется уменьшение доступной программе памяти (/r) и не используется
  заглушка нажатий CTRL-C (/c).

      Кроме блока начальной инициализации программы в файле startup.h--
  находятся динамические функции:

  void CLEARPOSTAREA( (E)AX );  - очистка post-области данных.
  unsigned int PARAMSTR( ECX ); - получить адрес элемента командной строки
  unsigned int PARAMCOUNT();    - получить число элементов в командной строке

      При разборе командной строки на составляющие ее элементы для 32-битных
  программ реализована поддержка длинных имен. Для 16-битных программ
  поддержка разбора командной строки с учетом длинных имен подключается, если
  Вы в начале свой программы укажете директиву:

  #define _USELONGNAME TRUE

      либо в c--.ini файле или в командной строке компилятора укажете опцию
  'd=_USELONGNAME'.


  13.5 mainlib.ldp файл.
  -----------------------

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

      Эти функции откомпилированы только для 16-битного режима работы
  программы. Если Вы будете использовать эти функции в 32-битной программе,
  то компилятор на это не выдаст никаких сообщений и включит эту функцию в
  Ваш код. Но при запуске такой программы она неизбежно потерпит крах.

      Использовать эту библиотеку очень просто. Все что нужно, это
  расположить эту библиотеку в одной с компилятором директории. Тогда
  компилятор, если встретит в вашей программе вызов функции, которая не была
  определена ни во включаемых в программу библиотечных файлах, ни в вашей
  программе, будет искать эту функцию в файле mainlib.ldp. Если эта функция
  будет найдена в этом файле, то ее код будет перенесен в Ваш файл, иначе
  будет выдано сообщение о неизвестной функции. Таким образом, чтобы функция
  была вставлена в вашу программу из библиотеки mainlib.ldp Вам нужно в свою
  программу не включать библиотечный файл, содержащий функцию с таким же
  именем.

      Список функций находящихся в этой библиотеке можно получить с помощью
  специальной программы cmmlib.exe. Эту программу можно найти в архиве
  cmmlib.rar. Извлеките программу cmmlib.exe из этого архива и расположите ее
  в одной с компилятором директории. Затем запустите эту программу с ключом
  /L и Вы получите список функций находящихся в этой библиотеке.


  13.6 C-- символы.
  ------------------

  SYMBOL|FUNCTION                  |EXAMPLE
  --------------------------------------------------------------------
    /*  |начинают блок комментария |/* комментарий */
    */  |завершают блок комментария|/* комментарий */
        |                          |
    //  |комментарий до конца линии|// комментарий
        |                          |
     =  |присвоение                |AX = 12;
     +  |сложение                  |AX = BX + 12;
     -  |вычитание                 |house = dog - church;
     *  |умножение или указатель   |x = y * z; AL = * var;
     /  |деление                   |x1 = dog / legs;
     &  |поразрядное логическое И  |polution = stupid & pointless;
     |  |поразрядное логическое ИЛИ|yes = i | mabe;
     ^  |поразрядн. исключающее ИЛИ|snap = got ^ power;
    <<  |битовый сдвиг влево       |x = y << z;
    >>  |битовый сдвиг вправо      |x = y >> z;
        |                          |
    +=  |сложение                  |fox += 12;   // fox = fox +12;
    -=  |вычитание                 |cow -= BX;   // cow = cow - BX;
    *=  |умножение                 |a *= b;      // a = a * b;
    /=  |деление                   |a /= b;      // a = a / b;
    &=  |поразрядное логическое И  |p &= q;      // p = p & q;
    |=  |поразрядное логическое ИЛИ|p |= z;      // p = p | z;
    ^=  |поразрядн. исключающее ИЛИ|u ^= s;      // u = u ^ s;
    <<= |битовый сдвиг влево       |x <<= z;     // x = x << z
    >>= |битовый сдвиг вправо      |x >>= z;     // x = x >> z
        |                          |
    ><  |обмен значениями          |x >< y; /* меняет местами значения x и y */
        |                          |
    ==  |проверка на равенство     |IF(AX == 12)
     >  |проверка на больше чем    |IF(junk > BOGUS)
     <  |проверка на меньше чем    |if( x < y )
    >=  |проверка больше или равно |if(AX >= 12)
    <=  |проверка меньше или равно |IF(BL >= CH)
   !=   |проверка на неравенство   |IF(girl != boy)
    <>  |проверка на отличие       |if (cat<>dog) /* та же функция что != */
        |                          |
    @   |вставка кода              |@ COLDBOOT(); /* вставляет COLDBOOT код */
    :   |динамическая функция      |: functionname () //объявляет functionname
    $   |ассемблерная команда      |$ PUSH AX   /* заносит AX в стек */
    #   |получение адреса(смещения)|loc = #cow;    /* loc = address of cow */
        |или директива             | #resize FALSE
    !   |оператор NOT или смена    |!x_var;  if(!proc())
        |флага операции сравнения. |
   ...  |любое число параметров в  | void proc(...);
   ::   |разрешение видимости      | ::var=0;
