Г Л А В А 8...............................................12 ВИДЕОФУНКЦИИ ТУРБО СИ.......................................12 В этой главе................................................12 Несколько слов о видеорежимах...............................14 Несколько слов о текстовых и графических окнах..............16 Что такое окно?.............................................16 Что такое viewport (графическое окно)?......................17 Координаты..................................................17 Программирование в текстовых режимах........................19 Функции ввода/вывода с консоли..............................19 Вывод текста и манипуляция с ним............................20 Управление режимами и окнами................................24 Управление атрибутами.......................................25 Запрос состояния............................................27 Текстовые окна..............................................29 Тип text_modes..............................................30 Цвета текста................................................32 Высокоэфективный вывод: переменная directvideo..............35 Программирование в графическом режиме.......................37 Функции графической библиотеки..............................40 Управление графической системой.............................40 Более подробное обсуждение..................................44 Рисование и закрашивание....................................47 Манипуляция экраном и графическим окном.....................51 Вывод текста в графическом режиме...........................55 Управление цветом..........................................60 Точки растра и палитры......................................61 Цвет фона и вычерчивания....................................63 Управление цветом на CGA....................................63 Низкое разрешение CGA.......................................64 Высокое разрешение CGA......................................67 Функции управления палитрой для CGA.........................68 Управление цветом на EGA и VGA..............................68 Обработка ошибок в графическом режиме.......................70 Запрос состояния............................................74 Г Л А В А 9................................................79 ЗАМЕЧАНИЯ ДЛЯ ПРОГРАММИСТОВ, РАБОТАЮЩИХ НА ТУРБО ПАСКАЛЕ....79 Структура программы.........................................81 Пример......................................................84 Сравнение базовых элементов.................................86 В ы в о д...................................................86 Т и п ы д а н н ы х.......................................90 Операции....................................................94 Ввод........................................................98 - 3,4 - Блок операторов............................................101 Выполнение по условию.......................................102 Циклы (итерации)............................................109 Цикл while (пока)...........................................109 Цикл do...while (выполнять...пока)..........................110 Цикл for (для)..............................................112 Подпрограммы................................................116 Прототипы функций..........................................120 Основной пример.............................................123 ОБЗОР СТРУКТУР ДАННЫХ.....................................128 Pointers (Указатели)........................................128 Arrays (Массивы)............................................133 Strings (Строки)............................................136 Structures (Структуры)......................................143 Union (Объединение).........................................147 Выводы по программированию..................................150 Чувствительность к регистрам................................150 Приведение типов............................................151 Константы, переменные записи и инициализация................152 Типы констант...............................................153 Инициализация переменных....................................155 Переменные памяти...........................................156 Динамическое распределение памяти...........................157 Аргументы командной строки..................................161 Файлы ввода/вывода..........................................164 Общие ошибки Паскаль программистов при работе на Си.........169 Ошибка #1: Присваивание и сравнение.........................169 Ошибка #2: Забывание о передаче адреса......................171 (особенно, при использовании scanf).........................171 Ошибка #3: пропуск скобок при вызове функции................172 Ошибка #4: предупреждающие сообщения........................173 Ошибка #5: индексация в многомерных массивах................174 Ошибка #6: Забывание о различиях между символьными массивами и символьными указателями.........................176 Ошибка #7: забывание о том, что Си чувствителен к размеру букв (строчные-заглавные).........................178 Ошибка #8: пропуск точки с запятой в последнем операторе блока.............................................179 Г Л А В А 10...............................................181 ИНТЕРФЕЙС МЕЖДУ ТУРБО СИ И ТУРБО ПРОЛОГОМ...................182 В этой главе................................................184 Компоновка Турбо Си и Турбо Пролога: обзор..................185 Пример 1: Сложение двух целых чисел.........................191 Исходный файл Турбо Си: CSUM.C..............................191 - 5,6 - Компиляция CSUM.C в CSUM.OBJ.............................192 Исходный файл Турбо Пролога : PROSUM.PRO....................194 Компиляция PROSUM.PRO в PROSUM.OBJ..........................194 Компоновка CSUM.OBJ и PROSUM.OBJ............................196 Инициализация Турбо Пролога:................................196 Главный модуль Турбо Пролога:...............................196 Набор модулей:..............................................197 Модуль таблицы идентификаторов:.............................197 Имя выходного файла.........................................197 Библиотеки:.................................................198 Пример 2: Использование библиотеки математических функций...200 Компиляция CSUM1.C и FACTRL.C в .OBJ........................202 И с х о д н ы й ф а й л Турбо Пролога: FACTSUM.PRO.........203 Компиляция FACTSUM.PRO в FACTSUM.OBJ........................207 Компоновка СSUM1.OBJ, FACTRL.OBJ и FACTSUM.OBJ..............207 Пример 3: Шаблоны аргументов и распределение памяти.........209 Вызов Турбо Пролога из Турбо Си.............................214 Списки и функторы...........................................220 Компиляция DUBLIST.C........................................224 Пример 4. Рисование 3-х мерных диаграмм.....................225 Компиляция CBAR.C...........................................226 Программа Турбо Пролога: PBAR.PRO...........................226 Компиляция PBAR.PRO в PBAR.OBJ..............................228 Компоновка PBAR.OBJ с модулем CBAR.OBJ......................228 Р е з ю м е.................................................230 ГЛАВА 11....................................................231 РУКОВОДСТВО ПО ЯЗЫКУ ТУРБО СИ...........................231 В этой части................................................233 Комментарии (K&R 2.1).......................................233 Идентификаторы (K&R 2.2)....................................235 Ключевые слова (K&R 2.3)...................................236 Константы (K&R 2.4).........................................238 Целые константы (K&R 2.4.1).................................238 Символьные константы (K&R 2.4.3)............................241 Константы с плавающей точкой (K&R 2.4.4)....................244 Строки (K&R 2.5)............................................245 Зависимость от аппаратных средств (K&R 2.6).................247 Преобразования (K&R 6)......................................249 Char, int и enum (K&R 6.1)..................................249 Указатели (K&R 6.4).........................................250 Арифметические преобразования (K&R 6.6).....................251 Операторы (K&R раздел 7.2).................................254 Спецификаторы типов и модификаторы (K&R 8.2)................255 Тип enum....................................................256 - 7,8 - Тип void....................................................257 Модификатор signed..........................................259 Модификатор const...........................................260 Модификатор volatile........................................262 Модификаторы cdecl и pascal.................................264 pascal......................................................265 cdecl.......................................................266 Модификаторы near, far и huge...............................267 Структуры и объединения (K&R раздел 8.5)..................270 Выравнивание слов...........................................270 Поля бит....................................................271 Операторы (K&R 9)...........................................273 Определения внешних функций (K&R 10.1)......................273 Модификаторы типа функции (K&R 10.1.1)......................274 Модификатор функции pascal..................................274 Модификатор функции cdecl...................................276 Модификатор функции interrupt...............................278 Модификаторы функций near, far и huge.......................279 Прототипы функций (K&R 10.1.2)..............................280 Правила видимости (K&R 11)..................................290 Команды управления трансляцией (K&R 12).....................292 Замена лексем (K&R 12.1)....................................292 Включение файла (K&R 12.2)..................................295 Условная компиляция (K&R 12.3)..............................297 Управление строками (K&R 12.4)..............................299 Директива error (ANSI Си 3.8.5).............................299 Директива pragma (ANSI Си 3.8.6)............................301 #pragma inline..............................................301 Директива null (ANSI Си 3.7)................................305 Встроенные макроимена (ANSI Си 3.8.8).......................305 Встроенные макросы Турбо Си.................................308 Анахронизмы (K&R 17)........................................310 Г Л А В А 12...............................................311 УГЛУБЛЕННЫЙ КУРС ПО ТУРБО СИ................................311 Модели памяти...............................................312 Регистры микропроцессора 8086...............................312 Регистры общего назначения..................................316 Сегментные регистры.........................................317 Регистры специального назначения............................317 Сегментация памяти..........................................319 Вычисление адреса...........................................320 Указатели типа NEAR, FAR И HUGE.............................322 Указатели типа NEAR.........................................323 Указатели типа FAR..........................................323 - 9,10 - Указатели типа HUGE.........................................326 Шесть моделей памяти в Турбо Си.............................329 Крохотная...................................................329 Малая.......................................................329 Средняя.....................................................330 Компактная..................................................330 Большая.....................................................331 Огромная....................................................331
Порядок программирования смешанных моделей памяти: модификация типа адресации..................................345 Объявление функций как NEAR или FAR.........................348 Объявление указателей как NEAR, FAR или HUGE................351 Способ указания на данный сегмент: Offset адрес (смещение)..355 Построение простых операторов объявления....................356 Использование библиотечных файлов...........................362 Компоновка смешанных модулей................................365 Программирование с совмещением языков.......................369 Последовательности передачи параметров типа Си и Паскаль....369 Последовательность передачи параметров типа Си..............370 Последовательность передачи параметров типа Паскаль.........372 Интерфейс с языком ассемблера...............................378 Порядок вызова ассемблера из Турбо Си.......................378 Определение констант данных и переменных....................384 Определение глобальных и внешних идентификаторов............385 Порядок вызова Турбо Си из .ASM.............................388 Указатели на функции........................................388 Указатели на данные.........................................389 Создание подпрограмм на ассемблере..........................392 Передача параметров.........................................394 Управление возвращаемыми величинами.........................395 Соглашения по регистрам.....................................403 Вызов Си-функций из .ASM подпрограмм........................405 ПРОГРАММИРОВАНИЕ НА НИЗКОМ УРОВНЕ: псевдопеременные, встроенный ассемблер и функции прерывания.409 Псевдопеременные............................................409 Использование встроенного ассемблера........................416 Команды.....................................................423 Строковые команды...........................................426 Префиксы повторения.........................................427 Команды перехода............................................427 Директивы ассемблера........................................429 Указатели встроенного ассемблера к данным и функциям........429 Встроенный ассемблер и регистровые переменные...............431 - 11,12 - смещения и замещения размеров операндов.....................433 Использование элементов Си-структуры........................434 Использование команд перехода и меток.......................437 Функции прерываний..........................................438 Примеры программирования на низком уровне...................441 Использование библиотек программ для работы с плавающей точкой.....................................................445 Эмуляция микросхемы 8087/80287..............................447 сопроцессора 8087/80287.....................................451 Если вы не используете плавающую точку......................453 Переменная среды 87.........................................457 Регистры и 8087/80287.......................................460 Использование matherr с плавающей точкой....................461 Предостережения и советы....................................462 Как Турбо Си использует RAM.................................462 Нужно ли вам использовать Паскаль-соглашения?...............463 Заключение..................................................464 Г Л А В А 8 ------------- ВИДЕОФУНКЦИИ ТУРБО СИ ----------------------------------------------------------------- Турбо Си предлагает полную библиотеку графических функций, при помощи которой вы можете изображать на экране цветные и черно -белые графики и диаграммы. В этой главе... ----------------------------------------------------------------- В этой главе мы сначала немного обсудим видеорежимы и окна. Потом мы раскажем о программировании в текстовых, а затем и гра- фических режимах. Новые видеофункции Турбо Си основаны на заимствовании прог- рамм из Турбо Паскаля. Если вы еще не знакомы с управлением на вашем ПК режимами экрана, с созданием текстовых и графических окон и управлением ими, то уделите несколько минут на знакомство - 13,14 - с этой темой. Несколько слов о видеорежимах. ----------------------------------------------------------------- У вашего ПК имеется устройство типа видеоадаптера. Это может быть монохромный адаптер дисплея - Monochrome Display Adapter (MDA) для вывода на дисплей только текста, или это может быть адаптер с возможностью вывода графики - Color Graphics Adapter (CGA), Enhanced Graphics Adapter (EGA) или Hercules Monochrome Graphics Adapter. Каждый адаптер можно устанавливать в различные режимы: 80- и 40-символьный (для текстов), с разной разрешающей способностью (для графики), цветной или черно-белый. Режимы экрана определяются при вводе в программу одной из соответствующих функций (textmode, initgraph или setgraphmode). # При текстовом режиме(text mode) экран делится на ячейки (80 или 40 колонок в ширину и 25 строк в высоту). Каждая ячейка состоит из атрибута и символа. Символ - это выводимый на экран ASCII символ, а атрибут показывает, как символ представлен на дисплее (цвет, интенсивность и т.д.). Турбо Си обеспечивает пол- ный набор подпрограмм для управления текстовым экраном, непос- редственного вывода текста на экран и для управления атрибутами - 15,16 - ячейки. # При графическом режиме(graphics mode) экран вашего ПК де- лится на точки растра (пиксели); каждая точка растра выглядит од- ной точкой на экране. Количество точек (разрешающая способность) зависит от типа видеоадаптера, установленного на вашей системе, и текущего режима адаптера. Вы можете использовать функции Турбо Си из новой графической библиотеки для изображения графики на экра- не: можете рисовать линии и графики, выделять геометрические фи- гуры и управлять цветом каждой точки растра. В текстовом режиме верхний левый угол экрана - это позиция (1, 1), координата x возрастает слева на право, а координата y - сверху вниз. В графическом режиме верхний левый угол - это пози- ция (0,0), а координаты направлены таким же образом. Несколько слов о текстовых и графических окнах ----------------------------------------------------------------- Турбо Си обеспечивает функции для создания и управления ок- нами на экране в текстовом и в графическом режимах. Если вы еще не знакомы с окнами и областями просмотра, то прочитайте предла- гаемый краткий обзор. Новые функции Турбо Си для управления текс- товыми и графическими окнами описаны ниже в этой главе в разделах "Программирование в текстовом режиме" и "Программирование в гра- фическом режиме". Что такое окно? ----------------------------------------------------------------- Окно - это прямоугольная область, заданная на видеоэкране вашего ПК при работе в текстовом режиме. Когда ваша программа производит вывод на экран, вывод ограничен активным окном. Ос- тальная часть экрана (за пределами окна) остается неизменной. По умолчанию окно распространяется на весь экран. Ваша прог- - 17,18 - рамма может изменить это состояние и сделать окно меньше (с по- мощью вызова функции window). Эта функция определяет расположение окна в координатах экрана. Что такое viewport (графическое окно)? ----------------------------------------------------------------- В графическом режиме вы также можете определить прямоуголь- ный участок на экране ПК - это область представления графической информации или графическое окно. Когда ваша программа выдает на экран рисунки и т.п., экраном фактически является графическое ок- но. Остальная часть экрана (за пределами окна) остается без изме- нений. Графическое окно задается в координатах экрана функцией setviewport.
Координаты ----------------------------------------------------------------- За исключением функций задания текстового и графического ок- на, все координаты для функций текстового и графического режима задаются в относительных единицах (по отношению к окнам), а не в абсолютных координатах экрана. Верхний левый угол окна при текс- товом режиме принимается за координату (1,1), верхний левый угол графического окна при графическом режиме - за координату (0,0). - 19,20 - Программирование в текстовых режимах ----------------------------------------------------------------- В этом разделе мы даем краткое описание функций текстового режима; более подробную информацию смотрите в главе 2 Справочного руководства по Турбо Си. Пакет функций Турбо Си для прямого ввода/вывода с консоли (cprintf, cputs и т.д.) был расширен и дополнен с целью совер- шенствования вывода текста и для обеспечения управления окнами, курсором и атрибутами. Все эти функции являются частью стандарт- ной библиотеки Турбо Си; они представлены прототипами в заголо- вочном файле CONIO.H. Функции ввода/вывода с консоли ----------------------------------------------------------------- Функции работы с текстами выполняются в любом (из пяти воз- можных) текстовом режиме; выбор режима зависит от типа видеоадап- тера и монитора. Текстовый режим определяется вызовом textmode. Использование этой функции описано далее в этой главе, а также в разделе textmode главы 2 Справочного руководства. Функции текстового режима представлены четырьмя отдельными группами: - вывода текста и манипуляции с ним; - управления окнами и режимами; - управления атрибутами; - запроса состояния. В следующих разделах мы рассматриваем эти четыре группы функций текстового режима. Вывод текста и манипуляция с ним -------------------------------- - 21,22 - Ниже представлен краткий перечень соответствующих функций: ============================================================ Запись и чтение текста: cprintf посылает форматированный вывод на экран cputs посылает строку на экран putch посылает один символ на экран getche читает символ и отображает его на экране Манипулирование текстом (и курсором) на экране: clrscr очищает текстовое окно clreol очищает строку с позиции курсора delline удаляет строку, на которой находится курсор gotoxy устанавливает курсор insline вставляет пустую строку под строкой, содержа- щей курсор movetext копирует текст с одного места экрана на другое Пересылка блоков текста в (из) память(и): gettext копирует блок текста с экрана в память puttext копирует блок текста из памяти на экран. ============================================================ Программы вывода текста на экран предусматривают использова- ние по умолчанию полноэкранных окон, так что вы можете писать, читать и манипулировать текстом без какой-либо предварительной установки режима. Текст пишется сразу на экран функциями консоль- ного вывода cprintf, cputs и putch, а для отображения вводимого символа (эхо) используется функция getche. Текст укладывается внутрь окна следующим образом: если текст заходит за правую гра- ницу окна, то соответствующий отрезок текста переносится на нача- ло следующей строки. Когда текст находится на экране, вы можете: стереть его (т.е. очистить активное окно) - clrscr, уничтожить часть строки - clreol, удалить строку целиком - delline, вставить пустую строку - insline. Последние три функции действуют в соответствии с поло- жением курсора; вы можете передвигать курсор к определенному мес- ту при помощи функции gotoxy. Вы также можете скопировать целый блок текста из одного прямоугольного участка окна в другой коман- дой movetext. Перенос прямоугольного блока текста с экрана в память вызы- вается функцией gettext, а обратная пересылка на экран (в любое - 23,24 - желаемое место) выполняется функцией puttext. Управление режимами и окнами ---------------------------- Вот две функции управления режимом и окном: ============================================================ textmode установка экрана в текстовый режим window задание окна в текстовом режиме ============================================================ Экран может быть установлен в один из нескольких текстовых режимов с помощью textmode (ограничения связаны только с типами адаптера и монитора вашей системы). Эта функция устанавливает полноэкранное текстовое окно в заданный режим и очищает его. Когда установлен текстовый режим, вы можете работать с пол- ным экраном или с его частью - окном, которое ограничивает прог- раммный вывод. Окно создается вызывом функции window, определяю- щей, какую область экрана займет окно. - 25,26 - Управление атрибутами --------------------- Здесь дается краткий перечень функций, управляющих атрибута- ми в текстовом режиме: ============================================================ Установка цвета символов и цвета фона: textcolor устанавливает цвет текста (атрибут) textbackground устанавливает цвет фона (атрибут) textattr устанавливает цвет символа и цвет фона (атри- буты) одновременно. Изменение интенсивности: highvideo устанавливает повышенную интенсивность текста lowvideo устанавливает пониженную интенсивность текста normvideo устанавливает нормальную интенсивность текста ============================================================ Функции управления атрибутами устанавливают текущий атрибут, который задается 8-битным значением: четыре младших бита управля-
ют цветом текста, следующие три определяют цвет фона, а самый старший бит является признаком мигания. После установки дальнейший текст будет выводиться в соот- ветствии с текущими атрибутами. С помощью функций управления ат- рибутами вы можете устанавливать цвет символа и цвет фона раз- дельно (textcolor и textbackground) или одновременно вызовом textattr с заданной комбинацией цветов. Также может быть указан признак мигания символа. Большинство цветных мониторов в цветовых режимах правильно передают цвета. Нецветные мониторы могут пере- водить цветное изображение в нецветное или создавать различные визуальные эффекты: жирный шрифт, подчеркивание, инверсное отоб- ражение и т.д. Вы можете поменять изображение высокой интенсивности на низ- кую вызовом lowvideo (выключает бит высокой интенсивности для символов) или, наоборот, поменять изображение низкой интенсивнос- ти на высокую вызовом highvideo (включает бит высокой интенсив- ности). Оперируя интенсивностью символа, вы можете вернуться к исходному уровню с помощью normvideo. - 27,28 - Запрос состояния ---------------- Здесь приводится краткий перечень функций запроса состояния ============================================================ gettextinfo заполняет структуру text_info информацией о текущем текстовом окне wherex сообщает x координату ячейки с курсором wherey сообщает y координату ячейки с курсором ============================================================ Среди функций Турбо Си для ввода/вывода с консоли имеется несколько функций для "запроса состояния". С помощью этих функций вы можете скорректировать информацию о текстовом окне и положении курсора в нем. Функция gettextinfo записывает в структуру text_info (опре- деленную в conio.h) информацию о текстовом окне, а именно: - текущий видеорежим; - положение окна в абсолютных координатах экрана; - размеры окна; - текущие цвета текста и фона; - текущую позицию курсора. Иногда бывает необходимой лишь часть этой информации. Так, вместо корректировки всей информации о текстовом окне, вы можете просто узнать положение курсора (относительно окна) при помощи wherex и wherey. - 29,30 - Текстовые окна ----------------------------------------------------------------- По умолчанию текстовое окно занимает весь экран. Вы можете изменить его, сократив вызовом функции window. Текстовые окна мо- гут включать до 25 строк (максимальное количество строк экрана для всех текстовых режимов) и до 40 или 80 колонок (в зависимости от установленного текстового режима). Отсчет координат текстового окна Турбо Си ведется от верхне- го левого угла. Координаты текстового окна в левом верхнем углу - (1,1); координаты правого нижнего угла (при 80-символьном полно- экранном текстовом окне) - (80,25). Пример ------ Предположим ваша 100% совместимая с IBM PC система установ- лена в 80-колоночный текстовый режим и вы захотели создать окно с верхним левым углом (10,8) и нижним правым углом (50,21). Для этого необходимо вызвать функцию window : window(10, 8, 50, 21); . Следующая таблица представляет список этих символических констант и соответствующих им числовых значений. Запомните, что только первые восемь цветов доступны для выбора фона (background), в то время как все 16 доступны для выбора цвета символов (foreground). ----------------------------------------------------------------- Символическая Численное Цвет символа Соответствующий константа значение или цвет фона? цвет ----------------------------------------------------------------- BLACK 0 обоих ЧЕРНЫЙ BLUE 1 обоих СИНИЙ GREEN 2 обоих ЗЕЛЕНЫЙ CYAN 3 обоих ГОЛУБОЙ
RED 4 обоих КРАСНЫЙ MAGENTA 5 обоих МАЛИНОВЫЙ BROWN 6 обоих КОРИЧНЕВЫЙ LIGHTGRAY 7 обоих СВЕТЛОСЕРЫЙ DARKGRAY 8 Символа ТЕМНОСЕРЫЙ LIGHTBLUE 9 Символа СВЕТЛОСИНИЙ LIGHTGREEN 10 Символа СВЕТЛОЗЕЛЕНЫЙ LIGHTCYAN 11 Символа СВЕТЛОГОЛУБОЙ LIGHTRED 12 Символа СВЕТЛОКРАСНЫЙ LIGHMAGENTA 13 Символа СВЕТЛОМАЛИНОВЫЙ YELLOW 14 Символа ЖЕЛТЫЙ WHITE 15 Символа БЕЛЫЙ BLINK 128 Символа МЕРЦАЮЩИЙ - 35,36 - ----------------------------------------------------------------- Вы можете добавить символическую константу BLINK (числовое значение 128) для аргумента цвета символа, если вы хотите чтобы символ мерцал. Высокоэфективный вывод: переменная directvideo ----------------------------------------------------------------- Консольный пакет ввода/вывода Турбо Си включает переменную, называемую directvideo. Эта переменная направляет ваш программный вывод прямо в видеопамять (directvideo = 1) или переключает его на использование BIOS - базовой системы ввода/вывода (directvideo = 0). Предопределенное значение directvideo = 1 (консольный вывод направляется прямо в видеопамять). В общем случае использование непосредственного вывода в видеопамять дает высокую эффективность (быстрый вывод), но для этого требуется 100% совместимость с IBM PC вашего компьютера: исполнение адаптера дисплея должно быть идентично IBM. Установка directvideo = 0 предназначена для любой машины BIOS совместимой с IBM, но текст будет выводиться на кон- соль медленее. - 37,38 - Программирование в графическом режиме ----------------------------------------------------------------- В этом разделе мы приводим краткое описание функций, которые вы можете использовать в графическом режиме. За более полной ин- формацией об этих функциях обратитесь к главе 2 Справочного руко- водства. Турбо Си предоставляет отдельную библиотеку из более чем 70 графических функций, от высокоуровневых (таких как setviewoport, bar3d и drawpoly) до побитно-ориентированных функций (таких как getimage и putimage). Эта графическая библиотека поддерживает множество разнообразных видов линий и закрашивания, а также нес- колько текстовых шрифтов, которые вы можете увеличивать, выравни- вать и ориентировать в горизонтальном или вертикальном положени- ях. Данные функции хранятся в новой библиотеке GRAPHICS.LIB, а их прототипы - в заголовочном файле GRAPHICS.H. Кроме этих двух файлов, графический пакет включает в себя драйверы графических устройств (файлы *.BGI) и штриховые (векторные) символьные шрифты (файлы *.CHR); эти дополнительные файлы мы обсудим в следующих разделах. Для использования любых графических функций вам необходимо: # При использовании TC.EXE переключить библиотеку Options/Linker/Graphics в положение On. Сборщик автомати- чески подключит графическую библиотеку к вашей программе. # При использования TC.EXE вы должны включить GRAPHICS.LIB в командную строку. Например, если ваша программа MYPROG.C . Функции графической библиотеки ----------------------------------------------------------------- Графические функции Турбо Си делятся на семь категорий: - управляющие графической системой; - рисование и закрашивание; - манипуляция экранами и графическими окнами; - вывод текста; - управление цветом; - обработка ошибок; - запрос состояния. Управление графической системой ------------------------------- Здесь приводится краткий перечень функций, управляющих гра- фической системой: - 41,42 - ================================================================= closegraph выключает графическую систему detectgraph проверяет аппаратуру и определяет необходимый графический драйвер и режим использования graphdefaults заменяет все переменные графической системы на их значения по умолчанию _graphfreemem освобождает графическую память; используется для включения вашей собственной программы _graphgetmem распределяет память, используемую графической системой; используется для включения вашей собс- твенной программы getgraphmode возвращает текущий графический режим getmoderange возвращает минимальный и максимальный режимы, являющиеся корректными для заданного устройства initgraph инициирует графическую систему и переводит аппа- ратуру в графический режим installuserdriver загружает дополнительный драйвер устройства в таблицу драйверов устройств BGI installuserfont загружает файл векторного шрифта, недоступного для графических подпрограмм registerbgidriver регистрирует скомпонованный или загруженный пользователем файл-драйвер для подключения его на этапе сборки restorecrtmode восстанавливает первоначальный (до использования initgraph) режим экрана setgraphbufsize задает размер внутренного графического буфера setgraphmode выбирает заданный графический режим, очищает эк- ран и восстанавливает все значения по умолчанию ================================================================= Графический пакет Турбо Си предоставляет графические драйве- ры следующих графических адаптеров (и полностью с ними совмести- мых): # Цветной графический адаптер (CGA) # Многцветный графический адаптер (MCGA) # Улучшенный графический адаптер (EGA)
# Видеографический адаптер (VGA) # Графический адаптер Hercules # 400-строчный графический адаптер фирмы AT&T # Графический адаптер 3270 PC # Графический адаптер IBM 8514 - 43,44 - Для запуска графической системы вам сначала нужно вызвать функцию initgraph. Эта функция загружает графический драйвер и переводит систему в графический режим. Вы можете приказать initgraph использовать определенный графический драйвер и режим, или же потребовать автоматического определения подключенного ви- деоадаптера и загрузки соответствующего драйвера. Если вы требуе- те от функции initgraph автоопределения, то для определения гра- фического драйвера и режима она вызывает detectgraph. Если вы приказываете initgraph использовать определенный графический драйвер и режим, то вы должны быть уверены в том, что ваша систе- ма поддерживает этот режим, иначе последствия непредсказуемы. Загрузив графический драйвер, вы можете получить его имя при помощи функции getdrivename, а число поддерживаемых им режимов при помощи функции getmaxmode. Функция getgraphmode сообщит вам о текущем графическом режиме. Узнав номер режима, вы можете полу- чить его название при помощи getmodename. Вы можете переключать графические режимы функцией setgraphmode, а также вернуть видео- режим в изначальное состояние (то, которое было до инициализации графики) функцией restorecrtmode. Эта функция возвращает экран в текстовый режим, но графическую систему не выключает (шрифты и драйверы остаются в памяти). graphdefaults устанавливает все графические параметры (раз- мер графического окна, цвета рисования и закрашивания, шаблоны и т.д.) в их значения по умолчанию. Функции installuserdriver и installuserfont позволяют допол- нять BGI дополнительными драйверами и шрифтами. И, наконец, когда вы прекращаете использовать графику, вызо- вите closegraph для выключения графической системы. closegraph выгрузит из памяти драйвер и восстановит первоначальный видеоре- жим (при помощи restorecrtmode). Более подробное обсуждение. --------------------------- Предыдущее рассмотрение заключало только обзор действия initgraph. В следующих параграфах мы более подробно опишем пове- дение initgraph, _graphgetmem и _graphfreemem. - 45,46 - Обычно функция initgraph загружает графический драйвер пос- редством выделения памяти для этого драйвера с последующей заг- рузкой с диска соответствующего файла .BGI. В качестве альтерна- тивы этой схеме динамической загрузки вы можете скомпоновать файл графического драйвера (или несколько файлов) непосредственно с файлом вашей программы. Для этого, во-первых, вы должны преобра- зовать файл .BGI в файл .OBJ (с помощью утилититы BGIOBJ), а за- тем включить в текст вашей программы вызов registerbgidriver (пе- ред вызовом initgraph) для регистрации этого графического драйвера. При компоновке программы вы должны будете скомпоновать файлы .OBJ для всех зарегистрированных драйверов. После определения (при помощи detectgraph) того, какой драй- вер должен использоваться, initgraph проверяет, зарегистрирован ли этот драйвер. Если да, то initgraph использует зарегистриро- ванный драйвер непосредственно из памяти. В противном случае initgraph выделяет память для драйвера и загружает с диска файл . BGI. Замечание: использование registerbgidriver является углуб- ленным методом программирования и, поэтому, не рекомендуется для начинающих программистов. Более детально эта функция описана в Приложении D Справочного Руководства. В процессе работы графической системы может понадобиться вы- делить память для драйверов, шрифтов и внутренних буферов. В слу- чае необходимости, для распределения памяти система вызывает _graphgetmem, а для освобождения ее - _graphfreemem. По умолчанию эти функции просто вызывают malloc и free соответственно. Вы можете изменить такое поведение графической системы путем определения собственных функций _graphgetmem и _graphfreemem. Сделав это, вы сможете сами управлять распределением графической памяти. Вы должны, однако, использовать такие же имена для своих версий функций по распределению памяти: они заменяют обычные функции с теми же именами в стандартных библиотеках Турбо Си. Замечание. Если вы определили собственную функцию _graphgetmem или _graphfreemem, то может появиться сообщение "Warning: duplicate symbols" вследствие того, что функции с таки- ми же именами находятся в графической библиотеке. Не обращайте внимания на это сообщение. - 47,48 - Рисование и закрашивание ------------------------ Ниже приведено краткое описание рисующих и закрашивающих функций: ================================================================= Рисование: arc вычерчивает круговую дугу circle вычерчивает окружность drawpoly вычерчивает многоугольник ellipse вычерчивает эллиптическую дугу getarccoords возвращает координаты последнего обращения к arc или ellipse getaspectratio возвращает коэффициент сжатия текущего графиче- ского режима getlinesettings возвращает текущие стиль, шаблон и толщину ли- нии line вычерчивает линию от (x0, y0) до (x1, y1) linerel вычерчивает линию от текущей позиции (ТП) до точки на заданном от нее расстоянии lineto вычерчивает линию от ТП до (x,y) moveto устанавливает ТП в (x,y) moverel перемещает ТП на заданное расстояние rectangle вычерчивает прямоугольник setaspectratio изменяет коэффициент сжатия setlinestyle устанавливает текущую толщину и стиль линии Закрашивание: bar вычерчивает и закрашивает прямоугольник bar3d вычерчивает и закрашивает параллелепипед fillellipse вычерчивает и закрашивает эллипс fillpoly вычерчивает и закрашивает многоугольник floodfill "лавинно" заполняет ограниченную область getfillpattern возвращает описанный пользователем шаблон закрашивания getfillsettings возвращает информацию о текущем шаблоне и цвете закрашивания pieslice вычерчивает и закрашивает круговой сектор sector вычерчивает и закрашивает эллиптический сектор setfillpattern устанавливает заданный пользователем шаблон закрашивания setfillstyle устанавливает шаблон и цвет - 49,50 - ================================================================= С помощью функций Турбо Си для вычерчивания и раскрашивания вы можете рисовать цветные линии, дуги, круги, эллипсы, прямоу- гольники и параллелепипеды, сектора, многоугольники, а также "правильные" и "неправильные" формы, базирующиеся на комбинациях вышеперечисленных фигур. Вы можете закрашивать любые ограниченные формы (или любые области вокруг этих форм) с помощью одного из 11 предопределенных шаблонов, либо с помощью вашего собственного, вами описанного шаблона. Вы можете также управлять толщиной и стилем вычерчиваемой линии и месторасположением ТП. Вы вычерчиваете линии и незакрашенные фигуры с помощью функ- ций arc, circle, drawpoly, ellipse, line, linerel, lineto и rectangle. Вы можете закрашивать эти фигуры с помощью floodfill, или осуществлять вычерчивание и закрашивание за один шаг с по- мощью bar, bar3d, fillellipse, fillpoly, pieslice или sector. Используя setlinestyle, вы можете задать, будет ли вычерчи- ваемая линия (включая линии, ограничивающие фигуры) толстой или тонкой, а также будет ли эта линия сплошной, точечной или еще ка- кой в соответствии с заданным вами шаблоном. С помощью setfillstyle вы можете выбрать предопределенный шаблон закрашива- ния, а с помощью setfillpattern - описать свой собственный шаб- лон. С помощью moveto вы можете передвигать текущую позицию в за- данную точку, а с помощью moverel - на заданное расстояние. Для определения текущих стиля и толщины линии вы можете об- ратиться к getlinesettings. Для получения информации о текущих шаблоне и цвете закрашивания вы можете использовать getfillsettings. Для получения пользовательского шаблона закраши- вания используйте getfillpattern. С помощью getaspectratio вы можете получить коээфициент сжа- тия (масштабирующий коэффициент, используемый графической систе- мой для получения правильных окружностей), а с помощью getarccoords - координаты последней нарисованной дуги или элипса. Если окружности рисуются с искажениями, используйте setaspectratio для корректировки изображения. - 51,52 -
Манипуляция экраном и графическим окном. ---------------------------------------- Ниже приведено краткое описание функций, выполняющих манипу- ляции над графическим образом. ================================================================= Манипуляция экраном: cleardevice очищает экран (активную страницу) setactivepage устанавливает активную страницу для графическо- го вывода setvisualpage устанавливает номер видимой графической страни- цы Манипуляция графическим окном: clearviewport очищает текущее окно getviewsettings возвращает информацию о текущем окне setviewport устанавливает окно для текущего графического вы- вода Манипуляция образом: getimage записывает в память битовый образ заданной об- ласти imagesize возвращает число байт, требуемых для сохранения прямоугольной области экрана putimage выводит ранее сохраненный битовый образ на эк- ран Манипуляция точками растра: getpixel возвращает цвет точки в (x,y) putpixel вычерчивает точку в (x,y) ================================================================= Помимо вычерчивания и закрашивания графическая библиотека предоставляет несколько функций для манипуляции экраном, графи- ческими окнами, образами и точками. Вы можете очистить весь экран одним махом при помощи cleardevice; данная функция очищает весь экран и переводит текущую позицию к началу окна, но оставляет без изменений все остальные параметры графического окна (стили линий и закрашивания, текстовый стиль, палитру, параметры окна и т. д.). - 53,54 - В зависимости от вашего графического адаптера, в вашей сис- теме имеется от одного до восьми буферов для страниц экрана, представляющих собой области памяти, в которых хранятся "точка за точкой" полноэкранные образы. С помощью setactivepage и setvisualpage вы можете определять, какая страница экрана являет- ся активной (т.е. куда графические функции направляют свой вы- вод), и какая страница является видимой (т.е. отображается на эк- ране). С помощью функции setviewport вы можете задать графическое окно ("фактический" прямоугольный "экран"). Вы задаете позицию окна в абсолютных координатах экрана и определяете необходимость буферизации этого окна. Для очистки окна вы можете использовать clearviewport. С помощью getviewsettings вы можете определить ко- ординаты окна в абсолютных координатах экрана, а также статус бу- феризации. С помощью getimage вы можете получить часть экранного обра- за; с помощью imagesize - вычислить число байт, требуемых для сохранения этого образа в памяти; а с помощью putimage - восста- новить этот образ в любом месте экрана. Координаты для всех функций вывода (рисования, закрашивания, вывода текста и т.д.) связаны с данным окном. Кроме того, с помощью getpixel (возвращающей цвет заданной точки) и putpixel (вычерчивающей заданную точку заданным цветом) вы можете манипулировать цветом отдельных точек растра. - 55,56 - Вывод текста в графическом режиме --------------------------------- Ниже приведено краткое описание функций, осуществляющих вы- вод текста в графическом режиме. ================================================================= gettextsettings возвращает текущие текстовые шрифт, направле- ние, размер и выравнивание. outtext выводит строку в текущую позицию (ТП) экрана outtextxy выводит строку в заданную позицию экрана registerbgifont регистрирует скомпонованный или загруженный пользователем шрифт settextjustify устанавливает величину выравнивания текста, ис- пользуемую функциями outtext и outtextxy settextstyle устанавливает текущие текстовые шрифт, стиль и коэффициент увеличения знаков setusercharsize устанавливает отношение ширины к высоте для штриховых (векторных) шрифтов textheight возвращает высоту строки в точках растра textwidth возвращает ширину строки в точках растра ================================================================= Графическая библиотека включает растровый шрифт 8x8 и нес- колько штриховых (векторных) шрифтов, используемых для вывода текста в графическом режиме. # В растровом шрифте каждый символ описывается матрицей то- чек растра # В штриховом шрифте каждый символ описывается набором век- торов, сообщающих графической системе, как вычерчивать этот символ. Преимущество в использовании штриховых шрифтов особенно про- является, когда вы начинаете вычерчивать большие символы. Т.к. штриховой шрифт задается векторами, то он сохраняет хорошее раз- решение и качество при его увеличении. С другой стороны, при уве- личении растрового шрифта, матрица умножается на масштабирующий коэффициент и при увеличении масштабирующего коэффициента разре- шение символов становится грубым. Для маленьких символов растро- вый шрифт может быть достаточно эффективным, но для увеличенных текстов вам лучше выбрать штриховой шрифт. - 57,58 - С помощью outtext и outtextxy вы можете выводить графический текст, а с помощью settextjustify - управлять выравниванием выво- димого текста (относительно ТП). С помощью settextstyle вы можете выбрать символьный шрифт, направление (горизонтальное или верти- кальное) и размер (масштаб). Для того чтобы получить текущие па- раметры текста, вызывается функция gettextsettings, которая запи- сывает в структуру textsettings текущие текстовый шрифт, выравнивание, увеличение и направление. Функция setusercharsize позволяет модифицировать ширину и высоту векторных шрифтов. Если буферизация "включена", весь вывод текстовых строк, вы- полняемый функциями outtext и outtextxy, будет буферизоваться в пределах графического окна. Если же буферизация "выключена", то эти функции будут исключать растровый шрифтовой вывод при выходе за край окна какой-либо части текстовой строки; вывод векторного шрифта будет усекаться по край окна. Функции textheight (которая вычисляет высоту строки в точках растра) и textwidth (которая вычисляет ширину строки в точках растра) можно использовать для определения размера на экране за- данной текстовой строки. Принятый по умолчанию растровый шрифт 8x8 встроен в графи- ческий пакет, поэтому во время работы программы он всегда досту- пен. Штриховые шрифты хранятся каждый в отдельном файле .CHR; они могут быть загружены во время выполнения программы или преобразо- ваны в файлы .OBJ (с помощью утилиты BGIOBJ) и скомпонованы с ва- шим файлом .EXE. Обычно функция settextstyle загружает шрифтовой файл пос- редством выделения памяти для шрифта и загрузки с диска соответс- твующего файла .CHR. В качестве альтернативы этой динамической схеме загрузки вы можете скомпоновать файл символьного шрифта (или несколько таких файлов) непосредственно в ваш исполняемый файл. Для этого, во-первых, вы преобразуете файл .CHR в файл .OBJ (с помощью утилиты BGIOBJ), а затем помещаете в текст вашей прог- раммы вызов функции registerbgifont (перед обращением к settextstyle) для регистрации символьного шрифта. При компоновке вашей программы вы должны скомпоновать файлы .OBJ для тех штрихо- вых шрифтов, которые вы зарегистрировали. Замечание: Использование registerbgifont является углублен- ным методом программирования, и поэтому не рекомендуется для на- - 59,60 - чинающих программистов. Более детально эта функция описана в При- ложении D Справочного руководства.
Управление цветом ------------------ Ниже приведено краткое описание функций управления цветом. ================================================================= Получение информации о цвете: getbkcolor возвращает текущий цвет фона getcolor возвращает текущий цвет вычерчивания getdefaultpalette возвращает структуру, определяющую палитру getmaxcolor возвращает максимальное значение, которое можно присвоить точке растра в текущем графическом режиме getpalette возвращает текущую палитру и ее размер getpalettesize возвращает размер палитры (т.е справочной таб- лицы палитры) Установка одного или нескольких цветов: setallpalette изменяет все цвета палитры на заданные setbkcolor устанавливает текущий цвет фона setcolor устанавливает текущий цвет вычерчивания - 61,62 - setpalette изменяет один цвет палитры в соответствии с заданным аргументом ================================================================= Перед общим описанием работы этих функций мы сперва приведем базовые сведения о том, как в действительности создается цвет на вашем графическом экране. Точки растра и палитры ---------------------- Графический экран состоит из массива точек растра, каждая точка растра соответствует одной (цветной) точке на экране. Вели- чина (значение) такой точки растра непосредственно не задает цвет; она является индексом в таблице цветов, называемой палит- рой. Элемент палитры, соответствующий заданному значению точки растра, содержит информацию о цвете данной точки. Данная косвенная схема имеет ряд применений. Хотя аппаратура может быть способна отображать много цветов, в конкретный момент времени может отображаться только некоторое подмножество из этих цветов. Число цветов, способных отображаться одновременно, равно числу элементов в палитре (размеру палитры). К примеру, EGA аппа- ратура может производить 64 различных цвета, но только 16 из них могут отображаться одновременно; размер палитры для EGA=16. Размер палитры size определяет диапазон значений точек раст- ра: от 0 до (size-1). Функция getmaxcolor возвращает максимальное значение точки растра (size-1) для текущих графического адаптера и режима. При обсуждении графических функций Турбо Си мы часто исполь- зуем термин "цвет"; например, текущий цвет вычерчивания, цвет за- полнения и цвет точки растра. Фактически этот цвет является зна- чением точки растра, т. е. представляется индексом в палитре. Только палитра определяет действительный цвет на экране. Манипу- лируя палитрой, вы можете изменять действительно отображаемый на экране цвет, даже без изменений значения точки растра (цвета вы- черчивания, цвета заполнения и т.д.). - 63,64 - Цвет фона и вычерчивания ------------------------ Цвет фона всегда соответствует значению 0 точки растра. При очистке некоторой области в цвет фона, точки растра этой области просто устанавливаются в 0. Цвет вычерчивания соответствует значению, в которое устанав- ливаются точки растра при вычерчивании линий. Вы выбираете цвет вычерчивания с помощью функций setcolor(n), где n есть допустимое для текущей палитры значение точки растра. Управление цветом на CGA ------------------------ Из-за различий в графической аппаратуре действительное уп- равление цветом несколько различается на EGA и CGA, поэтому мы опишем эти адаптеры раздельно. Управление цветом на адаптере AT&T и, при низком разрешении, на MCGA аналогично управлению цветом на CGA. На CGA вы можете выбирать между отображением графики в низ- ком разрешении (320x200), что позволит вам использовать 4 цвета, и в высоком разрешении (640x200), при котором вы можете использо- вать 2 цвета. Низкое разрешение CGA. В режимах низкого разрешения вы можете выбирать между че- тырьмя предопределенными четырехцветными палитрами. В любой из этих палитр вы можете устанавливать только ее первый элемент; элементы 1, 2, 3 - фиксированы. Первый элемент палитры (цвет 0) является цветом фона. Этот цвет фона может принимать значение лю- бого из 16 доступных цветов (смотри таблицу). Выбор конкретной палитры осуществляется путем выбора опреде- ленного режима (CGAC0, CGAC1, CGAC2, CGAC3); эти режимы соответс- твуют цветовым палитрам от 0 до 3, что отображено в следующей таблице. Цвета вычерчивания для CGA и соответствующие им констан- ты определены в файле graphics.h. - 65,66 - ----------------------------------------------------------------- Номер палитры Цвет, соответствующий значению точки растра 1 2 3 ----------------------------------------------------------------- 0 CGA_LIGHTGREEN CGA_LIGHTRED CGA_YELLOW (светлозеленый) (светлокрасный) (желтый) 1 CGA_LIGHTCYAN CGA_LIGHTMAGENTA CGA_WHITE (светлоголубой) (светломалиновый) (белый) 2 CGA_GREEN CGA_RED CGA_BROWN (зеленый) (красный) (коричневый) 3 CGA_CYAN CGA_MAGENTA CGA_LIGHTGRAY (голубой) (малиновый) (светлосерый) ----------------------------------------------------------------- Для того, чтобы назначить один из этих цветов цветом рисова- ния для CGA, вызовите функцию setcolor, передав ей в качестве ар- гумента либо номер цвета, либо соответствующее символическое имя; например, если вы используете палитру 3 и хотите назначить голу- бой цветом рисования: setcolor(1); или setcolor(CGA_CYAN); Доступные на CGA цвета фона, описанные в GRAPHICS.H, приве- дены в следующей таблице. ---------------------------------------------- Числовое Символическая Соответствующий значение константа цвет ---------------------------------------------- 0 BLACK ЧЕРНЫЙ 1 BLUE СИНИЙ 2 GREEN ЗЕЛЕНЫЙ 3 CYAN ГОЛУБОЙ 4 RED КРАСНЫЙ 5 MAGENTA МАЛИНОВЫЙ 6 BROWN КОРИЧНЕВЫЙ 7 LIGHTGRAY СВЕТЛОСЕРЫЙ 8 DARKGRAY ТЕМНОСЕРЫЙ 9 LIGHTBLUE СВЕТЛОСИНИЙ 10 LIGHTGREEN СВЕТЛОЗЕЛЕНЫЙ 11 LIGHTCYAN СВЕТЛОГОЛУБОЙ 12 LIGHTRED СВЕТЛОКРАСНЫЙ 13 LIGHTMAGENTA СВЕТЛОМАЛИНОВЫЙ - 67,68 - 14 YELLOW ЖЕЛТЫЙ 15 WHITE БЕЛЫЙ --------------------------------------------- Для присваивания одного из этих цветов цвету фона на CGA ис- пользуйте setbkcolor(color), где color - один из элементов приве- денной выше таблицы. Отметим, что для CGA этот цвет не является значением точки растра (индексом палитры); он непосредственно за- дает действительный цвет. Высокое разрешение CGA В режиме высокого разрешения (640x200) CGA отображает два цвета: черный цвет фона и цветное изображение. Значение точек
растра может быть равным 0 и 1. Из-за особенностей самого CGA, аппаратура, в действительности, воспринимает цвет изображения как цвет фона, поэтому вы можете устанавливать его с помощью функции setbkcolor. (Удивительно, но факт.) Возможные цвета изображения приведены в предыдущей таблице. CGA использует определенный цвет для отображения всех точек раст- ра, значение которых равно 1. Режимы, работающие описанным образом: CGAHI, MCGAMED, MCGAHI, ATT400MED, ATT400HI. Функции управления палитрой для CGA Так как палитра CGA предопределена, вам не следует использо- вать функцию setallpalette. Кроме того, функцию setpalette(index, actual_color) вы можете использовать только при index=0. (Это яв- ляется альтернативным способом для установки цвета фона на CGA в actual_color.) Управление цветом на EGA и VGA ------------------------------ На EGA палитра содержит 16 элементов из 64 возможных цветов, причем каждый из них может быть установлен пользователем. - 69,70 - Вы можете получить текущую палитру с помощью функции getpalette, которая заполняет структуру размером с палитру (16) и массив действительных элементов палитры ("номера цветов для аппа- ратуры", хранящихся в палитре). С помощью setpalette вы можете изменять каждый цвет палитры поодиночке, а с помощью setallpalette - все сразу. По умолчанию палитра EGA соответствует 16 цветам CGA, приве- денным в предыдущей таблице, - черный соответствует элементу 0, голубой - 1, ..., белый - 15. В GRAPHICS.H описаны константы, со- ответствующие значениям аппаратурных цветов: EGA_BLACK, EGA_WHITE и т.д. Кроме того, вы можете получить эти величины с помощью getpalette. Поведение функции setbkcolor(color) различается на адаптерах CGA и EGA. На EGA setbkcolor переносит величину действительного цвета из color в нулевой элемент палитры. Как только цвета установлены, драйвер VGA начинает вести се- бя так же, как драйвер EGA. Отличие лишь в том, что у него более высокое разрешение (и соответственно более мелкие точки растра). Обработка ошибок в графическом режиме ------------------------------------- Вот функции для обработки ошибок в графическом режиме: ================================================================= grapherrormsg возвращает строку с сообщением об ошибке для за- данного кода ошибки graphresult возвращает код ошибки для последней графической операции, вызвавшей затруднения ================================================================= Если при вызове функции из графической библиотеки возникает ошибка (например, не найден шрифт, заданный settextstyle), то вы- рабатывается соответствующий внутренний код ошибки. Этот код мож- но узнать при помощи вызова функции graphresult. Определены сле- дующие коды ошибок: - 71,72 - ----------------------------------------------------------------- код символическая соответствующая строка с сообщением ошибки константа об ошибке ----------------------------------------------------------------- 0 grOK No error (нет ошибки) -1 grNoInitGraph (BGI) graphics not installed (графика не запущена, используйте initgraph) -2 grNotDetected Graphics hardware not detected (нет оборудования) -3 grFileNotFound Device driver file not found (не найден файл драйвера устройства) -4 grInvalidDriver Invalid device driver file (неверный файл драйвера устройства) -5 grNoLoadMem Not enough memory to load driver (не хватает памяти для загрузки драйве- ра) -6 grNoScanMem Out of memory in scan fill (выход за пределы памяти при сканирова- нии) -7 grNoFloodMem Out of memory in flood fill (выход за пределы памяти при "лавин- ном" заполнении) -8 grFontNotFound Font file not found (не найден файл шрифта) -9 grNoFontMem Not enough memory to load font (не хватает памяти для загрузки шрифта) -10 grInvalidMode Invalid graphics mode for selected driver (недопустимый графический режим для заданного драйвера) -11 grError Graphics error (ошибка графики) -12 grIOerror Graphics I/O error (ошибка ввода/вывода в графическом режиме) -13 grInvalidFont Invalid font file (неверный файл шрифта) -14 grInvalidFontNum Invalid font number (неверный номер шрифта) -15 grInvalidDeviceNum Invalid device number (неверный номер устройства) -18 grInvalidVersion Invalid version of file (недопустимая версия файла) ----------------------------------------------------------------- - 73,74 - В результате обращения grapherrormsg(graphresult()) будет возвращена соответствующая строка (см. таблицу). Возвращаемый код ошибки изменяется лишь тогда, когда ка- кая-либо из графических функций сообщает об ошибке. Зануляется он только в результате успешного выполнения функции initgraph или при вызове graphresult. Поэтому, если вы хотите знать, какая гра- фическая функция какую ошибку возвратила, вам следует сохранять значение graphresult во временной переменной. Запрос состояния ---------------- Вот перечень функций запроса состояния в графическом режиме: ================================================================= getarccoords возвращает информацию о координатах последнего обращения к arc или ellipse getaspectratio возвращает коэффициент сжатия графического эк- рана getbkcolor возвращает текущий цвет фона getcolor возвращает текущий цвет рисования getdrivename возвращает имя текущего графического драйвера getfillpattern возвращает заданный пользователем шаблон закра- шивания getfillsettings возвращает информацию о текущих шаблоне закра- шивания и цвете getgraphmode возвращает текущий графический режим getlinesettings возвращает текущие стиль, шаблон и толщину ли- нии getmaxcolor возвращает максимальное допустимое значение - 75,76 - точки растра getmaxmode возвращает максимальный номер режима для теку- щего драйвера getmaxx возвращает текущее разрешение по оси x getmaxy возвращает текущее разрешение по оси y getmodename возвращает имя данного режима драйвера getmoderange возвращает диапазон режимов для данного драй- вера getpalette возвращает текущую палитру и ее размер getpixel возвращает цвет точки растра (x,y) gettextsettings возвращает текущие шрифт, направление, размер и выравнивание текста getviewsettings возвращает информацию о текущем графическом ок- не getx возвращает координату x текущей позиции gety возвращает координату y текущей позиции ================================================================= В каждой категории графических функций Турбо Си имеется хотя бы одна функция запроса состояния. Эти функции были упомянуты в соответствующих разделах, а здесь они собраны все вместе. Все они начинаются с get... (исключение составляет категория функций, об-
рабатывающих ошибки). Некоторые из них безаргументные и возвраща- ют единственное число, представляющее собой запрашиваемую инфор- мацию; другие принимают указатель на структуру, определенную в graphics.h и заполняют эту структуру соответствующей информацией, ничего при этом не возвращая. Функции запроса состояния, попадающие в категорию управления графической системой - это getgraphmode, getmaxmode и getmoderange. Первая возвращает целое число, представляющее собой информацию о текущем графическои драйвере и режиме, вторая - пре- дельный режим для данного драйвера, а третья - диапазон режимов, поддерживаемых данным графическим драйвером. Функции getmaxx и getmaxy возвращают максимальные координаты экрана для текущего графического режима. Функции запроса состояния при рисовании и закрашивании - это getarccoords, getaspectratio, getfillpattern, getfillsettings и getlinesettings. getarccoords заполняет структуру координатами последнего обращения к arc или ellipse; getaspectratio сообщает о коэффициенте сжатия для текущего режима, который позволяет графи- ческой системе вычерчивать правильные окружности. getfillpattern - 77,78 - возвращает текущий пользовательский шаблон. getfillsettings за- полняет структуру информацией о текущем шаблоне и цвете. getlinesettings заполняет структуру информацией о текущих сти- ле(сплошная, пунктирная и т.д.), толщине (нормальная или тонкая), и шаблоне линии. В категорию функций управления экраном и графическими окнами попадают getviewsettings, getx, gety и getpixel. Задав графичес- кое окно, вы можете узнать его абсолютные координаты и состояние буферизации при помощи вызова getviewsettings; функция заполнит структуру необходимой информацией. getx и gety возвращают коорди- наты текущей позиции относительно окна. getpixel возвращает цвет заданной точки растра. Категория функций вывода текста в графическом режиме содер- жит одну всеобъемлющую функцию запроса состояния: gettextsettings. Эта функция заполняет структуру информацией о текущем символьном шрифте, направлении вывода текста(горизонталь- ное или вертикальное снизу вверх), коэффициенте сжатия символов и выравнивании строк текста(как горизонтальном, так и вертикаль- ном). Категория функций управления цветом включает три функции запроса состояния. Функция getbkcolor возвращает текущий цвет фо- на, getcolor - текущий цвет рисования. Функция getpalette записы- вает в структуру размер текущей палитры рисования и содержимое палитры. getmaxcolor возвращает максимальное допустимое значение точки растра для текущего графического драйвера и режима (т.е. размер палитры минус единица). И наконец, функции getmodename и getdrivename возвращают со- ответственно имена данного режима драйвера и текущего графическо- го драйвера. - 79,80 - Г Л А В А 9 ------------ ЗАМЕЧАНИЯ ДЛЯ ПРОГРАММИСТОВ, РАБОТАЮЩИХ НА ТУРБО ПАСКАЛЕ. ----------------------------------------------------------------- Перед тем, как продолжить свое знакомство с Турбо Си, восс- тановите в памяти главы 6 и 7. Вспомните, как Си определяет базо- вые элементы программирования. В этой главе мы рассмотрим некото- рые из основных понятий Си, однако в главах 6 и 7 есть много деталей, которых вы здесь не найдете. Паскаль является достаточно последовательным и структурным языком, в то время как Си - довольно свободный и гибкий. Паскаль заботится о вас лучше, чем Си и таким образом больше подходит в качестве языка, используемого для обучения основам программирова- ния. Турбо Си и Турбо Паскаль - находятся где-то в середине спектра языков Си - Паскаль. Турбо Си добавляет некоторые струк- туры к Си, а Турбо Паскаль - некоторую гибкость Паскалю. Эта глава не предназначена быть исчерпывающим обсуждением Си } Из всех функций, которые вы объявляете, только одна должна иметь имя main. Это и есть главный модуль вашей программы. Други- ми словами, когда ваша программа начинает выполняться, вызывается функция main, а она может включать в свою очередь вызов других функций. Любая Си программа состоит только из функций. Однако, некоторые функции имеют тип void и не возвращают значений; так, - 83,84 - что они аналогичны процедурам Паскаля. Также (в отличие от Паска- ля) вы можете просто игнорировать любые значения, возвращаемые функциями. Пример ---------------------------------------------------------------- Представлены две программы (одна написана на Турбо Паскале, другая на Турбо Си), иллюстрирующие сходство и различие между их структурами программ: Турбо Паскаль Турбо Си ________________________________________________________________
program My_Prog; var I,J,K : Integer; int i,j,к function Max(A,B:Integer):Integer; int max (int a, int b) begin { if A>B if (a>b) return (a); then Max:=A else return (b); else Max:=B } end; /* конец max () */ {конец функции Max} - 85,86 - procedure Swap (var A,B: Integer); void swap (int *a,int *b) var Temp : Integer; begin { int temp; Temp:=A; A:=B; B:=Temp temp= *a; *a=*b; *b=temp; end; } {конец процедуры swap} /*конец swap () */ begin {глав. модуль программы main () My_Prog} I:=10; J:=15; { K:=Max (I,J); i=10; j=15; Swap (I,K); k=max (i,j); Write ('I=',I:2, 'J=', J:2); swap (&i, &k); Writeln ('K=', K:2) printf("i=%2d j=%2d",i,j); end printf ("k=%2d\n",k); {конец программы My_Prog} } /*конец main*/ ----------------------------------------------------------------- Сравнение базовых элементов. ---------------------------------------------------------------- Как и в главе 6, рассмотрим семь базовых элементарных поня- тий программирования - вывод, типы данных, операции, ввод, выпол- нение по условию, выполнение в цикле и подпрограммы. В ы в о д. ----------------------------------------------------------------- Основные команды вывода Турбо Паскаля это - Write и Writeln. Турбо Си, напротив, имеет множество команд, позволяющих выполнять точно то, что вы хотите. Основной командой является printf. Она имеет формат: printf( Writeln('"Hello," said John'); printf("\"Hello,\"said John\n"); Writeln(A,' + ',B,' = ',C); printf("%d + %d = %d\n",a,b,c); Writeln('You owe us S',Amt:6:2); printf("You owe us S%6.2f\n",amt); Writeln('Your name is ',Name,'?'); printf("Your name is %s?\n",name); Writeln('The answer is ',Ans); printf("The answer is %c\n",ans); - 89,90 - Write(' A = ',A:4); printf(" a = %4d",a); Writeln(' A*A = ',(A*A):6); printf(" a*a = %6d\n",a*a); ---------------------------------------------------------------- Другие два оператора вывода Си, которые бы вы вероятно хоте- ли бы знать - это puts и putchar. puts получает строку в качестве аргумента и выводит ее, автоматически добавляя символ перехода на новую строку. Например, следующие команды эквивалентны: Writeln(Name); puts(Name); Writeln('Hi, there!');Writeln; puts("Hi, there!\n"); Команда putсhar (вывести символ) значительно проще. Она вы- водит всего один символ. Например: write(Ch); putchar(ch); Т и п ы д а н н ы х. ---------------------------------------------------------------- Основные типы данных Турбо Паскаля имеют соответствующие эк- виваленты в Турбо Си. Однако Си имеет как значительно большее разнообразие типов данных с широким выбором числовых диапазонов для значений целых и с плавающей точкой, так и спецификаторов signed и unsigned (со знаком и без знака). Ниже приведена таблица соответствия между типами данных на Паскале и Си. Турбо Паскаль (версия 3.х) Турбо Си ---------------------------------------------------------------- char (1 байт) chr(0-255) char (1 байт) -129 - 127 byte (1 байт) 0 - 255 unsigned char (1 байт) 0 - 255 integer (2 байт) -32763 - 32767 short (2 байта) -32768 - 32767 int (2 байта) -32769 - 32767 - 91,92 - unsigned int (2 байта) 0 - 65535 long (4 байта) -2^31 - 2^31-1 unsigned long (4 байта) 0 - (2^32-1) real (6 байта) 1E-38 - 1E+38 float (4 байта) +-3.4E+308 double (8 байт) +-1.7E+308 boolean (1 байт) false, true 0 = false, не ноль = true ---------------------------------------------------------------- Обратите внимание на то, что в Си нет логического типа дан- ных: выражения, в которых требуются логические значения, интер- претируют значения "ноль", как false (ложь), а все другие, как "true" (истина). В дополнение к приведенным типам, Турбо Си имеет enumerated (перечислимый) тип данных; однако, в отличие от Паскаля, это пе- реназначаемые целые константы и они полностью совместимы со всеми целыми типами.
- 93,94 - Турбо Паскаль Турбо Си ---------------------------------------------------------------- type Days = (Sun,Mon,Tues,Wed, enum days = (Sun,Mon,Tues,Wed, Thurs,Fri,Sat); Thurs,Fri,Sat); var Today : Days; enum days today; ---------------------------------------------------------------- Операции. ---------------------------------------------------------------- В Турбо Си имеются все операции Турбо Паскаля и еще некото- рые другие. Основным отличием между этими двумя языками является ис- пользование операции присвоения. В Паскале, присвоение (:=) яв- ляется оператором. В Си присвоение (=) - оператор, который может использоваться в выражениях. В таблице 6.1 дается сравнение операций на Турбо Паскале и Турбо Си. Операции объединены в группы и приводятся в порядке приоритета. - 95,96 - Табл.6.1 Операции на Паскале и Си ---------------------------------------------------------------- унарный минус A:= - B; a= - b; унарный плюс A:= + B; a= + b; логическое НЕ not Flag; !flag; поразрядное дополнение A:= not B; a=-b; адрес A:=Addr(B); a=&b; указатель(ссылка) A:= IntPtr^; a=*intptr; размер A:= SizeOf(B); a=sizeof(b); увеличение A:= Suсс(A); a++ и ++a уменьшение A:= Pred(A); a-- и --a умножение A:= B*C; a= b*c; целочисленное деление A:= BdivC; a= b/c; деление X:= B/C; x= b/c; модуль (остаток) A:= B mod C; a= b%c; сложение A:= B + C; a=b+c; вычитание A:= B - C; a=b-c; сдвиг вправо A:= B shr C; a=b>>c; сдвиг влево A:= B shl C; a=b - адрес, по которому scanf размещает вводимые данные. Это значит, что вам часто необходимо, будет использовать оператор адреса (&). Есть также другие общеис- пользуемые команды: gets, которая читает входную строку до тех пор, пока вы не нажмете Ввод, и getch, которая читает символ пря- мо с клавиатуры без эхо. Приведем несколько команд ввода на Паскале и, соответствен- но, на Си. Турбо Паскаль (версия 3.x) Турбо Си ---------------------------------------------------------------- Readln(A,B); scanf("%d%d",&a,&b); Readln(Name); scanf("%s",name); /* или gets(name); */ Readln(X,A); scanf("%f%d",&x,&a); Readln(Ch); scanf("%c",ch); Read(Kbd,Ch); ch = getch(); ---------------------------------------------------------------- Отметим одно важное отличие между двумя способами ввода строк (scanf и gets). Scanf читает все символы до тех пор, пока не встретится пробел (табуляция, конец строки); напротив, gets считывает любые символы, пока вы не нажмете Ввод. - 101,102 -
Блок операторов. ----------------------------------------------------------------- И Паскаль, и Си поддерживают концепцию блока (группы опера- торов, которая может быть помещена в любое место как один опера- тор). В Паскале блок имеет вид: begin и управление пере- ходит далее пока не встретится метка case Fri:. Оператор break передает управление в конец оператора switch. Однако, в конце не- дели программа будет работать по другому:метка case Sat: заставит выполниться printf, после чего управление перейдет к оператору puts. - 109,110 - Циклы (итерации). ----------------------------------------------------------------- В Си, так же как в Паскале, есть 3 типа циклов: while, do...while и for, которые соответствуют трем конструкциям Паска- ля: while, repeat...until, for.
Цикл while (пока). ----------------------------------------------------------------- Этот цикл наиболее близок в обоих языках: while b) them Max := A return(a); else Max := B else return(b);
end; } ---------------------------------------------------------------- Заметим, что в Си оператор return используется для возвраще- ния значения функции, в то время как в Паскале это достигается присвоением значения имени функции. Турбо Паскаль Турбо Си ----------------------------------------------------------------- procedure Swap(var X,Y : Real); void swap(float *x, float *y) var Temp : Real; { begin float temp; Temp := X; temp= *a; X := Y; *a = *b; Y := Temp *b = temp; end; } ----------------------------------------------------------------- B Паскале имеется 2 типа параметров:var (передача по адресу) и value (по значению). В Си параметры передаются только по значе- нию. Если вы хотите иметь параметры, передаваемые по адресу, то вы должны передавать адрес, а формальный параметр определить как указатель. Это было продемонстрировано в вышеприведенном примере функции swap. Ниже приведена программа вызова этих подпрограмм: - 119,120 - Турбо Паскаль Турбо Си ---------------------------------------------------------------- Q := 7.5; g = 7.5; R := 9.2; r = 9.2; Writeln('Q=',Q:5:1,'R=',R:5:1); printf("g=%5.1f r=%5.1f\n",g,r); Swap(Q,R); swap(&g,&r); Writeln('Q=',0:5:1,'R=',R:5:1); printf("g=%5.1f r=%5.1f\n",g,r); ----------------------------------------------------------------- Отметим использование операции адреса (&) в Си при передаче параметров g и r подпрограмме swap. Прототипы функций. ----------------------------------------------------------------- Наиболее важные различия между Паскалем и Си касаются функ- ций: Паскаль всегда делает проверку на соответствие количества и типов параметров, определенных в функции, с количеством и типами параметров, используемых при вызыве этой функцией. Другими слова- ми, допустим вы определили функцию: function Max(I,J : Integer) : Integer; и пытаетесь вызвать ее с действительными значениями (А:=Мах(В,3.52);). Что произойдет? Вы получите при трансляции ошибку, сообщающую вам, что присутствует несоответствие типов, т. к. 3.52 не является целым. В Си это не так, по умолчанию Си-компилятор не производит проверку ошибок при вызове функции: он не проверяет возвращаемые функцией параметры, их типы и количество. С одной стороны - это дает некоторую свободу, так как можно вызывать функцию до того, как она будет определена, с другой - может причинить и беспокойс- тво (см. "Ошибку #2" в заключении главы). Можно ли это как-то из- - 121,122 - бежать? Турбо Си поддерживает прототипы функций. Вы можете понимать это, как нечто подобное предварительному описанию в Паскале. Ста- райтесь располагать прототипы функций в начале текста программы (перед вызовом этих функций). Запомните, что прототип функции - является видом описания и должен предшествовать фактическому вы- зову функции. Прототип функции имеет формат: int max(int a, int b); void swap(float *x, float *y); void swapitem (listitem *i, listitem *j); void sortlist (list l, int c); void dumplist (list l, int c); В отличие от оператора предварительного описания - forward у Паскаля, прототипы функций в Си освобождают вас от лишних усилий при фактическом определении функции. Другими словами вы можете определить функцию как вам хочется (или определить ее, используя современный стиль программирования). Конечно, если описание функ- ции не будет соответствовать прототипу, Турбо Си выдаст ошибку компиляции. Турбо Си поддерживает и классический, и современный стиль, однако сейчас в Си тенденция к использованию нового стиля, поэто- му и мы рекомендуем вам применять прототипы функций. Использование прототипов функций помогает избежать некоторых проблем, особенно, когда вы используете оттранслированные библио- теки Си программ. Вы можете создать отдельный файл и ввести в не- го заголовки функций всех подпрограмм из библиотеки. Когда вы за- хотите использовать любую программу из библиотеки, включите ваш заголовок файла в программу (с помощью директивы #include). Такой способ поможет вам избежать неприятностей от возможной ошибки во время трансляции. - 123,124 - Основной пример. ----------------------------------------------------------------- Теперь приведем большой пример, целую программу, использую- щую многое из того, что вы узнали до сих пор. Программа определя- ет массив myList, длина которого - LMAX, а тип (List Item) соот- ветствует целому. Массив инициализируется неупорядоченными числа- ми, выводится на дисплей подпрограммой dumplist, сортируется по порядку с помощью sortlist, и снова выводится на дисплей. Отметим, что данная Си версия программы не самая лучшая. Она была написана в максимально возможном приближении к версии на Паскаль. Некоторые места, которые не соответствуют друг другу, демонстрируют коренные различия Си и Паскаля. Турбо Паскаль Турбо Си ---------------------------------------------------------------- program DoSort; const LMax = 100; #define LMAX 100 type Item = Integer; typedef int item; List = array[1..LMax] of Item; typedef item list[LMAX]; var myList : List; list myList; Count,I : Integer; int count, i; Ch : Char; char ch; procedure SortList(var L:List; void swapitem(item *i,item*j) C : Integer); var { Top,Min,K : Integer; item temp; temp = *i; *i = *j; *j =temp; procedure SwapItem(var I,J:Item); } /* swapitem */ var Temp : Item; void sortlist(list l, int c) begin Temp:=I; I:=J; J:=Temp end; (*конец SwapItem*) { int top,min,k; begin (*Основное тело SortList*) for Top:=l to C-1 do begin for(top=0; top<=c; k++) if L[K]< l[min]) then Min := K; min = k; SwapItem(L[Top],L[Min]) swapitem(&l[top],&l[min]); end } end;(*конец SortList*) }/*sortlist*/ procedure DumpList(L:List; void dumplist(list l,int c) C : Integer); var I : Integer; { begin int i; for I := l to C do for (i=0; i<=c; i++) Writeln('L(',I:3,')=',L[I]:4) printf("l/%3d)=%4d\n",i,l[i]); end; (*Конец DumpList*) } /* dumplist()*/ main() begin (*Основное тело DoSort*) { for I := l to LMax do for (i=0; i
DumpList (myList,Count); dumplist(myList,count); Read(Kbd,Ch); ch = getch(); SortList(myList,Count); sortlist(myList,count); DumpList(myList,Count); dumplist(myList,count); Read(Kbd,Ch) ch = getch(); end.(* of DoSort *) } /* main */ --------------------------------------------------------------- Остановимся на наиболее важных моментах: # В Паскаль-версии, мы помещаем процедуру SwapItem внутрь про- цедуры SortList; в Си нельзя вкладывать функции, поэтому мы вынесли swapitem "вне" sortlist. # В Си массивы начинаются с индекса 0 и располагаются до ]; Отметим, что в отличие от Паскаля, вы не имеете возможности применять короткую запись, т.е. [] скобки с запятыми (см. Ошибку #5).
В Си выделяется область памяти достаточная для
- 143,144 - Structures (Структуры). ----------------------------------------------------------------- Оба языка и Паскаль, и Си позволяют определить унифицирован- ные и неоднородные структуры данных. В Паскале они называются records, а в Си - структуры. Формат для обоих: Турбо Паскаль Турбо Си ---------------------------------------------------------------- type typedef struct { , если вы не собираетесь указывать больше таких переменных. Полями ссылок в Паскале являются: xp.w, xp.hib и xp.lob, а в Си - xc.w, xc.b.hib и xc.b.lob. Выводы по программированию. ----------------------------------------------------------------- Как программист на Паскале, вы не должны ощущать трудности при изучении Турбо Си. Но есть небольшая область в программирова- нии, которая вызывает некоторые трудности при переходе с языка на язык. Мы обсудим каждый из таких разделов в этой части. Чувствительность к регистрам. ----------------------------------------------------------------- Паскаль, в отличие от Си, не чувствителен к типу букв (строчные - заглавные). Если идентификаторы indx, Indx и INDX в Паскале ссылаются на одинаковые переменные, то в Си - на три раз- личные переменные. Примечание: т.к. вызовы функций не проявляют себя до тех пор пока программа на Си не скомпанована, то различие в регистрах может не чувствоваться. Для вашей собственной пользы будьте точны при выборе регистра на Си. - 151,152 -
Приведение типов. ----------------------------------------------------------------- Паскаль, как правило, позволяет использовать только ограни- чение типов. Функция Ord() переводит из любого порядкового (пере- числимого) типа в Integer; Chr() переведит из Integer (или родс- твенных типов) в Char. Турбо Паскаль допускает некоторые добавоч- ные типы при переводе (так называемое retyping) между всеми по- рядковыми типами (Integer, Char, Boolean и переменным типам дан- ных). Си более свободен и позволяет вам пробовать смену одного типа на любой другой, но без гарантии благоприятного исхода. Турбо Паскаль Турбо Си ---------------------------------------------------------------- ; Элементы данных, нуждающиеся более чем в одной величине (массивы, структуры) должны иметь значения, заключенные в фигур- ные скобки и отделенные запятыми ({like_this, and_this, and_this_ too}). int x = 1, y = 2; char name []="Франк"; char answer ='Y'; char key = 3; char list[2] [10] = {"Первый", "Второй"}; Переменные памяти. ----------------------------------------------------------------- Си определяет несколько классов памяти для переменных; два наиболее важных - external (внешний) и automatic (local) (мест- ный). Глобальные переменные (которые описываются вне любой функ- ции, включая основную) являются внешними по умолчанию. Это подра- зумевает, что им присваивается начальное значение 0 в начале выполнения программы, если вы сами не присвоили им значения. Переменные, указываемые в пределах функций (в том числе ос- новной), принимаются по умолчанию automatic. Им не присваиваются значения, если вы не сделали это, и они теряют свои величины меж- ду вызовами этой функции. Однако вы можете указать такие перемен- ные static; им будет присвоен 0 (сразу, в начале выполнения прог- раммы) и они сохранят свои значения между вызовами функций. Рассмотрим следующий пример init test (void) { int i; - 157,158 - static int count; ... } Переменная i находится в стеке и может быть инициализирован- на функцией test каждый раз, когда вызывается программа. Cтати- ческая переменная count, c другой стороны находится в глобальной зоне данных и инициализируется нулем, когда программа выполняется впервые. Count сохраняет свое предыдущее значение при следующем вызове функции test. Динамическое распределение памяти. ----------------------------------------------------------------- В Турбо Паскале есть несколько методов для управления кучей (динамической областью памяти). Дадим следующее описание на Турбо Паскале: type ItemType=Integer; ItemPtr=^ItemType; var p : ItemPtr; На практике используется три различных метода распределения и освобождения динамической памяти: (* New и Dispose*) New(p); {Автоматическое размещение требуемой памяти} ... Dispose(p); {Автоматическое освобождение задействованой памяти} (*New, Mark и Release*) New (p); {Автоматическое размещение требуемой памяти} ... Mark(p); - 159,160 - Release(p) {Освобождение всей динамической памяти, начиная с p до конца кучи} (*FreeMem и GetMem*) GetMem(p, Sizeof(ItemType)); {Определение объема памяти для размещения} ...
FreeMem(p, Sizeof(ItemType)); {Определение объема памяти для освобождения} В Турбо Си для распределения и освобождения динамической па- мяти используются подпрограммы, которые абсолютно похожи на GetMem и Dispose в Турбо Паскале: ); BlockWrite(F3,buffer,Sizeof(buffer); fwrite(&buffer,1, sizeof(buffer),f3); Вам необходимо обратиться к книге 2 (Справочное руководство) для более детального понимания работы каждой из программ на Турбо Си. Это короткий пример программы демонстрирует вывод текстового
файла (имя которого вводится в командной строке) на экран. Турбо Паскаль Турбо Си ---------------------------------------------------------------- program DumpIt; # include void swap (float *x, float *y); Теперь, если вы прокомпилируете вашу программу с оператором swap(q,r), вы получите ошибку, сообщающую, что вы имеете несоот- ветствие типов при вызове swap. Ошибка #3: пропуск скобок при вызове функции. ----------------------------------------------------------------- В Паскале, процедура, не имеющая параметров, вызывается просто по имени: AnyProcedure; i := AnyFunction; В Си, название функции, если даже нет параметров, должно всегда содержать открытые и закрытые скобки. Легко ошибиться так: AnyFunction; /*Нет никакого действия*/ i = AnyFunction; /*Присвоение i адреса AnyFunction */ когда вы действительно хотели: AnyFunction(); /*Вызвать AnyFunction*/ - 173,174 - i = AnyFunction(); /*Вызвать AnyFunction, присвоить результат i */ Ошибка #4: предупреждающие сообщения. ----------------------------------------------------------------- В добавление к основным ошибочным сообщениям, Турбо Си также выдает предостережения о не фатальных ошибках. Используя некор- ректную функцию вызова из предшествующего примера, Турбо Си может выдать следующие предостережения: Warning test.c 5: Code has no effect in function main (код не исполняется в основной функции) Warning test.c 6: Non-portable pointer assigment in function main (в основной функции присваивается не мобильный указатель) Оба оператора действительно законны, и так как ошибки не имели место, может быть создан файл .OBJ. Остерегайтесь! Эти типы предупреждений будут всегда при фатальных ошибках. Не относитесь к предостережениям легкомысленно. Ошибка #5: индексация в многомерных массивах. ----------------------------------------------------------------- Предположим, вы имеете двумерный массив, названный matrix, и хотите сослаться на ячейку памяти (i,j). Как программист на Пас- кале, вы склоны записать это следующим образом: x = matrix [i,j]; Это вполне возможно, однако не делайте так. В Си это означает серию выражений, отделенных запятыми; в этом случае, полное выражение берет величину из последнего выра- жения, так предшествующий оператор является эквивалентом x = matrix[j]; - 175,176 - Это не то, что вы хотели, но это законченный оператор в Си. Все, что вы получите, это предостережение, так как Си думает, что вы пробуете присвоить x адрес matrix [j], то есть j-той строки. В Си вы должны явно индексировать каждый массив. Так вам надо напи- сать x = matrix[i] [j]; Памятка. Для многомерного массива каждый индекс заключается в отдельные скобки. Ошибка #6: Забывание о различиях между символьными
массивами и символьными указателями. ----------------------------------------------------------------- Предположим, что мы имеем следующие операторы: сhar *str1, str2[30]; str1 = "This is test"; str2 = "This is another test"; Первое присвоение доступно, второе - нет. Почему? str1 ука- зывает на строку, и когда транслятор видит этот оператор присвое- ния, он создает строку "This is test" где-нибудь в вашем объект- ном файле и присваивает адрес str1. Напротив, str2 - постоянный указатель блока из 30 байт;вы не можете изменить адрес, который он содержит. То, что вы хотите на- писать, будет: strcpy (str2, "This is another test"); В этом варианте из постоянной строки "This is another test" - 177,178 - байт за байтом копируется в область, адресуемую str2. Ошибка #7: забывание о том, что Си чувствителен к размеру букв (строчные-заглавные). ----------------------------------------------------------------- В Паскале идентификаторы indx, Indx, INDX все одинаковы; заглавные и строчные буквы являются тождественными. В Си - нет. Если вы напишите: int Indx; а потом: for (indx=1; indx #define LMAX 100 Если вы забудете и введете #define LMAX 100;, тогда препро- цессор подставит 100; везде где встретится LMAX, (точка с запятой и все). Запомните - Си требует внимательности от программистов. Вы должны быть более точными, чем при программировании на языке Пас- каль. - 181,182 - Г Л А В А 10 -------------- ИНТЕРФЕЙС МЕЖДУ ТУРБО СИ И ТУРБО ПРОЛОГОМ. ----------------------------------------------------------------- Теперь, получив знания по Турбо Си, вы можете объединить два очень мощных языка, реализованных в настоящий момент на PC. Ком- понуя модули Турбо Си с модулями Турбо Пролога, вы можете, тем самым, "подключать" искусственный интеллект (ИИ) к своим приклад- ным программам, написанным на Турбо Си. Если вы опытный програм- мист, работающий на Си, то должны были отметить несколько преиму- ществ Турбо Си перед другими реализациями языка Си. Если же вы только изучаете Си, то сейчас самый подходящий момент для того, чтобы посмотреть, как Турбо Си и Турбо Пролог дополняют друг дру- га. Турбо Си - процедурный язык, а Турбо Пролог - язык, базирую- щийся на логическом программировании. Связывание ваших прикладных программ, написанных на Турбо Си и на Турбо Прологе, может дать следующие преимущества интеллектуальной технологии: - продукционно-ориентированные управляющие структуры; - интегрированную оболочку на базе естественного языка. - 183,184 - Компоновка с Турбо Прологом позволяет включать в прикладные программы, написанные на Турбо Си, мощь искусственного интеллек- та, так что вы можете решать перспективные проблемы, просто опи- сывая их и запуская в работу логический механизм Турбо Пролога. Для многих прикладных программ на Турбо Си компоновка с програм- мами Турбо Пролога будет значительно сокращать время разработки программного обеспечения, увеличивать прозрачность и гибкость программ. В этой главе... ----------------------------------------------------------------- В этой главе мы объясним этапы компиляции и компоновки Турбо Си и Турбо Пролог программ и приведем четыре примера, демонстри- рующие этот процесс. Первый пример - простейшая программа, де- монстрирующая компиляцию и компоновку. Второй пример идет немного дальше, он покажет, как компоновать программы, используя дополни-
тельные библиотеки Си. Третий пример демонстрирует распределение памяти. Последний пример описывает практическую программу работы с графикой и демонстрирует некоторые мощные возможности, которые вы получите от комбинирования двух языков. - 185,186 - Компоновка Турбо Си и Турбо Пролога: обзор ----------------------------------------------------------------- Компиляция и компоновка ваших модулей, написанных на Турбо Си, с модулями и программами Турбо Пролога выполняется напрямую. Вам только необходимо запомнить следующие пункты: Компиляция ваших программных модулей: # Ваши Си функции, вызываемые Турбо Прологом, должны иметь суф- фикс _0 (см. первый пример программы на Си, CSUM.C, в этой главе) , если вы не используете в Турбо Прологе расширение as " вается так: void *_malloc (int size). _free освобождает память, распределеную в "куче" Пролога, и вызывается так: _free(void *ptr, int size). Когда используется alloc_gstack, память при неудачной отра- ботке освобождается автоматически, что заставляет Турбо Про- лог производить откат через операцию размещения памяти. - 189,190 - # printf, putc и соответствующие им функции вывода на экран не работают после компоновки Турбо Си и Турбо Пролога. Тем не менее, писать символы в окна Пролога может wrch, а zwf явля- ется аналогом предиката Турбо Пролога writef. zwf - это printf с некоторыми ограничениями. zwf(FormatString, аргумент 1, аргумент 2, аргумент 3, ...) FormatString - строка формата, аналогичная используемой в printf. Посмотрите в справочном руководстве по Турбо Проло- гу, какие спецификации преобразования поддерживаются. zwf и wrch находятся в PROLOG.LIB. # Функции Си, вызываемые Турбо Прологом, не должны возвращать значений и должны быть определены как void. Шаблон на аргу- менты должен быть специфицирован в глобальных объявлениях предикатов Турбо Пролога. Так: factorial(integer,real) - (i,o) language c позволяет определить Турбо Прологу, что factorial - есть функция от двух аргументов: первый - integer (целый), второй - real (с плавающей точкой). Конструкция (i,o) указывает, что первый аргумент (integer) является входным, а второй - указатель на число с плавающей точкой, которому будет прис- воено значение факториала. Буква с в этой конструкции указы- вает Турбо Прологу, что эта функция использует соглашения вызывов Си (смотри третий пример программы в этой главе: DUBLIST.C и PLIST.PRO). Заметьте, что значение возвращается в виде указателя. Для большей информации по описанию шаблонов на аргументы смотрите обсуждение его альтернативы в примере 3. - 191,192 - Пример 1: Сложение двух целых чисел ----------------------------------------------------------------- Следующий пример комбинирует функцию Турбо Си (которая скла- дывает два целых числа) с модулем на Турбо Прологе, который поме- щает результат, вычисленный функцией Си, в текущее окно. Исходный файл Турбо Си: CSUM.C ----------------------------------------------------------------- /* подпрограмма вывода zwf работает подобно подпрограмме printf язы- ка Си. Она печатает результат в текущем окне */ extern void zwf(char *format, ...); void sum_0(int parm1,int parm2, int *res_p) { zwf("Это функция суммирования: parm1=%d, parm2=%d, parm1,parm2); *res_p=parm1+parm2; } /* конец sum_0 */ Компиляция CSUM.C в CSUM.OBJ ----------------------------------------------------------------- После редактирования и сохранения на диске файла CSUM.C, вам необходимо выбрать опции для его компиляции. Турбо Си для этого предоставляет вам два метода: 1. Выберете опции для компиляции из меню Интегрированной среды Турбо Си: Options/Compiler/Model/Large (-ml) Options/Compiler/Optimization/Jump Optimization...On (-O) Options/Compiler/Code Generation/Generate Underbars...Off (-u-) Options/Compiler/Optimization/Use Register Variables...Off (-r-) Сразу после выбора опций выберите в главном меню Турбо Си опцию Options/Store options - Опции/Сохранить опции, когда же на-
чальная установка параметров будет сохранена, выберите Compile/ Compile to OBJ. Турбо Си скомпилирует CSUM.C в выбранных режимах в объектный модуль CSUM.OBJ. 2. Если вы предпочтете компилировать CSUM.C командной стро- - 193,194 - кой по стандарту DOS вместо использования меню Турбо Си, то после приглашения операционной системы, введите tcc -ml -O -c -u- -r- csum Замечание: Турбо Пролог компилируется только в большой моде- ли памяти, поэтому для обеспечения связи между Турбо Си и Турбо Прологом вы должны использовать при компиляции опцию -ml. Исходный файл Турбо Пролога : PROSUM.PRO ----------------------------------------------------------------- global predicates sum(integer,integer,integer) - (i,i,o) language c /* описание аргументов функции суммирования - sum определено как (i,i,o) и специфициру- ет, что третий аргумент возвращает значе- ние, а первые два принимают). */ goal sum(7,6,X),write("Sum=",X). Компиляция PROSUM.PRO в PROSUM.OBJ ----------------------------------------------------------------- После редактирования и сохранения PROSUM.PRO вам необходимо откомпилировать его в обьектный (.OBJ) файл, который позволит скомпоновать его с обьектным модулем Турбо Си. Чтобы сделать это, выберите опцию Compile из главного меню Турбо Пролога, а затем - OBJ File . Когда Турбо Пролог завершит компиляцию исходного файла в обьектный файл, вы можете выполнить компоновку и запуск вашего - 195,196 - примера. Компоновка CSUM.OBJ и PROSUM.OBJ ----------------------------------------------------------------- Для компоновки модулей Турбо Пролога с модулями Турбо Си, вы можете использовать или интегрированную среду Турбо Си, или ком- поновщик TLINK (автономный компоновщик, включенный в ваш пакет Турбо Си). За командой tlink следуют аргументы командной строки: главные модули Турбо Пролога, набор других модулей, выходные фай- лы и библиотеки. Кроме специально оговоренных случаев, они должны следовать в следующем порядке: Инициализация Турбо Пролога: --------------------------- # INT.OBJ (модуль инициализации Турбо Пролога) Главный модуль Турбо Пролога: ---------------------------- # главный модуль Турбо Пролога, который содержит раздел gоal - 197,198 - Набор модулей: ------------- (эти модули не обязательно выстраивать в какой-то определен- ный порядок) # ассемблеровские .OBJ модули # Турбо Си .OBJ модули # Турбо Пролог .OBJ модули Модуль таблицы идентификаторов: ------------------------------ # Имя основной таблицы идентификаторов Пролога (это обяза- тельный параметр, он должен завершать список модулей) Имя выходного файла ------------------- # имя выполняемого файла, который будет создан Библиотеки: ---------- # перечисляются все библиотеки, содержащие функции, необхо- димые для компонуемых модулей. Последовательность важна: первыми идут библиотеки, определяемые пользователями, затем PROLOG.LIB, далее, если необходимо, EMU.LIB и MATHL.LIB, и наконец, CL.LIB. В этом примере мы использовали программу компоновки Turbo Link (обозначаемую как tlink) и передали ей следующие аргументы: # Программы Турбо Пролога, INIT.OBJ и PROSUM.OBJ # Объектный модуль Турбо Си CSUM.OBJ # Таблицу идентификаторов PROSUM.SYM и выполняемый файл TEST.EXE # библиотеки PROLOG.LIB, CL.LIB (для работы с плавающей точ- кой используйте EMU.LIB и MATHL.LIB) Замечание: PROSUM.SYM является файлом, содержащим таблицу имен и типов переменных, имеющихся в программе PROSUM.OBJ. Ниже приведена командная строка для компоновки нашего перво- - 199,200 - го примера: tlink init prosum csum prosum.sym, test.exe,,prolog+cl Пример 2: Использование библиотеки математических функций ----------------------------------------------------------------- Второй пример похож на первый, он демонстрирует, как записы- ваются две функции на Турбо Си и как они комбинируются с програм- мой на Турбо Прологе. Мы оформляем каждую из используемых функций Турбо Си в виде отдельного исходного файла; CSUM1.C складывает два действительных числа и возвращает сумму, а FACTRL.C вычисляет факториал целого числа. Программа на Турбо Прологе FACTSUM.PRO записывает результаты программы в два окна Пролога. Данный пример использует библиотеку математических функций большой модели памя- ти - MATHL.LIB. И с х о д н ы й ф а й л Турбо Си: CSUM1.C extern void zwf(char *format, ...); void sum_0(double parml, double parm2, double *res_p) { *res_p=parm1+parm2; zwf("Это функция суммирования: parm1=%f, parm2=%f, result=%f", parm1,parm2,*res_p); - 201,202 -
} И с х о д н ы й ф а й л Турбо Си: FACTRL.C void factorial_0(int top, double *result) /* Вычисляет факториал */ { double x; int i; if (top makewindow(1,49,31, "Окно взаимодействия Турбо Пролога с программами Турбо Си", 0,0,15,80), makewindow(2,47,3, "Окно Турбо Пролога для Турбо Пролог программы", 15,0,10,80), /* Приглашение пользователя к первому вводу */ write("Введите целое число; Турбо Си вычислит факториал."), readint(Int), nl, shiftwindow(1), /* Переадресация окна вывода в окно Турбо Си */ /* Вызов модуля Турбо Си factrl и вычисление факториала */ factorial(Int, Result), shiftwindow(2), /* Переадресация окна вывода в окно Турбо Пролога */ /* Приглашение пользователя ко второму вводу */ write("Введите действительное число для сложения с факториалом"), readreal(Real),nl, shiftwindow(1), /* Переадресация окна вывода в окно Турбо Си */ /* Вызов модуля Турбо Си сsum1 и вычисление суммы */ sum(Result,Real,Sum), shiftwindow(2), /* Переадресация вывода в окно Турбо Пролога */ /* Запись результата первого вычисления в окно */ write("Значение факториала числа ",Int," равно ",Result),nl, /* Запись результата второго вычисления в окно */ write("Результат ", Result, " + ",Real," = ",Sum),nl. - 207,208 - Компиляция FACTSUM.PRO в FACTSUM.OBJ ----------------------------------------------------------------- Также как и в первом примере вы должны откомпилировать файл с исходным текстом на Турбо Прологе в обьектный файл (.OBJ) перед компоновкой его с другими модулями. Перед тем как выполнить ком- пиляцию, выберите из главного меню Турбо Пролога Оptions/Obj. Компоновка СSUM1.OBJ, FACTRL.OBJ и FACTSUM.OBJ ----------------------------------------------------------------- В команде на компоновку, используемой в этом примере, - объектными модулями Турбо Пролога являются модули INIT.OBJ и FACTSUM.OBJ; - объектными модулями Турбо Си являются модули CSUM1.OBJ и FACTRL.OBJ; - именами выходных файлов являются FACTSUM.SYM (таблица идентификаторов) и SUM.EXE (выполняемый файл); - необходимыми библиотеками являются PROLOG.LIB, EMU.LIB, MATHL.LIB и CL.LIB. Приведенная ниже команда компонует модули: tlink init factsum factrl csum1 factsum.sym,sum,,prolog +emu+mathl+cl Замечание: команда должна быть набрана в одной строке. - 209,210 - Пример 3: Шаблоны аргументов и распределение памяти. ----------------------------------------------------------------- Следующая программа представляет код по созданию на Турбо Прологе функтора (functor) и списка (list) в Турбо Си, а также возвращению этих новых структур в Турбо Пролог. Этот пример также демонстрирует возможное распределение памяти в глобальном стеке Турбо Пролога. Списки представляют собой рекурсивные структуры из
трех элементов, а функторы - структуры языка Си из двух элементов (они описаны более полно после этого примера). - Модуль на Турбо Си DUBLIST.C содержит три функции. Первые две могут брать список, состоящий из целых чисел, и возвращать структуру с целым элементом, являющимся первым числом (из спис- ка), или же брать структуру, в которой есть целое число, и возв- ращать список с этим числом. Третья функция берет целое число n и создает список из двух целых чисел, первым элементом которого яв- ляется само число n, а вторым - число 2n. - Необходимо отметить, что для каждого глобального предиката Турбо Пролога возможно использование альтернативных шаблонов, описывающих аргументы, и что каждый такой шаблон требует альтер- нативной функции Турбо Си. Для следующего примера функция clist_0 должна соответствовать первому шаблону описания аргументов (i,о), а функция clist_1 - второму шаблону (о,i). global predicates clist(ilist,ifunc) - (i,o) (o,i) language c - Спецификация (i,o) означает, что ilist будет передана в ва- шу функцию Турбо Си clist_0, а ifunk - указатель на структуру, который будет определен внутри функции Турбо Си clist_0. Специфи- кация (o,i) означает, что ifunс будет передана в clist_1, а ilist - указатель на списковую структуру, которая будет определена внутри clist_1. - Если дополнительный шаблон был специфицирован в глобальном домене вашей Турбо Пролог программы, то функция clist_2 вынуждена будет обрабатывать этот дополнительный шаблон. И с х о д н ы й ф а й л Турбо Си: DUBLIST.C void fail_cc(void); void *alloc_gstack(int size); - 211,212 - struct ilist { char functor; /* тип элемента списка */ /* 1 = элемент списка */ /* 2 = конец списка */ int val; /* фактический элемент */ struct ilist *next /* указатель на следующий узел */ }; struсt ifunc { char type; /* тип функтора */ int value; /* значение функтора */ }; void clist_0(struct ilist *in, struct ifunc **out) { if (in->functor != 1) fail_cc();/*завершить, если список пуст */ *оut = alloc_gstack(sizeof(struct ifunc)); (*out)->value = in->val; /* out присваивает значение f(x)*/ (*out)->type = 1 /* задание типа функтора */ } void clist_1(struct ilist **out, struct ifunc *in) { int temp = 0; struct ilist *endlist= alloc_gstack(sizeof(struct ilist)); endlist->functor = 2; temp = in->value; temp += temp; *out = alloc_gstack(sizeof(struct ilist)); (*out)->val = temp; /* возвращается [2*X], как элемент */ /* списка */ (*out)->functor = 1;/* устанавливается тип элемента */ /* списка. Если это не выполнять, */ /* то возвращается значение лишенное */ /* смысла */ (*out)->next = endlist; } void dublist_0(int n, struct ilist **out) { /* эта функция создает список [n,n+n] */ struct ilist *temp; struct ilist *endlist= alloc_gstack(sizeof(struct ilist)); - 213,214 - endlist->functor = 2; temp = alloc_gstack(sizeof(struct ilist)); temp->val = n; /* первому элементу списка присваивается значение n */ temp -> functor = 1; *out = temp; /* теперь мы должны разместить второй элемент списка */ temp = alloc_gstack(sizeof(struct ilist)); next = temp; /* установка второго элемента после */ /* первого */ } Вызов Турбо Пролога из Турбо Си ----------------------------------------------------------------- Возможен не только вызов Турбо Прологом предикатов, написан- ных на Турбо Си, но также и вызов Турбо Си прологовских предика- тов. Если некоторый глобальный предикат описан в Турбо Прологе как language c и существуют предложения (clauses) для данного предиката, то Турбо Пролог создаст подпрограмму, которую можно будет вызывать из Турбо Си. Следующая программа на Турбо Прологе объявляет два глобаль- ных предиката языка Си: message и hello_c. Предикат message может быть вызван из модуля Си посредством использования имени функции message_0 в тексте программы на Си. global predicates message(string) - (i) language c hello_c - language c clauses message(S) :- makewindow(13,7,7,"",10,10,3,50), - 215,216 - write(S), readchar(_), removewindow. goal message("Привет из Турбо Пролога"), hello_c. Раздел goal в данном примере вызывает функцию Турбо Си hello_c, которая, в свою очередь, вызывает предикат Турбо Пролога message_0 для вывода сообщения. void message_0(char *str); void hello_c_0(void) { message_0("Привет из Турбо Си"); } Вы можете использовать эту возможность для легкого доступа к мощной библиотеке Турбо Пролога из других языков. Вы легко можете определить ваши собственные библиотечные подпрограммы в модуле на Турбо Прологе, например так: project "dummy" /* подставьте сюда имя */ global predicates myfail language c as "fail" mymakewindow(integer,integer,integer,string,integer,integer, integer,integer) - (i,i,i,i,i,i,i,i) language c as "makewindow" myshiftwindow(integer) - (i) language c as "shiftwindow" myremovewindow language c as "removewindow" write_integer(integer) - (i) language c as "write_integer" write_real(real) - (i) language c as "write_real" write_string(string) - (i) language c as "write_string" myreadchar(char) - (o) language c as "readchar" myreadline(string) - (o) language c as "readline" extprog language c as "extprog" clauses myfail :- fail. - 217,218 - mymakewindow(Wno,Wattr,Fattr,Text,Srow,Scol,Rows,Cols) :- makewindow(Wno,Wattr,Fattr,Text,Srow,Scol,Rows,Cols). myshiftwindow(WNO) :- shiftwindow(WNO). myremovewindow :- removewindow. write_integer(I) :- write(I). write_real(R) :- write(R). write_string(S) :- write(S). myreadchar(CH) :- readchar(CH). myreadline(S) :- readln(S).
Следующая процедура Турбо Си, extprog, демонстрирует исполь- зование этих новых библиотечных подпрограмм. extprog создает окно Турбо Пролога, после чего выполнит в этом окне некоторые процеду- ры ввода и вывода. void makewindow(int wno,int wattr,int fattr,char *title, int row,int srow,int scol); void write_string(char *text); void readchar(char *ch); void readline(char *in_str[]); void removewindow(void); void extprog(void) { char dummychar; char *Name; makewindow(1,7,7,"Hello there",5,5,15,60); write_string("\n\nIsn't it easy"); readchar(&dummychar); write_string("\nEnter your name: "); readline(&Name); write_string("\nYour name is: "); write_string(Name); readchar(&dummychar); removewindow(); } - 219,220 - Единственное ограничение при вызове Турбо Пролога из Турбо Си состоит в том, что программа на Турбо Прологе должна быть главной программой ввиду того, что Турбо Прологу необходимо уста- навливать "кучу" и стеки. Списки и функторы ----------------------------------------------------------------- Турбо прологовские списки и функторы - это структуры Турбо Си (смотри DUBLIST.C). Списки - это рекурсивные структуры, состоящие из трех эле- ментов. Первый элемент структуры является типом, он может прини- мать значение 1 (если это элемент списка) и 2 (если это конец списка). Второй элемент структуры - фактическое значение; он дол- жен соответствовать типу элемента, определенного в программе Тур- бо Пролога. Например, список из действительных чисел может иметь вид: struct alist { char funct; double elem; /* cписок элементов типа real */ struct alist *next; } Третий элемент является указателем на следующий узел. - 221,222 - Функторы Турбо Пролога - это структуры Си, состоящие из двух элементов. Первый элемент соответствует описанию домена на Турбо Прологе. (Для получения более полной информации об описании доме- нов в программах Турбо Пролога обратитесь к справочному руководс- тву Турбо Пролога). Например: domains func = i(integer); s(string) predicates call(func) goal call(X) Функтор Турбо Пролога func имеет два типа значений: первый - integer, второй - string. Поэтому в этом примере тип элементов структуры Турбо Си должен быть 1 или 2; 1 соответствует первому типу (целые числа), а тип 2 - второму типу (строковому). Второй элемент структуры Турбо Си является фактическим зна- чением функтора и определяется как union (объединение) для воз- можных типов аргументов. union val { int ival; char *svar; }; struct func { char type; /* тип может быть 1 или 2 в соответствии с */ /* описанием домена в Турбо Прологе */ union val value; /* значение элемента функтора. */ } Замечание: функции alloc_gstack, _malloc и _free должны ис- пользоваться для распределения памяти (они находятся в CPINIT.OBJ). Эти функции необходимы, во-первых, для выделения па- мяти структурам Турбо Си и занесения их в динамическую область или стек Турбо Пролога и, во-вторых, для освобождения памяти в динамической памяти Турбо Пролога. Когда используется функция alloc_gstack, память при ложных правилах будет освобождаться автоматически, вынуждая Турбо Пролог выполнять повторное распределение памяти. Синтаксис этих функций Турбо Си приведен ниже: - 223,224 - void *alloc_gstack(size) /* распределяет память в стеке */ void *_malloc(size) /* распределяет память в "куче" */ _free(void * ptr,size) /* освобождает "кучу" */ Ниже приведен главный модуль Турбо Пролога PLIST.PRO, кото- рый вызывает функции из DUBLIST.C и печатает результаты. domains ilist = integer* ifunc = f(integer) global predicates clist(ilist,ifunc) - (i,o) (o,i) language c dublist(integer,ilist) - (i,o) language c goal clearwindow, clist([3],X), /* связывает X с f(3) */ write("X = ",X),nl, clist(Y,X), /* связывает Y с [6] */ write("Y = ",Y),nl, dublist(6,Z), /* связывает Z с [6,12] */ write(Z),nl. Компиляция DUBLIST.C ----------------------------------------------------------------- Также как и в первых двух примерах, вы должны откомпилиро- вать модуль DUBLIST.C в объектный (.OBJ) файл перед компоновкой его с главным модулем на Турбо Прологе PLIST.PRO. Вот команда для компоновки: tlink init plist dublist plist.sym, dublist, , prolog+emu+mathl+cl - 225,226 - Пример 4. Рисование 3-х мерных диаграмм. ----------------------------------------------------------------- На этом примере мы рассмотрим, как компилировать и компоно- вать модули Си и Пролога для создания унифицированной программы со смешением языков, комбинирующей гибкость искусственного интел- лекта с графическими возможностями Си. Конкретно программа включает следующее: - Модуль Турбо Си CBAR.C, который рисует столбиковые диаг- раммы (гистограммы), используя входные данные из другого файла. - Главный модуль Турбо Пролога PBAR.PRO, который требует от пользователя ввода данных. Текст программы на Си находится в файле CBAR.C на диске из комплекта поставки. Компиляция CBAR.C
----------------------------------------------------------------- Также как и в первых трех примерах, вы должны компилировать модуль Турбо Си CBAR.C в объектный (.OBJ) файл перед компоновкой его с главным модулем на Турбо Прологе PBAR.PRO. Программа Турбо Пролога: PBAR.PRO --------------------------------- Текст программы находится в файле PBAR.PRO. Эта программа на Турбо Прологе приглашает пользователя соз- давать, сохранять, загружать или рисовать диаграммы. Если пользоваталь пожелает создавать диаграмму, то эта прог- рамма запросит у него спецификацию для ее построения, определит позицию каждого столбика в окне и вызовет модуль Си для изображе- ния диаграммы. После того, как каждая полоса будет вычерчена, ди- аграмма будет включена в базу данных Турбо Пролога. - 227,228 - Пользователь может выбрать опцию сохранения диаграммы; PBAR запишет описание текущей диаграммы в файл для последующего ис- пользования. Если пользователь укажет режим загрузки, то PBAR уничтожит текущее описание диаграммы и загрузит специфицированное пользова- телем описание диаграммы из файла. Получив последнее задание draw (рисовать), PBAR будет ис- пользовать описание из базы данных и осуществит рекурсивный вызов модуля BAR Турбо Си, который изобразит столбиковую диаграмму в соответствии с ее текущим описанием в базе данных. Компиляция PBAR.PRO в PBAR.OBJ ----------------------------------------------------------------- Как и в примере 1, вы должны компилировать исходный файл главного модуля Турбо Пролога в объектный (.OBJ) перед компонов- кой его с модулями Турбо Си. Компоновка PBAR.OBJ с модулем CBAR.OBJ ----------------------------------------------------------------- Приведенная ниже команда на компоновку связывает PBAR.OBJ с предварительно откомпилированным модулем Турбо Си CBAR.OBJ. Ком- понентами этой команды являются: - объектные модули Турбо Пролога INIT.OBJ и PBAR.OBJ - объектный модуль Турбо Си CBAR.OBJ - таблица идентификаторов PBAR.SYM и выходной файл BARCHART.EXE (выполняемый файл) - библиотеки PROLOG.LIB и CL.LIB Команда на компоновку имеет вид: - 229,230 - tlink init pbar cbar pbar.sym,barchart,,prolog+cl Р е з ю м е ----------------------------------------------------------------- На этих четырех примерах мы показали, как компоновать модули Турбо Пролога с вашими программами Турбо Си. Если вы - опытный программист, работающий на Турбо Прологе, и желаете узнать больше о программировании на Си, то мы рекомендуем вам ознакомиться с главами 3 и 7 этого руководства. Если же вы - опытный програм- мист, работающий на Си, и желаете преобрести информацию о Турбо Прологе, мы рекомендуем вам учебники по Турбо Прологу, такие как Using Turbo Prolog by P.Robinson (McGraw-Hill). - 231,232 - ГЛАВА 11. РУКОВОДСТВО ПО ЯЗЫКУ ТУРБО СИ ----------------------------------------------------------------- Наиболее популярным первоисточником по языку Си является книга Б.Кернигана и Д.Ритчи "Язык программирования Си" (далее для краткости K&R"). Данная книга не определяет полный стандарт Си (эта задача решена американским национальным институтом стандар- тов ANSI). В K&R, с другой стороны, дается минимальный стандарт, так что программа, соответствующая этому стандарту, может быть откомпилирована любым транслятором Си, поддерживающим определения K&R. Турбо Си поддерживает не только определения, данные K&R, но и большинство расширений стандарта ANSI. Поэтому, на самом деле Турбо Си - это улучшенный и расширенный язык Си с дополнением но- вых возможностей, большей мощностью и гибкостью по отношению к стандарту. Мы не имеем возможности перепечатать здесь K&R или стандарт ANSI, вместо этого раскажем вам о расширениях определе- ний K&R, отмечая какие из них соответствуют стандарту ANSI, а ка- кие являются нашими собственными. * прим. пер. - переведена на русский язык (г.Москва, "Финансы и статистика", 1985 г.). - 233,234 - В этой части... ----------------------------------------------------------------- Для облегчения работы с руководством мы следуем (более или менее) содержанию Приложения А K&R, которое называется "Справоч- ное руководство по Си". Не на все разделы этого приложения приво- дятся ссылки, некоторые мы пропускаем. Вы можете сделать вывод что в них содержатся только незначительные отличия между опреде- лениями Турбо Си и K&R. Кроме того, для большего понимания неко- торых расширений ANSI и Турбо Си, мы приводим необходимую инфор- мацию в том же порядке, что и в ANSI стандарте Си, а не в порядке, принятом в K&R. Комментарии (K&R 2.1) ----------------------------------------------------------------- По определению K&R не разрешается использовать вложенные комментарии. Например, конструкция: /* Попытка прокомментировать myfunc() */ /* myfunc() { printf("Это моя функция\n} /* просто строка */ } */ будет интерпретироваться как только один комментарий, оканчиваю- щийся справа после фразы "просто строка". Оставшаяся скобка и ко-
нец комментария вызовут синтаксическую ошибку. По умолчанию Турбо Си не разрешает вложенные комментарии имеет Турбо Си не разрешает вложенные комментарии, хотя вы можете кор- ректно откомпилировать (такую как показано) программу с вложен- ными комментариями, используя опцию компилятора -c (либо с по- мощью конструкции Nested Comments... ON (разрешить вложение ком- ментариев) в O/C/Source меню). Для обеспечения большей мобиль- ности правильнее отмечать код, который должен быть прокомментиро- ван, директивами #if 0 и #endif. Комментарии заменяются одним символом пробела после макро- расширения. В других реализациях комментарии уничтожаются пол- ностью или иногда используется передача лексем. Смотрите "Замена - 235,236 - лексем" в этой главе. Идентификаторы (K&R 2.2) ----------------------------------------------------------------- Идентификаторы - это просто те имена, которые вы даете пере- менным, функциям, типам данных или другим объектам, определенным пользователями. В Си идентификаторы могут включать буквы (A...Z, a...z) и цифры (0...9), а также символ подчеркивания. Турбо Си также разрешает вам использовать знак доллара ($). Конечно, иден- тификатор может начинаться только с буквы или символа (_). Регистр (верхний или нижний) имеет значение: другими словами идентификаторы indx и Indx различны. В Турбо Си внутри программы значащими являются первые 32 символа идентификатора ; однако вы можете изменить это число с помощью опции компилятора -i#, где # является числом значащих символов. (Это определяется в меню оп- ций O/C/S/Identifier Length (Длина Идентификатора) .) Первые 32 символа являются значащими также и для глобальных идентификаторов, берущихся из других модулей. Однако, вы имеете опцию разрешающую определять или нет чувствительность к регистрам этих идентификаторов, используя опцию Case sensitive link..ON из подменю Options/Linker или /c опцию компоновщика TLINK, запускае- мого с командной строки. Но отметим, конечно, что идентификаторы типа pascal никогда не чувствительны к регистру во время компо- новки. Ключевые слова (K&R 2.3) ----------------------------------------------------------------- В таблице 8.1 приведены ключевые слова, зарезервированные Турбо Си, которые не должны использоваться в качестве названий идентификаторов. Предшествующее им сокращение AN соответствует ANSI расширениям K&R, а TC - расширениям Турбо Си. Ключевые слова entry и fortran, упомянутые в K&R, не используются и не резерви- руются в Турбо Си. - 237,238 - Таблица 11.1: Ключевые слова зарезервированные Турбо Си. ----------------------------------------------------------------- TC asm extern return TC _cs TC _DH auto TC far short TC _ds TC _DL break float AN signed TC _es TC _DX case for sizeof TC _ss TC _BP TC cdecl goto static TC _AH TC _DI char TC huge struct TC _AL TC _SI AN const if switch TC _AX TC _SP continue int typedef TC _BH default TC interrupt union TC _BL do long unsigned TC _BX double near AN void TC _CH else TC pascal AN volatile TC _CL AN enum register while TC _CX Константы (K&R 2.4) ----------------------------------------------------------------- Турбо Си поддерживает все типы констант, определенные в K&R, с некоторыми расширениями. Целые константы (K&R 2.4.1) --------------------------- 0xFFFFFFFF будет переполнение (как описано выше) ----------------------------------------------------------------- Символьные константы (K&R 2.4.3) -------------------------------- Турбо Си поддерживает двухсимвольные константы, например, 'An', '\n\t' и '\007\007'. Эти константы имеют 16-битное предс- тавление типа int, причем первый символ находится в младшем бай- те, а второй в старшем. Запомните, что такие константы не перено- симы в другие компиляторы Си. Односимвольные константы, такие как 'A', '\t' и '\007' также имеют 16 битное представление типа int. В этом случае младший байт является сигналом переполнения в старшем байте; т.е., если десятичное значение больше чем 127, то старший байт устанавлива- ется в -1 [=0xFF]. Это может быть запрещено объявлением, что тип char по умолчанию является незначащим. Для этого используется опция компилятора -k или конструкция Default char type...Unsigned в подменю Options/Compiler/Source, делающая старший байт нулевым, не считаясь со значением младшего байта. Турбо Си поддерживает ANSI расширение, допускающее шестнадца- тиричное представление кодов символов, например, '\x1F','\x82' и так далее. Кроме того допустима запись x и X, а также использова- ние от одной до трех цифр. Турбо Си также поддерживает другие ANSI расширения из списка разрешенных escape (эскейп)-последовательностей. Escape-последо- вательности представляют собой значения, которыми засылаются сим-
вольные или строковые константы,перед которым находится обратный слеш (\). В таблице 11.3 приведен список всех разрешенных после- довательностей, а те что отмечены звездочкой (*) являются допол- нением к списку, приведенному в K&R. - 243,244 - Таблица 11.3. Escape последовательности Турбо Си ----------------------------------------------------------------- Последовательность Значение Символ Что делает ----------------------------------------------------------------- * \a 0x07 BEL Звуковой сигнал \b 0x08 BS - 247,248 - Зависимость от аппаратных средств (K&R 2.6) ----------------------------------------------------------------- В K&R признается, что размер и числовой диапазон основных типов данных (со всеми вариациями) очень зависит от конкретной архитектуры компьютера. Это справедливо для Турбо Си и для боль- шинства других компиляторов Си. В таблице 11.4 приведен список размеров и соответствующих диапазонов для различных типов данных в Турбо Си. Таблица 11.4. Типы данных, размеры и диапазоны в Турбо Си ----------------------------------------------------------------- Тип Размер (в битах) Диапазон ----------------------------------------------------------------- unsigned char 8 0 - 255 char 8 -128 - 127 enum 16 -32768 - 32767 unsigned short 16 0 - 65535 short 16 -32768 - 32767 unsigned int 16 0 - 65535 int 16 -32768 - 32767 unsigned long 32 0 - 4294967295 long 32 -2147483648 - 2147483647 float 32 3.4E-38 - 3.4E+38 double 64 1.7E-308 - 1.7E+308 long double 80 3.4E-4932 - 1.1E+4932 pointer 16 (указатели near,_cs,_ds,_es,_ss) pointer 32 (указатели far, huge) - 249,250 - Преобразования (K&R 6) ----------------------------------------------------------------- Турбо Си поддерживает стандартные механизмы по автоматичес- кому преобразованию одного типа данных в другой. Следующие разде- лы поясняют дополнения к K&R или раскрывают специфические тонкос- ти данной реализации. Char, int и enum (K&R 6.1) ----------------------------------------------------------------- Преобразование символьной константы к целому типу имеет ре- зультатом 16-битное значение, поскольку и одно, и двусимвольные константы представляются 16-битным значением (см. K&R 2.4.3.). Результат преобразования символьного объекта (такого как перемен- ная) к целочисленному объекту автоматически получает знаковое расширение, если вы не сделали по умолчанию тип char беззнаковым, используя при компиляции опцию -k. Объекты типа signed char при преобразовании их в int всегда используют знаковое расширение; объекты типа unsigned char всегда устанавливают старший байт в ноль. Значения типа enum преобразуются в int без модификации; ана- логично значения, имеющие тип int, могут преобразовываться в зна- чения перечислимого типа enum, а символы преобразуются в int зна- чения и обратно. Указатели (K&R 6.4) ----------------------------------------------------------------- В Турбо Си различные указатели вашей программы, могут быть различных размеров, в зависимости от используемой модели памяти или используемого модификатора типа указатель. Например, когда вы компилируете программу, используя конкретную модель памяти, адре- суемые модификаторы (near, far, huge, _cs, _ds, _es, _ss) в вашем исходном тексте могут изменять размер указателя, заданный данной моделью памяти. Указатель должен быть объявлен как указатель на некоторый конкретный тип, даже если данный тип - void (который в действи- - 251,252 -
тельности означает указатель на ничего). Однако, будучи объявлен- ным, такой указатель может указывать на объект любого другого ти- па. Турбо Си позволяет переназначать указатели, но компилятор бу- дет предупреждать о случаях переназначения, если только предвари- тельно этот указатель не был объявлен как указатель на тип void. Однако, указатели на типы данных не могут быть преобразованы в указатели на функции, и наоборот. Арифметические преобразования (K&R 6.6) ----------------------------------------------------------------- В K&R определяются обычные арифметические преобразования, определяющие, что произойдет с любыми величинами, использованными в арифметических выражениях (операнд, оператор, операнд). Здесь приведены шаги, используемые Турбо Си, для преобразования операн- дов в арифметических выражениях: 1. Любой не integer и не double тип преобразуется в соответс- твии с таблицой 11.5. После этого любые два значения, объединен- ные оператором, являются целыми - int (включая long и unsigned модификаторы) или с плавающей точкой - double. 2. Если какой-либо операнд имеет тип double, то другой операнд тоже преобразуется в double. 3. В случае, если один из операндов имеет тип unsigned long, то другой операнд тоже преобразуется в unsigned long. 4. В случае, если один из операндов имеет тип long, то другой операнд тоже преобразуется в long. 5. В случае, если какой-либо из операндов имеет тип unsigned, то другой операнд тоже преобразуется в unsigned. 6. В последнем варианте, оба операнда являются типа int. Результат выражения определяется типами обоих операндов. - 253,254 - Таблица 11.5: Методы, используемые для определения результата в арифметических преобразованиях. ----------------------------------------------------------------- Тип Преобразуется в Метод ----------------------------------------------------------------- char int со знаком unsigned char int нулевой старший байт (всегда) signed char int со знаком (всегда) short int если unsigned, то unsigned int enum int тоже значение float double мантисса дополняется нолями ----------------------------------------------------------------- Операторы (K&R раздел 7.2) ----------------------------------------------------------------- Турбо Си поддерживает унарный оператор + , K&R - нет. Этот оператор неэффективен, но необходимо предвидеть симметрию с про- тивоположным оператором. Стараясь создать эффективно компилируе- мые выражения, Турбо Си обычно выполняет перегруппировку выраже- ния с переупорядочиванием коммутативных операторов (таких как * и бинарный +). Однако, Турбо Си не будет переупорядочивать выраже- ния с плавающей точкой. То есть можете использовать скобки для выделения одних преобразований в выражениях с плавающей точкой перед другими. Например, если a, b, c и f имеют тип float, тогда выражение f = a + (b + c); заставит сначала оценить значение (b+c), а затем прибавить результат к a. - 255,256 - Спецификаторы типов и модификаторы (K&R 8.2) ----------------------------------------------------------------- Турбо Си поддерживает следующие основные типы, отсутствующие у K&R: - unsigned char - unsigned short - unsigned long - long double - enumeration - void Типы int и short совпадают в Турбо Си; оба занимают 16 бит. Смотрите "Hardware Specifics" (аппаратно-архитектурную специфику) для более детального понимания о воплощении различных типов. Тип enum ----------------------------------------------------------------- Турбо Си поддерживает все перечислимые типы ANSI стандарта. Перечислимый тип данных используется для описания дискретного множества целых значений. Например, вы можете объявить следующее: enum days { sun, mon, tues, wed, thur, fri, sat }; Имена, перечисленные в days, являются целыми константами: первая (sun) автоматически установлена в ноль, а каждая следующая имеет значение на единицу больше, чем предыдущая (mon = 1, tues=2 и т.д.). Однако, вы можете присвоить константам и определенные значения; следующие имена, без конкретных значений, будут тогда, как и раньше, иметь значения предыдущих элементов с увеличением на единицу. Например, enum coins {penny=1, nickle=5, dime=10, quarter=25}; Переменной перечислимого типа может быть присвоено любое значение типа int - проверка типа не производится. - 257,258 - Тип void ----------------------------------------------------------------- У K&R каждая функция возвращает значение; если тип не объяв- лен, то функция принадлежит типу int. В Турбо Си поддерживается тип void, определенный в ANSI стандарте. Он используется для яв- ного описания функций, не возвращающих значений. Аналогично, пус- той список параметров может быть объявлен зарезервированным сло- вом void. Например, void putmsg(void) { printf("Hello, world\n"); } main() { putmsg(); } Вы можете преобразовывать выражение к типу void, для того чтобы явно указать, что значение, возвращаемое функцией, игнори- руется. Например, если вы хотите приостановить выполнение прог- раммы до тех пор пока пользователь не нажмет какую-либо клавишу, то можете написать: (void) getch(); Наконец, вы можете объявить указатель на объект типа void. Данный указатель не будет указателем на ничто; а будет указателем на любой тип данных, причем конкретный тип этих данных знать нео- бязательно. Вы можете присваивать любой указатель указателю типа void (и наоборот) без приведения типов. Однако вы не можете ис- пользовать оператор косвенной адресации (*) с указателем void, т. к. используемый тип неопределен. - 259,260 - Модификатор signed ----------------------------------------------------------------- Кроме указанных у K&R трех типов модификаторов - long, short и unsigned - Турбо Си поддерживает еще три: signed, const и volatile (все определены в ANSI стандарте).
Модификатор signed противоположен unsigned и явно указывает, что величина со знаком. Данный модификатор используется преиму- щественно для документирования и придания законченного вида прог- раммам. Однако, если вы компилируете программу, используя по умолчанию беззнаковый тип char (вместо знакового), то нужно ис- пользовать модификатор signed, для того чтобы определить перемен- ную или функцию типа signed char. Модификатор signed, использо- ванный сам по себе, означает signed int, также как и unsigned оз- начает unsigned int. Модификатор const ----------------------------------------------------------------- Модификатор const, по определению стандарта ANSI, не допус- кает каких бы то ни было переопределений собственных значений или других косвенных изменений, таких как увеличение или уменьшение. Указатель типа const не может быть модифицирован, в отличии от самого объекта, который он определяет. Замечание: модификатор const, используемый сам по себе, эквивалентен const int. Рассмот- рим следующие примеры: const float pi = 3.1415926; const maxint = 32767; char *const str = "Hello, world"; /* Указатель типа const */ char const *str2 = "Hello, world"; /* Указатель на строку типа const */ Приведенные ниже утверждения недопустимы: pi = 3.0; /* Присвоение значения константе */ - 261,262 - i = maxint--; /* Уменьшение константы */ str = "Hi, there!"; /* Переназначение указателя */ Заметим, однако, что вызов функции strcpy(str,"Hi, there!") допустим, поскольку выполняет посимвольное копирование строки символов "Hi, there!" в область памяти, определяемую str. Модификатор volatile ----------------------------------------------------------------- Модификатор volatile, также определенный стандартом ANSI, почти полная противоположность const. Он указывает, что объект может быть изменен не только вами, а также и чем-нибудь вне вашей программы, например, программой прерываний или портом ввода/выво- да. Объявление объекта как volatile предупреждает компилятор, что не нужно делать предположений относительно значения объекта, в тот момент когда оцениваются содержащие его выражения, т.к. зна- чение может (теоретически) измениться в любое время. Кроме того, volatile запрещает также компилятору использовать вместо перемен- ных регистровые переменные. volatile int ticks; interrupt timer(); { ticks++; } wait (int interval) - 263,264 - { ticks = 0; while(ticks что компилятор с высоким уровнем оптимизации может не загрузить значение ticks внутри цикла while, т.к. цикл не изменяет значения ticks. Модификаторы cdecl и pascal ----------------------------------------------------------------- Турбо Си позволяет вызывать из вашей программы программы, написанные на других языках, и наоборот. При смешивании языков вы должны умело обращаться с двумя важными объектами: идентификато- рами и передаваемыми параметрами. При компиляции программы Турбо Си, все глобальные идентифи- каторы программы, т.е. имена функций и глобальные переменные, сохраняются в объектном коде для последующей компоновки. По умол- чанию эти идентификаторы записываются в оригинальном виде, (т.е в соответствии с состоянием регистра - заглавный, строковый или и тот, и другой). Кроме того, символ подчеркивание (_) предшествует идентификатору, если вы не использовали опцию -u(Generated underbars...OFF). Аналогично, все внешние идентификаторы, объявленные вами в программе, остаются в том же самом формате. Компоновщик (по умолчанию) различает регистры клавиатуры, поэтому идентификаторы, используемые в различных исходных файлах, должны точно соответст- вовать и по орфографии, и по регистрам клавиатуры. - 265,266 - pascal ----------------------------------------------------------------- В определенных ситуациях, например, при использовании кода, написанного на других языках, описанный выше метод сохранения имен, применяемый по умолчанию, может вызвать ряд проблем. Турбо Си дает вам путь для обхода этих проблем. Вы можете объявить любой идентификатор как идентификатор типа pascal. Это означает, что идентификатор преобразуется к верхнему регистру и к нему не добавляется символ подчеркивания. (Если идентификатор яв- ляется функцией, то данное правило распространяется и на переда- ваемые параметры; см. "Модификаторы типа функций" для более де- тального понимания.) При этом используемый вами в исходном коде регистр не учитывается, т.к. на этапе компоновки применяется только верхний регистр. cdecl ----------------------------------------------------------------- Установив при компиляции опцию -p (соответствующую стандарт- ному Паскалю), вы можете сделать все глобальные идентификаторы в исходном файле типа pascal. Однако затем вы можете захотеть ука- зать, что некоторые идентификаторы чувствительны к регистру и впереди имеют знак подчеркивания, особенно, если это Си идентифи- каторы из другого файла. В этом случае вы можете сделать объявление этих идентифика- торов как cdecl, (что также будет влиять и на передачу в функцию параметров). Вы можете заметить, например, что все функции в заголовках файлов (stdio.h и др.) имеют тип cdecl. Это дает возможность про- вести компоновку с библиотечными программами, если вы используете опцию -p при компиляции. Для выяснения деталей смотрите раздел 10.1.1 K&R в этой главе, а также главу 12. - 267,268 - Модификаторы near, far и huge ----------------------------------------------------------------- Турбо Си имеет три модификатора, воздействующих на косвенный оператор (*), и, тем самым, модифицирующих указатели на данные. Речь идет о near, far и huge. Назначение этих ключевых слов более детально объясняется в главе 12, а здесь дается лишь краткий об- зор. Турбо Си позволяет использовать при компиляции одну из нес- кольких моделей памяти. Модель, которую вы используете, определя- ет (среди прочих деталей) внутренний формат указателей на данные. Если вы используете малую модель памяти (tiny, small, medium), все указатели имеют длину только 16 бит и задают смещение относи- тельно регистра сегмента данных (DS). Если вы используете большую модель (compact, large, huge), все указатели на данные имеют дли-
ну 32 бита и задают адрес сегмента и смещение. Иногда, когда используется модель данных одного размера, у вас может возникнуть желание объявить указатель с размером или форматом другим чем у используемого по умолчанию. Вы можете сде- лать это с помощью модификаторов near, far и huge. Указатель типа near имеет размер 16 бит; он использует теку- щее содержимое регистра сегмента данных (DS) для определения ад- реса сегмента. По умолчанию он используется для малых моделей данных. При использовании указателей типа near, данные вашей программы ограничены размером 64 K текущего сегмента данных. Указатель типа far имеет размер 32 бита и содержит как ад- рес, так и смещение. По умолчанию он используется для больших мо- делей. При использовании указателей типа far допускаются ссылки на данные в пределах адресуемого пространства 1 Мб процессора Intel 8088/8086. Указатель типа huge имеет размер 32 бита и аналогично преды- дущему, содержит адрес сегмента и смещение, однако, в отличии от указателей типа far, указатель huge всегда поддерживается норма- лизованным. Более детально он рассматривается в главе 12, а здесь дается отличие от указателей типа far: # Операторы отношения (==, !=, | |-----------|---|-----------|--------------|-----| | m | k |не использ.| j | i | |___________|___|___________|______________|_____| Поля целого типа хранятся в одной из двух форм; самый левый бит - знаковый бит. Например, поле бит типа signed int шириной 1 бит может хранить только значение -1 или 0, и любое ненулевое значение будет интерпретироваться как -1. - 273,274 - Операторы (K&R 9) ----------------------------------------------------------------- В Турбо Си реализованы все операторы, описанные в K&R, без исключений и модификаций. Определения внешних функций (K&R 10.1) ----------------------------------------------------------------- В Турбо Си описание extern, заданное внутри функции, имеет действие в пределах данного блока. Описание не будет распозна- ваться вне блока, в котором оно определено. Однако, Турбо Си бу- дет запоминать описания, для того чтобы сопоставить их с последу- ющими описаниями тех же самых объектов. Турбо Си поддерживает большинство предложенных ANSI расшире- ний к определениям функций K&R, включая, в частности, дополни- тельные модификаторы функций и прототипы функций. Турбо Си под- держивает также несколько собственных расширений и определений функций, таких как функции типа interrupt (прерывание). Модификаторы типа функции (K&R 10.1.1) ----------------------------------------------------------------- В дополнение к external и static, Турбо Си поддерживает ряд модификаторов, специфицирующих описания функций: pascal, cdecl, interrupt, near, far и huge. Модификатор функции pascal -------------------------- Модификатор pascal, используемый в Турбо Си, предназначен для функций (и указателей на функции), которые используют приня- тую в Паскале последовательность передачи параметров. Это позво- ляет писать на языке Си функции, которые могут быть вызванными из программ, написанных на других языках; а также обращаться из ва- ших Си программ к внешним программам, написанным на языках отлич- ных от Си. Имя функции преобразуется к верхнему регистру, для правильной работы компоновщика. Примечание: использование опции компилятора -p (Calling - 275,276 - convention...Pascal) будет вызывать обращение со всеми функциями (и указателями на эти функции) как если бы они были типа pascal. Кроме того, функции, объявленные типа pascal, могут вызываться из Си программ, также как и Си программа может быть вызвана из функ- ции, имеющей тип pascal. Например, если вы объявили и откомпили- ровали следующую функцию: pascal putnums(unt i, int j, int k) { printf("And the answers are: %d, %d и %d\n",i,j,k); } другая Си программа может затем компоноваться с ней и обращаться к ней, используя описание: pascal putnums(int i, int j, int k); main() { putnums(1,4,9); } Функции типа pascal не могут иметь различное число аргумен-
тов, как, например, функция printf. По этой причине вы не можете использовать эллипсис (...) (т.е. опускать подразумеваемый параметр) в определении функции типа pascal. (См. "Прототипы функций" для понимания использования элипсиса при определении функции с различным числом аргументов.) Модификатор функции cdecl ------------------------- Модификатор cdecl также является специфичным для Турбо Си. Как и модификатор pascal, он используется с функциями или указа- телями на функции. Его действие отменяет директиву компилятора -p и разрешает вызывать функции как обычные функции Си. Например, если вы при компиляции вышеприведенной программы установили опцию -p, но захотели использовать printf, то должны поступить следую- щим образом: extern cdecl printf(); putnums(int i, int j, int k); - 277,278 - cdecl main() { putnums(1,4,9); } putnums(int i, int j, int k) { printf("And the answers are: %d, %d и %d\n",i,j,k); } Если программа компилируется с опцией -p, то все функции, используемые из библиотеки времени выполнения, необходимо объяв- лять как cdecl. Если вы посмотрите файлы заголовков (такие как STDIO.H), то увидете, что каждая функция явно описана как cdecl, т.е. заранее подготовлена к этому. Заметьте, что главная програм- ма main должна быть также объявлена как cdecl, поскольку действие стартового кода программы, написанного на Си, всегда начинается c вызова модуля main. Модификатор функции interrupt ----------------------------- Модификатор interrupt также является специфическим для Турбо Си. Функции interrupt специально введены для использования с век- торами прерываний процессоров типа 8086/8088. Турбо Си будет ком- пилировать функцию типа interrupt в дополнительную функцию, вход и выход которой сохраняется в регистрах AX, BX, CX, DX, SI, DI, ES и DS. Другие регистры: BP, SP, SS, CS и IP сохраняются как часть последовательности Си вызова или как часть самой обработки прерывания. Рассмотрим пример типичного определения функции типа interrupt. void interrupt myhandler() { . . . } Вы можете объявлять функции прерываний как функции типа void. Функции прерываний могут объявляться для любой модели памя- ти. Для всех моделей, исключая huge, в регистр DS заносится прог- раммный сегмент данных. Для модели huge в DS заносится модульный - 279,280 - сегмент данных. Модификаторы функций near, far и huge ------------------------------------- Модификаторы near, far, и huge специфичны для Турбо Си. Они могут комбинироваться с модификаторами cdecl или pascal, но не с interrupt. Не-interrupt функции могут быть объявлены как near, far, или huge. Они по умолчанию устанавливаются для данной модели памяти. Near-функции используют near-вызовы, а far- или huge-фун- кции используют far-вызовы инструкций. В моделях памяти tiny, small, и compact специально неоговоренные функции по умолчанию имеют тип near. В моделях medium и large неоговоренные функции по умолчанию имеют тип far. В модели памяти huge по умолчанию ис- пользуется тип huge. Функции типа huge и far одинаковы за тем ис- ключением что в регистр DS заноситься адрес сегмента данных ис- ходного модуля когда вызывается huge-функция и он сбрасывается для far-функции. Функции типа huge обычно используются когда неб- ходимо организовать интерфейс с кодом на языке ассемблера который не может использовать некоторую память занятую из Турбо Си. Прототипы функций (K&R 10.1.2) ------------------------------ При объявлении функций в K&R допускается только указание ее имени, типа и скобок без параметров. Параметры (если они есть) объявляются только во время явного определения самой функции. ANSI стандарт и Турбо Си разрешают использовать прототипы функций для объявления функции. Эти объявления включают информа- цию о параметрах функции. Компилятор использует данную информацию для проверки вызовов функций на соответствие данных, а также для преобразования аргументов к требуемому типу. Рассмотрим следующий фрагмент программы: long lmax(long v1, long v2); main() { int limit = 32; char ch = 'A'; - 281,282 - long mval; mval = lmax(limit,ch); } Используя прототип функции для lmax, эта программа будет преобразовывать параметры limit и ch к типу long, используя стан- дартные правила преобразования, прежде чем они будут помещены в стек для обращения к lmax. При отсутствии прототипа функции пара- метры limit и ch были бы помещены в стек, соответственно, как це- лое значение и символ; в этом случае в lmax передавались бы пара- метры, не совпадающие по размеру и содержанию с ожидаемыми. Это ведет к возникновению проблем. В то время как Си в K&R не выпол- няет никакого контроля типа параметров или их числа, использова- ние прототипов функций очень помогает выявлять "жучки" и другие ошибки программистов. Прототипы функций также помогают при документировании прог- рамм. Например, функция strcpy имеет два параметра: исходную строку и выходную строку. Вопрос - как их распознать? Прототип функции char *strcpy(char *dest, char *source); делает это ясным. Если в файле заголовка имеются прототипы функ- ций, то вы можете распечатать этот файл и получить большую инфор- мацию необходимую для написания программы, вызывающей эти функ- ции. Описание функции с включенным в скобки единственным словом void означает, что функция совсем не имеет аргументов f(void) В противном случае, в скобках указывается список параметров, разделенных запятыми. Так, объявление может быть сделано в виде: func(int *, long); или с включением идентификаторов, как ниже: func(int *count, long total); - 283,284 - В обоих случаях, указанных выше, функция func принимает два параметра: указатель на тип int, названный count, и целую пере- менную типа long, названную total. Включение идентификатора в объявление имеет смысл лишь для вывода его в диагностическом со- общении, в случае возникновения несоответствия типа параметров. Прототип обычно определяет функцию как функцию, принимающую фиксированное число параметров. Для Си функций, принимающих раз- личное число параметров (таких, как printf), пропотип функции мо- жет заканчиваться элипсисом (...), например f(int *count, long total,...) У прототипов такого вида фиксированные параметры проверяются во время компиляции, а опущенные передаются, как при отсутствии прототипа. Рассмотрим несколько примеров объявления функций и прототи- пов. int f(); /* Функция возвращает величину типа int без информации о параметрах. Это классический стиль K&R */ int f(void); /* Функция возвращает значение типа int, */ /* параметры не передаются */ int p(int,long); /* Функция возвращает целое, а получает */ /* два параметра; */ /* первый имеет тип int, второй - long */
int pascal q(void); /* Функция типа pascal, возвращающая */ /* целое; параметры не передаются */ char far * s(char *source, int kind); /* Функция возвра- */ /* щает указатель типа far на строку,*/ /*а получает два параметра: типа char* и типа int */ int printf(char *format,...); /* Функция возвращает */ /* значение типа int, получая указатель */ /* на фиксированный параметр типа char */ /* и любое количество дополнительных параметров */ /* неизвестного типа */ - 285,286 - int (*fp)(int); /* Указатель на функцию, возвращающую */ /* целое и получающую единственный */ /* целый параметр */ Ниже приводятся правила, регламентирующие работу с модифика- торами языка и формальными параметрами в вызовах функций Турбо Си, как использующих прототипы, так и не использующих их: Правило #1. Модификаторы языка для описания функций должны совпа- дать с модификаторами, используемыми в объявлении функции, для всех обращений к функции. Правило #2. Функция может изменять значения формальных парамет- ров, но это не оказывает какого-либо воздействия на значения действительных аргументов в вызывающей прог- рамме, за исключением функций прерывания. Для большей информации смотрите "Функции прерывания" в главе 12. Если прототип функции не объявлен предварительно, то Турбо Си преобразует целочисленные аргументы при обращении к функции согласно правилам, приведенным в разделе "Арифметические преобра- зования". Если прототип объявлен, то Турбо Си преобразует задан- ные аргуметы к типу, назначенному для параметров. Если прототип функции включает элипсис (...), то Турбо Си преобразует все заданные аргументы функции как в любом другом прототипе (до элипсиса). Компилятор будет расширять любые аргу- менты, заданные после фиксированного числа параметров по нормаль- ным правилам для аргументов функций без прототипов. Если есть прототип, то число аргументов должно быть соответ- ственным (за исключением случая, когда в прототипе использован элипсис). Типы должны быть совместимы только по размеру, для того чтобы корректно производились преобразования типов. Вы всегда должны использовать явные преобразования аргументов к типу, допустимому для прототипа функции. Следующий пример прояснит данные замечания: int strcmp(char *s1, char *s2); /* Полный прототип */ int *strcpy(); /* Нет прототипа */ int samp1(float, int, ...); /* Полный прототип */ samp2() - 287,288 - { char *sx, *cp; double z; long a; float q; if (strcmp(sx, cp)) /* 1. Верно */ strcpy(sx, cp, 44);/* 2. Верно, но не переносимо из Турбо Си */ samp1(3, a, q); /* 3. Корректно */ strcpy(cp); /* 4. Ошибка при выполнении */ samp1(2); /* 5. Ошибка при компиляции */ } Пять вызовов (каждый с комментарием) примера демонстрируют различные варианты вызовов функций и прототипов. В вызове No 1 использование функции strcmp явно соответству- ет прототипу, что справедливо для всех случаев. В вызове No 2 обращение к strcpy имеет лишний аргумент (strcpy определена для двух аргументов, а не для трех). В этом случае Турбо Си теряет небольшое количество времени и создает код для помещения лишнего аргумента в стек. Это, однако, не является синтаксической ошибкой, поскольку компилятор не знает о числе ар- гументов strcpy. Такой вызов не допустим для других компиляторов. В вызове No 3 прототип требует, чтобы первый аргумент для samp1 был преобразован к float, а второй - к int. Компилятор вы- даст предупреждение о возможной потере значащих цифр поскольку при преобразовании типа long к типу int отбрасываются старшие би- ты. (Вы можете избавиться от такого предупреждения, если зададите явное преобразование к целому.) Третий аргумент, q, соответствует элипсису в прототипе. Он преобразуется к типу double согласно обычному арифметическому преобразованию. Вызов полностью коррек- тен. В вызове No 4 снова вызывается strcpy, но число аргументов слишком мало. Это вызовет ошибку при выполнении программы. Компи- лятор будет молчать (если даже число параметров отличается от числа параметров в предыдущем вызове той же функции), т.к. для strcpy не определен прототип функции. - 289,290 - В вызове No 5 функции samp1 задано слишком мало аргументов. Т.к. samp1 требует минимум два аргумента, этот оператор является ошибочным. Компилятор выдаст сообщение о том, что в вызове не хватает аргументов. Важное замечание: если ваш прототип функции не соответствует действительному определению функции, то Турбо Си обнаружит это в том и только в том случае, если это определение находится в том же файле, что и прототип. Если вы создаете библиотеку программ с передаваемым набором прототипов (файлом include), то вы должны учитывать включение файла с прототипами во время компиляции биб- лиотеки, с целью выявления любого противоречия между прототипами и действительными определениями функций. Правила видимости (K&R 11) ----------------------------------------------------------------- Турбо Си разрешает более свободное обращение с неуникальными идентификаторами, чем того требует K&R. В Турбо Си различают че- тыре класса идентификаторов: Переменные, имена новых типов, описываемых с помощью typedef, и перечисленные члены должны быть уникальными внутри блока, в котором они описаны. Идентификаторы, объявленные внешни- ми, должны быть уникальными среди переменных, описанных как внеш- ние. Имена структур, объединений и перечислений должны быть уни- кальными внутри блока, в котором они описаны. Эти имена, описан- ные вне пределов какой-либо функции, должны быть уникальны среди всех соответствующих имен, описанных как внешние. Имена членов структуры и объединения должны быть уникальны в структуре или объединении, в которых они описаны. Не существует никаких ограничений на тип или смещение для членов с одинаковыми именами в различных структурах. - 291,292 - Метки на которые ссылаются операторы goto должны быть уникальными внутри функции, в которой они определены. Команды управления трансляцией (K&R 12) ----------------------------------------------------------------- Турбо Си поддерживает все управляющие команды, описанные в K &R. Этими командами являются директивы препроцессора - строки ис- ходной программы, начинающиеся со знака #, за которым или перед которым может следовать символ пробела или табуляции. Замена лексем (K&R 12.1) ----------------------------------------------------------------- Турбо Си поддерживает определения K&R для #define и #undef со следующими дополнениями: 1. Ниже приведенные идентификаторы не должны встречаться в
директивах #define и #undef: _STDC_ _FILE_ _LINE_ - 293,294 - _DATE_ _TIME_ 2. Две лексемы могут быть помещены вместе в макроопределение с разделением их знаками ## (плюс необязательные пробелы с каждой стороны). Препроцессор удаляет пробелы и ##, а также комбинирует разделенные лексемы. Это может быть использовано для создания идентификаторов, например, задав: #define VAR(i,j) (i ## j) при VAR(x,6) обращение вызовет подстановку x6. Это заменяет иногда употребляемую, но не переносимую запись (i/**/j). 3. Вложенные макросы в строке макроопределения сработают лишь тогда, когда сработает сам макрос, а не при его описании. Это больше касается вложенных макросов #undef. 4. Символ #, помещаемый перед макроаргументом, указанном в последовательности, преобразует аргумент в строку. При макроподс- тановке производится замена # - 297,298 - Условная компиляция (K&R 12.3) ----------------------------------------------------------------- Турбо Си поддерживает определение условной компиляции K&R с помощью замены соответствующих строк на пустые. Игнорируемые строки начинаются с директив #if, #ifdef, #ifndef, #else, #elif, и #endif, также как и все некомпилируемые строки, являющиеся ре- зультатом этих директив. Все директивы условной компиляции должны заканчиваться в исходной программе или include файле, в которых они начались. Турбо Си поддерживает также оператор ANSI defined(символ). Значение 1 (true) присваивается, если символ был предварительно определен (с использованием #define) и затем не был отменен (с использованием undef); в противном случае присваивается 0 (false). Так, директива #if defined(mysym) адекватна директиве #ifdef mysym Преимущество в том, что можно повторно использовать defined в сложном выражении, стоящем за директивой #if: #if defined(mysym) || defined(yoursym) Наконец, Турбо Си (в отличие от ANSI) позволяет использовать оператор sizeof в выражениях препроцессора. Так вы можете напи- сать следующее: #if (sizeof(void *) == 2) #define SDATA #else #define LDATA #endif - 299,300 - Управление строками (K&R 12.4) ----------------------------------------------------------------- Турбо Си поддерживает определение #line, данное в K&R. Рас- ширения макросов в #line такие же, как и в #include. Директива error (ANSI Си 3.8.5) ----------------------------------------------------------------- Турбо Си поддерживает директиву #error, которая упоминается (но не определена в полной мере) в ANSI-стандарте. Ее формат: #error errmsg и вызывает сообщение Fatal: filename line# Error directive: errmsg (Фатальная ошибка: Имяфайла строка# Ошибка в директиве: errmsg) У программистов принято включать эту директиву в условный препроцессор, что захватывает некоторые нежелательные состояния во время компиляции. При благоприятном исходе это состояние не будет true. В случае, если это состояние true, вы захотите напе- чатать сообщение об ошибке и остановить компиляцию. Вы вставляете директиву #error внутрь условия, для которого true является неже- лательным исходом. Например, предполагается, что #define MYVAL принимает значения либо 0 либо 1. Вы можете тестировать на некор- ректность значение MYVAL, если включите в ваш исходный код следу- ющее условие: #if (MYVAL != 0 && MYVAL != 1) #error MYVAL должен быть определен только либо 0 либо 1 #endef Препроцессор просматривает текст, уничтожая комментарии, а на экран выводит оставшийся текст без просмотра для выявления макросов. - 301,302 -
Директива pragma (ANSI Си 3.8.6) ----------------------------------------------------------------- Турбо Си поддерживает директиву #pragma, которая (как и error), неясно определяется в стандарте ANSI. Ее целью является разрешить специализированные директивы по форме: #pragma ---------------------------------------------------------------- Ради завершенности, ANSI-стандарт и Турбо Си опознают пустую директиву, состоящую из строки со знаком #. Эта директива всегда игнорируется. Встроенные макроимена (ANSI Си 3.8.8) ---------------------------------------------------------------- ANSI стандарт требует чтобы в реализации было 5 встроенных макросов. Турбо Си применяет все 5. Отметим, что каждый из них начинается и оканчивается символами подчеркивания (__). _LINE_ Номер обрабатываемой строки исходной программы - десятич- ная константа. Первая строка исходного файла определена как 1. _FILE_ Имя обрабатываемого исходного файла - строка символов. Данное макроопределение изменяется всякий раз, при обра- ботке компилятором директивы #include или директивы #line, или по окончании включаемого файла. _DATE_ Дата начала обработки текущего исходного файла - строка символов. Каждое вхождение _DATE_ в заданном файле гарантирует одно значение, независимо от продолжительности обработки. Дата имеет формат mmm dd yyyy, где mmm - месяц (Jan, Feb и т.д.), dd - число текущего месяца (1...31; в первой пози- ции dd ставится пробел, если число меньше 10), yyyy - год (1988, 1989 и т.д.). _TIME_ Время начала обработки текущего исходного файла препроцес- сором - строка символов. Каждое вхождение _TIME_ в заданном файле гарантирует одно значение, независимо от продолжительности обработки. Время имеет формат hh:mm:ss, где hh - час (00...23), mm - минуты (00...59), ss - секунды (00...59). _STDC_ Константа, равная 1, если вы компилируете с (-A) флагом - 307,308 - (ANSI keywords only...ON), устанавливающим совместимость с ANSI стандартом; иначе макроопределение не определено. Встроенные макросы Турбо Си ----------------------------------------------------------------- Для вашего использования препроцессор Турбо Си определяет несколько дополнительных макросов. Также, как для макросов, пред- писанных стандартом ANSI, каждый из них начинается и оканчивается двумя символами подчеркивания. _TURBOC_ Выдает номер текущей версии Турбо Си - шеснадцатиричная константа. Версия 1.0 представляется как 0x0100; версия 1.2 - как 0x0102 и т.д. _PASCAL_ Определяет наличие флага -p; устанавливается в целую константу, равную 1, если используется флаг -p; иначе не определяется. _MSDOS_ Целая константа, равная 1, для всех компиляторов _CDECL_ Сигнализирует о том, что флаг -p не использовался (Calling convention...C); устанавливает целую констан- ту, равную 1, если флаг -p не использовался; иначе не определяется. - 309,310 - Следующие 6 макросов зависят от выбранной для компиляции мо- дели памяти. Для заданной компиляции определяется только одна из них; остальные (по-определению) не определяются. Например, если вы компилируете с малой моделью, то определяется _SMALL_, а ос- тальные - нет; поэтому директива #if defined(_SMALL_) будет иметь значение true (истина), в то время как #if defined(_HUGE_) (или любая другая) будет иметь значение false (ложь). Действительное
значение для любого определенного макроса равно 1. _TINY_ Опция выбора крохотной модели памяти _SMALL_ Опция выбора малой модели памяти _MEDIUM_ Опция выбора средней модели памяти _COMPACT_ Опция выбора компактной модели памяти _LARGE_ Опция выбора большой модели памяти _HUGE_ Опция выбора огромной модели памяти Анахронизмы (K&R 17) ----------------------------------------------------------------- Никаких из упомянутых в K&R анахронизмов в Турбо Си не су- ществует. - 311,312 - Г Л А В А 12 ------------- УГЛУБЛЕННЫЙ КУРС ПО ТУРБО СИ ----------------------------------------------------------------- Рады видеть вас здесь. Эта глава охватывает три основные темы. Во-первых, рассказы- вает о моделях памяти - от крохотной до огромной. Далее - о выбо- ре нужной модели, исходя из конкретной задачи. Затем - о возмож- ности смешивания языков программирования. Вы с этим уже частично знакомы по Главе 10, которая объясняет связь Турбо Си с Турбо Прологом. Здесь же рассматривается смешивание с другими языками, включая Паскаль и ассемблер. После чего предлагается три аспекта низкоуровневого программирования на Турбо Си: встроенный код ас- семблера, псевдопеременные и обработка прерываний. В заключение рассмотрены особенности работы с числами с плавающей точкой. Итак, начнем. Модели памяти ----------------------------------------------------------------- Что такое модели памяти, почему вас должен беспокоить этот вопрос? Для того чтобы ответить на него, вы должны разобраться, на какой вычислительной установке работаете. Ее основной вычисли- тельной единицей должен быть микропроцессор (CPU), принадлежащий к семейству Intel iAPX86, т.е. 8086, 8088, 80186, 80286. Далее мы будем рассматривать 8086. Регистры микропроцессора 8086. ----------------------------------------------------------------- Регистры общего назначения ЙНННННННННЛННННННННН» AX є AH є AL є Аккумулятор МНННННННННОННННННННН№ BX є BH є BL є База МНННННННННОННННННННН№ - 313,314 - CX є CH є CL є Счетчик МНННННННННОННННННННН№ DX є DH є DL є Данные ИНННННННННКНННННННННј Сегментные адресные регистры ЙННННННННННННННННННН» CS є є Сегмент кода МННННННННННННННННННН№ DS є є Сегмент данных МННННННННННННННННННН№ SS є є Сегмент стека МННННННННННННННННННН№ ES є є Дополнительный сегмент ИНННННННННННННННННННј (данных) Cпециальные регистры ЙННННННННННННННННННН» SP є є Указатель стека МННННННННННННННННННН№ BP є є Указатель базы МННННННННННННННННННН№ SI є є Индекс источника МННННННННННННННННННН№ DI є є Индекс получателя - 315,316 - ИНННННННННННННННННННј Рис. 12.1. Регистры 8086. На рис.12.1 показаны регистры микропроцессора 8086 с крат- ким описанием назначения каждого из них. Есть еще 2 регистра: IP (программный счетчик) и регистр флажков, но Турбо Си не имеет к ним доступа, поэтому они здесь не показаны. Регистры общего назначения. ----------------------------------------------------------------- Регистры общего назначения чаще всего используют для хране- ния и обработки данных. Каждый из них имеет некоторые специальные функции, которые выполняет только он. Например: - многие математические операции могут быть выполнены только с использованием AX; - BX может быть использован для хранения смещения в удален- ном указателе (far-указателе); - CX используется в некоторых командах цикла микропроцессо- ра 8086 (LOOP-командах); - DX используется определенными командами для хранения дан- ных. Но имеется много операций, в которых участвуют все эти ре- гистры; в этих случаях вы можете свободно выбирать любой из них. - 317,318 - Сегментные регистры. ----------------------------------------------------------------- Cегментные регистры содержат начальный адрес каждого из 4 сегментов. Как описано в следующем разделе, для того чтобы полу- чить правильный 20-битный адрес этого сегмента, 16-битная величи- на в сегментном регистре сдвигается влево на 4 бита (умножается на 16). Регистры специального назначения. ----------------------------------------------------------------- Микропроцессор 8086 также имеет несколько регистров специ- ального назначения:
- регистры SI и DI могут часто использоваться аналогично регист- рам общего назначения. Кроме того, их используют как индексные регистры. В Турбо Си их также используют как регистровые пере- менные; - регистр SP указывает на текущую вершину стека и является смеще- нием относительно стекового сегмента; - регистр BP является вторичным указателем стека, обычно исполь- зуется для индексирования при поиске параметров, передаваемых в стеке. Базовый индексный регистр (BP) используется в функциях Си как базовый адрес для аргументов и автоматических переменных. Па- раметры имеют положительные смещения относительно BP, которые су- щественно зависят от модели памяти и числа регистров, сохраняемых при вызове функциий. BP всегда указывает на сохраненную ранее ве- личину BP. Функции, которые не имеют параметров и не имеют в объ- явлении аргументов, не должны вообще использовать или сохранять BP. Автоматические переменные имеют отрицательное смещение от BP, первая такая переменная имеет наибольшее отрицательное смеще- ние. - 319,320 - Сегментация памяти. ---------------------------------------------------------------- Микропроцессор 8086 имеет сегментированную архитектуру памя- ти. Он обеспечивает общее адресное пространство величиной 1 Мб, однако может прямо адресовать только 64К. Логическую единицу па- мяти размером 64К называют сегментом, отсюда и название "сегмен- тированная архитектура памяти". Теперь возникают вопросы: сколько различных сегментов су- ществует, где они располагаются и как 8086 понимает, где они рас- положены? - 8086 работает с 4 различными сегментами: кода, данных, стека и дополнительных данных.Кодовый сегмент содержит машинную программу; сегмент данных - данные; стековый сегмент - стек; до- полнительный сегмент обычно используют для дополнительных дан- ных; - 8086 имеет 4 16-битных сегментных регистра, называемых CS, DS, SS и ES; они указывают на сегменты кода, данных, стека и до- полнительных данных соответственно; - сегмент может быть расположен в любом месте памяти.По причине, которая будет рассмотрена далее, сегмент всегда начина- ется с адреса, кратного 16 (в десятичной системе). Вычисление адреса ----------------------------------------------------------------- Итак, каким же образом 8086 использует эти сегментные регис- тры для вычисления адреса? Полный адрес 8086 получает из 2-х 16-битных величин: адреса сегмента и смещения. Предположим, что адрес сегмента данных - величина в DS регистре - 2F84 (HEX-код), и вы хотите вычислить реальный адрес некоторых данных, которые имеют смещение 0532 (HEX-код) от начала сегмента данных. Как это делается? Вычисление адреса происходит следующим образом: величина сегментного регистра сдвигается на 4 бита (1 разряд HEX-кода) влево и затем складывается со смещением. Полученная в результате 20-битная величина есть реальный адрес данных, что иллюстрирует - 321,322 - следующая запись: DS регистр (сдвинутый) 0010 1111 1000 0100 0000 = 2F840 смещение 0000 0101 0011 0010 = 00532 адрес 0010 1111 1101 0111 0010 = 2FD72 Начальный адрес сегмента - всегда 20-битное число, однако сегментный регистр содержит только 16 бит. Т.о., младшие 4 бита всегда предполагаются равными нулю. Это означает, как мы уже от- мечали, что сегменты могут начинаться только с адреса, кратного 16-и, т.е. с адреса, в котором младшие 4 бита (младшая HEX-цифра) нулевые. T.о., если DS-регистр содержит величину 2F84, то сегмент данных реально начинается с адреса 2F840. Как отмечалось, отрезок памяти из 16 байт называется параграфом, т.е. этот сегмент всегда начинается на границе параграфа. Стандартно адрес представляется =, < и <=) исполь- зуют только смещение. Для операторов == и != необходимы все 32 бита для того, что- бы можно было произвести сравнение с NULL указателем (0000:0000).В противном случае, если для проверки равенства вы используете только смещение, то любой указатель со смещением 0000 будет равен NULL указателю, а это не является тем, что вы хотите. Еще вам необходимо знать следующее. Если вы прибавляете ве-
личину к far-указателю, то изменяется только смещение. Если ре- зультат сложения превышает величину FFFF (это максимально возмож- ная величина смещения), то указатель делает циклический переход назад, к началу сегмента. Например, результатом прибавления 1 к 5031:FFFF, будет 5031:0000 (а не 6031:0000). Аналогично, если вы вычитаете 1 из 5031:0000, то получите 5031:FFFF (а не 5030:000F). Если вы хотите сравнить указатели, то надежнее использовать или near-указатели, которые всегда используют один и тот же адрес сегмента, или huge-указатели, описание которых следует далее. Указатели типа HUGE ----------------------------------------------------------------- Указатели типа huge (также как и far) имеют 32-битную длину и содержат как адрес сегмента, так и смещение. Однако, в отличие от far, они нормализованы с целью решения проблем, рассмотренных в предыдущем разделе. - 327,328 - Что такое указатель типа huge? Это 32-битный указатель, ко- торый имеет наибольшую из возможных величину адреса сегмента. Так как сегмент всегда начинается с адреса, кратного 16 (или 10h), то это означает, что смещение может принимать значения от 0 до 15 (от 0h до Fh). Как нормализовать указатель? Очень просто: переведите его в 20-битный адрес, затем используйте правые 4 бита для смещения и левые 16 бит для адреса сегмента. Например, дан указатель 2F84:0532; мы переведем его в абсолютный адрес 2FD72, который за- тем нормализуется в 2FD7:0002. Приведем несколько указателей и их нормализованные значения: 0000:0123 0012:0003 0040:0056 0045:0006 500D:9407 594D:0007 7418:D03F 811B:000F Теперь вы знаете, что указатели типа huge всегда хранятся нормализованными. Почему это важно? Потому, что для любого данно- го адреса памяти имеется только один возможный указатель типа huge - пара "сегмент:смещение". Это означает, что операторы >, >=, МНННННННННННННННННННННННННННННН№ до конца є FAR ХИП є памяти МНННННННННННННННННННННННННННННН№ є Свободная область є ИННННННННННННННННННННННННННННННј Конеч.адрес. Рис.12.3. Малая модель сегментации памяти
- 335,336 - Вложенные sfiles ЪДДДДДДДДДДї(CS-указатель одновременно іsfileA ітолько на 1 sfile) CS -->іsfileB і ЪДґ... і Сегментные і іsfileZ і Размер регистры: і АДДДДДДДДДДЩ Нач.адрес. сегмента: CS -->ЙНННННіНННННННННННННННННННННННННН» єЪДДДДБї єкаждый sfile єіsfileі_TEXTclass'CODE'программаєдо 64К єАДДДДДЩ є DS,SS -->МНННННННННННННННННННННННННННННННН№ Й є_DATA class 'DATA' иниц.данные є » є МНННННННННННННННННННННННННННННННН№ є є є_BSS class 'BSS' не иниц.данные є є є МНННННННННННННННННННННННННННННННН№ є DGROUР Н№ є ХИП є МН до 64К є МНННННННННННННННННННННННННННННННН№ є є є Свободная область є є SP(TOS)-->МНННННННННННННННННННННННННННННННН№ є И є СТЕК є ј Стартовый SP-->МНННННННННННННННННННННННННННННННН№ до конца є FAR ХИП є памяти МНННННННННННННННННННННННННННННННН№ є Свободная область є ИННННННННННННННННННННННННННННННННј Конеч.адрес. Рис. 12.4. Средняя модель сегментации памяти - 337,338 - Сегментные Размер регистры: сегмента: Нач.адрес. CS,DS -->ЙНННННННННННННННННННННННННННННН» є_TEXT class 'CODE'Программа є до 64К МНННННННННННННННННННННННННННННН№ Й є_DATA class 'DATA'иниц.данные є » DGROUP Н№ МНННННННННННННННННННННННННННННН№ МН до 64К И є_BSS CLASS 'BSS' 'иниц.данные є ј SS -->МНННННННННННННННННННННННННННННН№ є Свободная область є SP(TOS)-->МНННННННННННННННННННННННННННННН№ є СТЕК є до 64К Стартовый SP-->МНННННННННННННННННННННННННННННН№ до конца є ХИП є памяти МНННННННННННННННННННННННННННННН№ є Свободная область є ИННННННННННННННННННННННННННННННј Конеч.адрес. Рис.12.5. Компактная модель сегментации памяти Вложенные sfiles ЪДДДДДДДДДДї(CS-указатель одновременно іsfileA і только на 1 sfile) CS -->іsfileB і ЪДґ... і Сегментные і іsfileZ і Размер регистры: і АДДДДДДДДДДЩ Нач.адрес. сегмента: CS -->ЙНННННіНННННННННННННННННННННННННН» єЪДДДДБї є Каждый sfile іsfileB і(DS-указатель одновременно і... і только на 1 sfile) іsfileZ і АДДДДДДДДДДЩ Вложенные sfiles Рис.12.7. Огромная модель сегментации памяти Таблица 12.1 обобщает информацию о различных моделях и пока- зывает их в сравнении. Модели группируют в зависимости от того, какова у них величина программы и данных, т.е. маленькая (64К) или большая (1М). Эти группы соответствуют колонкам и столбцам в таблице 12.1. Так, например, крохотную, малую и компактную модели называют "моделями с малыми программами" потому, что, по умолча- нию, указатели в таких программах только near. Аналогично ком- пактную, большую и огромную модели называют "моделями с большими данными" потому, что, по умолчанию, указатели к данным в них только far. Заметим, что это также верно для огромной модели: по умолчанию, указатели к данным только far, а не нормализованные (huge). Если вы хотите использовать huge-указатели к данным, то вы должны предварительно объявить их как huge.
- 343,344 - ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН РАЗМЕР ДАННЫХ РАЗМЕР ПРОГРАММЫ ННННННННННННННННННННННННННННННННННННННННННННННННННН 64К 1М ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН 64К Крохотная(данные и програм- ма перекрываются; общий размер 64К) Малая (не перекрываются; Средняя общий размер 128К) (малые данные,большая программа) 1М Компактная Большая (большие данные,малая (большие данные и программа) программы) Огромные (аналогично большой, только данные больше 64К ) ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН Таблица 12.1. Модели памяти. Важное замечание. Когда вы компилируете модуль (указывая ис- ходный файл, содержащий несколько функций), результирующая прог- рамма не может быть более 64К, т.к. все функции должны включаться в один программный сегмент. Это верно, даже если вы используете модели с большими программами (среднюю, большую и огромную). Если ваш модуль больше величины одного программного сегмента (64К), то вы должны разделить его на разные исходные программные файлы, от- компилировать каждый файл в отдельности и затем объединить их вместе. Проще говоря, хотя огромная модель и позволяет статичес- ким данным занимать объем больше, чем 64К, в каждом модуле все равно должно быть меньше, чем 64К. - 345,346 - Порядок программирования смешанных моделей памяти: модификация типа адресации ----------------------------------------------------------------- Турбо Си вводит семь новых ключевых слов, отсутствующих в стандартном (Керниган и Ритчи или ANSI) Си: near, far, huge, _cs, _ds, _es, _ss. С некоторыми ограничениями и предупреждениями они могут использоваться как модификаторы указателей (в отдельных случаях функций). В Турбо Си вы можете модифицировать функции и указатели, ис- пользуя ключевые слова near, far или huge. Ранее в этом разделе мы объясняли суть near-, far- и huge- указателей к данным. Near- функции вызываются ближними запросами и заканчиваются ближними возвратами. Far-функции, соответственно, вызываются удаленно и завершаются удаленными возвратами. Huge-функции подобны far-функ- циям, но, в отличие от них, huge могут изменять содержимое ре- гистра DS. Есть также четыре специальных near-указателя к данным: _cs, _ds, _es, _ss. Это 16-битные указатели, которые специфически свя- заны с соответствующими сегментными регистрами. Например, если вы объявите указатель char _ss *p ; то p будет содержать 16-битное смещение в стековом сегменте. Функции и указатели в программе по умолчанию устанавливаются near или far в зависимости от выбранной вами модели памяти. Если функция или указатель near, то она автоматически связывается либо с CS, либо с DS регистром. Таблица 12.2 показывает, как это происходит. Заметим, что ве- личина указателя соответствует или работе в рамках 64К (near, внутри сегмента), или работе в адресном пространстве до 1М (far, имеет свой собственный адрес сегмента) - 347,348 - НННННННННННННННННННННННННННННННННННННННННННННННННННННННННН Модель памяти Указатель к функции Указатель к данным НННННННННННННННННННННННННННННННННННННННННННННННННННННННННН Крохотная near,_cs near,_ds Малая near,_cs near,_ds Компактная far near,_ds Средняя near,_cs far Большая far far Огромная far far НННННННННННННННННННННННННННННННННННННННННННННННННННННННННН Таблица 12.2. Результирующие указатели Объявление функций как NEAR или FAR ----------------------------------------------------------------- Иногда вам может захотеть (или понадобиться) отменить тип функции, принимаемый по умолчанию для вашей модели памяти, пока- занной в таблице 12.1. Например, предположим, вы используете большую модель памяти, но имеете в вашей программе рекурсивную (самовызывающуюся) функцию, такую как double power (double x, int exp) { if (exp <= 0) return(0); else return(x*power(x,exp-1)); } Power постоянно вызывает сама себя. Это делается с помощью удаленного вызова, что требует большего размера стека и более длительных циклов. Объявив power как near, вы снимете некоторые расходы, заставляя все вызовы к этой функции быть ближними: - 349,350 - double near power (double x, int exp) Такое описание гарантирует, что power будет вызываема только из того программного сегмента, в котором она оттранслирована, и все ее вызовы будут near. Это означает, что если вы используете большую программную модель (среднюю, большую или огромную), вы можете вызывать power только в том модуле, где она определена. Другие модули имеют свои собственные программные сегменты и, следовательно, не могут вызы- вать near-функции в других модулях. Более того, near-функция должна быть определена или объявлена до первого ее использования, иначе компилятор не сможет понять, что необходимо генерировать near-вызов. Наоборот, объявление функции far означает, что и возврат бу- дет far. В малых программных моделях far-функции должны быть объ- явлены или определены до первого их использования, что обеспечи- вает их запуск через far-вызов. Вернемся к примеру с power. Теперь понятно также, что необ- ходимо объявить power как статическую, т.к. она будет вызываться только внутри текущего модуля. В этом случае, если она будет ста- тической, ее имя станет недоступно любой функции, находящейся вне модуля. Так как power всегда задействует фиксированное число ар- гументов, вы можете еще больше ее оптимизировать, объявив ее как pascal, а именно: static double near pascal power (double x, int exp) - 351,352 -
Объявление указателей как NEAR, FAR или HUGE ----------------------------------------------------------------- Вы уже видели, зачем вам может понадобиться объявлять функ- ции для различных моделей памяти, применяемых в программе. А за- чем может понадобиться делать то же самое с указателями? По при- чинам, что уже описанны выше: во избежание необоснованного расширения (объявив near, когда по умолчанию должно быть far) или чтобы обратиться к чему-либо, находящемуся вне допустимого сег- мента (объявив far или huge, когда по умолчанию должно быть near). Встречаются, конечно, потенциальные ловушки в присвоении функциям и указателям типов, отличных от принятых по умолчанию. Например, предположим, вы имеете следующую малую модель програм- мы: void myputs(s) char *s; { int i; for (i = 0; s[i] != 0; putc(s[i]); } main() { char near *mystr; mystr = "Hello, world\n"; myputs(mystr); { Эта программа работает прекрасно, а объявление mystr как near излишне, т.к. все указатели, как программы, так и данных, будут near. Но что будет, если вы перекомпилируете эту программу, ис- пользуя компактную (или большую, или огромную) модель памяти? Указатель mystr в main будет все еще near (это по-прежнему 16-би- товый указатель). Однако указатель s в myputs теперь far, т.к. это устанавливается по умолчанию. Это означает, что myputs будет выбирать 2 слова из стека, создавая far-указатель, и полученный адрес будет, несомненно, другим, чем у mystr. - 353,354 - Как вам решить эту проблему? Решается она следующим образом: myputs объявляется в стиле современного Си, т.е.: void myputs(char *s); { /*body of myputs*/ } Теперь, когда Турбо Си компилирует вашу программу, ему из- вестно, что myputs предполагает указатель на char, и, т.к. вы компилируете в рамках большой модели, указатель должен быть far. Поэтому Турбо Си будет помещать регистр сегмента данных (DS) в стек совместно с 16-битной величиной mystr, формируя far-указа- тель. Как быть в обратном случае: параметры для myputs объявлены как far и компилируются для малой модели памяти? Без использова- ния прототипа функции вы столкнетесь с проблемами, т.к. main бу- дет помещать в стек как смещение, так и адрес сегмента, но myputs предполагает только смещение. При определении прототипа функции main будет помещать в стек только смещение. Вывод: если вы собираетесь использовать явное объявление указателей как near или far, то надежнее всего использовать про- тотипы функций для любых функций, где вы планируете их использо- вать. - 355,356 - Способ указания на данный сегмент: Offset адрес (смещение) ----------------------------------------------------------------- Каким образом вы можете указатель типа far разместить в дан- ной ячейке памяти (специальном сегменте - offset адресе)? Вы мо- жете воспользоваться встроенной библиотечной подпрограммой MK_FP, которая выбирает сегмент и смещение и возвращает far- указатель. Например: MK_FP(segment_value, offset_value) Данный far-указатель (fp) может быть и сегментом FP_SEG(fp), и смещением FP_OFF(fp). Для более детального ознакомления с этими тремя библиотечными подпрограммами Турбо Си следует обратиться к "Справочному Руководству по Турбо Си". Построение простых операторов объявления ----------------------------------------------------------------- Операторы объявления в Си вы используете для объявления ти- пов функций, переменных, указателей и данных. Си позволяет вам строить комплексные операторы объявления. В этом разделе даются некоторые примеры операторов объявления, которые могут помочь вам в их построении (и прочтении); будет также показано, как избежать некоторые ловушки. Традиционно программирование на Си дает возможность сразу строить полный оператор объявления, вставляя при необходимости определения. К сожалению, это делает программы сложными для чте- ния (и написания). Например, рассматриваемые в таблице 12.3 операторы объявле- ния предполагают, что вы компилируете в рамках малой модели памя- ти (малая программа, малые данные). ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН int f1(); Функция, возвращающая целое. - 357,358 - int *p1; Указатель на целое. int *f2(); Функция, возвращающая указа- тель на целое. int far *p2; Far- указатель на целое. int far *f3(); Near- функция, возвращающая far- указатель на целое. int * far f4(); Far-функция, возвращающая near-указатель на целое. int (*fp1) (int); Указатель на функцию, возвра- щающую целое и принимающую целое. int (*fp2) (int *ip); Указатель на функцию, возвра- щающую целое и принимающую указатель на целое. int (far *fp3)(int far *ip) Far-указатель на функцию,воз- вращающую целое и принимающую far-указатель на целое. int (far *list[5]) (int far *ip); Массив из 5 far-указателей на функции, возвращающие целое и принимающие far- указатели на целое. int (far *gopher(int (far *fp[5])(int far *ip)))(int far *ip); Near-функция, принимающая мас- сив из 5 far-указателей на фу- нкции, возвращающие целое и принимающие far- указатели на целое, и возвращающая один из этих указателей НННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН Таблица 12.3. Объявление указателей без typedef - 359,360 - Здесь представлены все допустимые операторы объявления в по- рядке возрастания трудности их восприятия. Однако, благоразумно используя typedef, вы можете усилить четкость восприятия этих операторов. Ниже представлены те же операторы, записанные с использова- нием typedef.
ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН int (f1); Функция, возвращающая целое. typedef int *intptr; intptr p1; Указатель на целое. intptr f2(); Функция, возвращающая указа- тель на целое. typedef int far *farptr; farptr p2; Far-указатель на целое. farptr f3(); Near-функция, возвращающая far-указатель на целое. intptr far f4(); Far-функция, возвращающая near-указатель на целое. typedef int (*fncptr1)(int); fncptr1 fp1; Указатель на функцию, воз- вращающую целое и принимаю- щую целое. typedef int (*fncptr2)(intptr); fncptr2 fp2; Указатель на функцию, воз- вращающую целое и принимаю- щую указатель на целое. typedef int (far *ffptr)(far ptr); ffptr fp3; Far - указатель на функцию, вовращающую целое и принима- ющую far-указатель на целое. typedef ffptr ffplist[5]; ffplist list; Массив из 5 far- указателей на функции, возвращающие це- лое и принимающие far-указа- ли на целое. - 361,362 - ffptr gopher(ffplist); Near-функция,принимающая мас- сив из 5 far-указателей на функции, возвращающие целое и принимающие far-указатели на целое, и возвращающая один из этих указателей. ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН Таблица 12.4. Объявление указателей при помощи typedef Как видите, есть значительная разница в наглядности и чет- кости восприятия между объявлением gopher при помощи typedef и предыдущим способом объявления. При грамотном использовании опе- раторов типа typedef и прототипов функций вам легче будет состав- лять, отлаживать и сопровождать ваши программы. Использование библиотечных файлов ---------------------------------------------------------------- Турбо Си предлагает версию стандартных библиотечных подпрог- рамм для каждой из шести моделей памяти. Применяясь для работы в интегрированной среде (TC), Турбо Си достаточно хорошо приспособ- лен для компоновки требуемых библиотек в нужном порядке в зависи- мости от выбранной вами модели. Кроме того, будучи применен как автономный компилятор (TCC), Турбо Си достаточно хорошо приспо- соблен для автоматической компоновки. Однако, если вы используете непосредственно TLINK (компонов- щик Турбо Си) как автономный редактор связей, то вам необходимо точно определить, какие библиотеки использовать. Если вы не соби- раетесь использовать все 6 моделей памяти, то вам нужно перепи- сать на рабочий или жесткий диск лишь файлы используемой модели (моделей). - 363,364 - Ниже приведен список библиотечных файлов для каждой модели памяти. ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН Крохотная COT.OBJ, MATHS.LIB, CS.LIB Малая COS.OBJ, MATHS.LIB, CS.LIB Компактная COС.OBJ, MATHC.LIB, CC.LIB Средняя COM.OBJ, MATHM.LIB, CM.LIB #include "myputs.h" main() { char near *mystr; mystr = "Hello, world\n"; myputs(mystr); } Когда вы компилируете эту программу, Турбо Си читает прото- тип функции из MYPUTS.H и воспринимает его как far-функцию, пред- - 367,368 - полагающую наличие far-указателя. Поэтому он сформирует надлежа- щую программу, даже если она скомпилирована с использованием ма- лой модели памяти. Наконец, как же поступать, если вам надо выполнить компонов- ку с библиотечными подпрограммами? Лучше всего в этом случае ис- пользовать одну из библиотек больших моделей, объявив все ее ком- поненты как far. Для этого скопируйте каждый заголовочный файл, который вы обычно используете (такой как STDIO.H) и переименуйте соответствующим образом копии (например, FSTDIO.H). После этого отредактируйте каждый скопированный прототип функции таким образом, чтобы он был однозначно far, например: int far cdecl printf(char far * format, ...);
В этом случае будут не только выполняться far-вызовы подп- рограмм, но и передавемые указатели так же будут far. Модифици- руйте вашу программу таким образом, чтобы она включала в себя но- вый файл заголовка: #include из стека параметры, так как это сделает вызывающая программа. Например, программа на ассемблере, созданная транслятором из ис- ходной программы на Си, для главной функции выглядит так: mov word ptr [bp-8],5 ; i = 5 mov word ptr [bp-6],7 ; j = 7 mov word ptr [bp-2],0014h ; k = 0x1407AA mov word ptr [bp-4],07AAh push word ptr [bp-2] ; Push старший полубайт k push word ptr [bp-4] ; Push младший полубайт k push word ptr [bp-6] ; Push j push word ptr [bp-8] ; Push i call near ptr funca ; вызов funca (push addr) add sp,8 ; восстановить стек Обратим внимание на последнюю команду: add sp,8. Транслятор знает, сколько параметров помещено в стеке в данный момент; кроме того он знает, что адрес возврата, помещенный в стек во время вы- зова funca, уже вынут из него командой ret в конце funca. Последовательность передачи параметров типа Паскаль ----------------------------------------------------------------- Другой метод передачи параметров - стандартный метод типа Паскаль (известный также как Паскаль-соглашение по вызову). Но - 373,374 - нельзя сказать, что вы можете вызывать функции Турбо Паскаля из Турбо Си: это неверно. Рассматриваемая последовательность заносит параметры слева направо; таким образом, если funca объявлена как void pascal funca (int p1, int p2, int p3 ); то при вызове этой функции параметры в стек заносятся слева нап- раво (p1, p2, p3), а следом за ними в стек помещается адрес возв- рата. Таким образом, если вы даете вызов main () { int i,j; long k; ... i = 5; j = 7; k = 0x1407AA; funca(i,j,k); ... } то стек будет выглядеть так (в момент перед занесением в стек ад- реса возврата): SP+06: 0005 i = p1 SP+04: 0007 j = p2 SP+02: 0014 SP: 07AA k = p3 В чем же проявляется отличие такого типа последовательности передачи параметров от рассмотренного ранее? Кроме изменения по- рядка занесения параметров, последовательность передачи парамет- ров типа Паскаль предполагает, что вызванная подпрограмма (funca) знает, сколько параметров передано и, соответственно, помещено в стек. Другими словами, подпрограмма вызова funca на ассемблере теперь выглядит так: push word ptr[bp-8] ; Push i push word ptr [bp-6] ; Push j push word ptr [bp-2] ; Push старший байт k push word ptr [bp-4] ; Push младший байт k call near ptr funca ; вызов funca (Push addr) Заметим, что здесь нет команды add sp,8 после вызова. Вместо этого funca использует команду ret 8 при завершении для того, - 375,376 - чтобы очистить стек перед возвратом к main. По умолчанию все функции, написанные на Турбо Си, используют метод передачи пара- метров типа Си. Исключение составляет случай, когда вы используе- те -р опцию транслятора (соглашение по вызову...Паскаль), когда все функции используют метод типа Паскаль. В этой ситуации вы мо- жете заставить нужную функцию применить метод передачи параметров типа Си с помощью модификатора cdecl: void cdecl funca (int p1, int p2, int p3); Этот оператор игнорирует -p опцию. Зачем вообще нужно Паскаль-соглашение по вызову? По трем ос- новным соображениям: - Вы можете быть вызваны подпрограммой, написаной на ассемб- лере, которая использует данное соглашение по вызову. - Вы можете быть вызваны подпрограммами, написаными на дру- гих языках. - Вызов создаваемой программы будет менее громоздким, пос- кольку теперь не нужно очищать стек после ее окончания.
Какие проблемы могут возникнуть при использовании Пас- каль-соглашения по вызову? Во-первых, оно не так сильно, как Си-соглашение по вызову. Вы не можете передать переменное число параметров (что возможно в случае Си-соглашения), т.к. вызванная подпрограмма должна знать, сколько параметров заносится в стек и, соответственно, извлекает- ся из него. Передача слишком малого или слишком большого коли- чества параметров будет приводить к серьезным трудностям, в то время как при Си-соглашении это не даст нежелательного эффекта (за исключением, возможно, неверных ответов). Во-вторых, если вы используете -р опцию транслятора, то вы должны обязательно включить файлы заголовков для стандартных Си-функций, которые вы вызываете. Это необходимо потому, что если вы этого не сделаете, Турбо Си будет использовать Паскаль-согла- шение для вызова каждой из этих функций, и ваша программа навер- няка потерпит крах, потому что: 1) параметры будут в неверном порядке; - 377,378 - 2) ничем не будет очищаться стек. Файлы заголовков объявляют каждую из таких функций как cdecl, поэтому, если вы включете их, транслятор увидит это и бу- дет использовать Си-соглашение по стеку. Однако, т.к. идентифика- торы cdecl дополняются символом подчеркивания, в отличие от иден- тификаторов Pascal, то при компоновке вы получите множество ошибок, если не установите режим Generate undebars "Off". Потом у вас будут большие неприятности. Заключение: если вы собираетесь использовать Паскаль-согла- шение по вызову в программе Турбо Си, то убедитесь, что каждая функция явно объявлена как cdecl или pascal. Это очень полезно в случае использования опции компиляции о требовании наличия прото- типа, т.к. в этом случае вы можете быть уверены, что каждая функ- ция вызывается строго в соответствии с ее прототипом. Интерфейс с языком ассемблера ----------------------------------------------------------------- Теперь вы знаете, как работает каждое соглашение по вызову и что в этом случае делает компилятор Турбо Си. Что же вам необхо- димо делать в вызываемой подпрограмме? Расмотрим это на примере написанной на ассемблере подпрограммы, которую можно вызывать из Турбо Си. Замечание: в этом разделе мы подразумеваем, что вы знаете, как писать программы на языке ассемблера для 8086 и как опреде- лять сегменты, константы данных и т.п. Если вы не знакомы с этими вопросами, то можете ознакомиться с ними в Turbo Assembler Reference Guide. Порядок вызова ассемблера из Турбо Си ----------------------------------------------------------------- Вы можете написать подпрограммы на языке ассемблера в виде модулей и скомпоновать их с вашими программами на Си. Однако име- - 379,380 - ются определенные соглашения, которым вы должны следовать: 1) убедиться, что компоновщик может получить необходимую ин- формацию; 2) убедиться, что формат файла согласуется с типом модели памяти, используемой в вашей программе на Си. Ниже приведена общая схема программы: =filname_DATA ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН - 383,384 - Таблица 12.5. Идентификаторы замещения и модели памяти Определение констант данных и переменных ----------------------------------------------------------------- Выбор модели памяти влияет также на способ определения конс- тант данных, являющихся указателями к программе, данным или к то- му и другому сразу. Табл.12.5 показывает, как будут выглядеть,эти указатели. Здесь ххх - адрес, являющийся указателем. Обратите внимание на то, что одни указатели используют ко-
манду DW (резервировать слово), в то время как другие - DD (ре- зервировать двойное слово), что определяет размер результирующего указателя. Числовые и текстовые константы определяются как обыч- но. Переменные определяются так же, как константы. Если вы хоти- те получить переменные, которые не инициализированы определенными значениями, вы можете объявить их в _BSS-сегменте, поставив воп- росительный знак (?) там, куда вы потом поместите переменные. - 385,386 - Определение глобальных и внешних идентификаторов ----------------------------------------------------------------- Создание вами модуля не принесет большого успеха до тех пор, пока ваша программа на Турбо Си не будет знать, какие функции она может вызывать и на какие переменные может ссылаться. Кроме того, вам может потребоваться возможность вызывать функции Турбо Си из подпрограмм, написанных на ассемблере или ссылаться на перемен- ные, объявленные в Турбо Си. Чтобы освоить эти возможности, вам нужно разобраться в трансляторе и компоновщике Турбо Си. Когда вы объявляете внешний идентификатор, транслятор автоматически ставит перед ним знак подчеркивания (_) до его сохранения в объектном модуле. Это озна- чает, что вы должны поставить знак подчеркивания перед любым идентификатором в вашем ассемблеровском модуле, к которому вы хо- тите получить доступ из программы на Си. К Паскаль-идентификато- рам обращение идет иначе, чем к идентификаторам Си, - они исполь- зуют только верхний регистр и не снабжаются символом подчеркивания. Знак подчеркивания для идентификаторов Си необязателен, он устанавливается по умолчанию(on). Он может быть отключен (off) с помощью опции командной строки -u-. Однако, если вы используете стандартные библиотеки Турбо Си, то столкнетесь с проблемами, ес- ли только вы не переделали эти библиотеки. (Чтобы сделать это, вам понадобится другой продукт Турбо Си - исходный текст программ для библиотек поддержки; контактируйте с фирмой Borland International для получения более подробной информации.) Если любая asm-программа в исходном файле ссылается на любой Си-идентификатор (данные или функции), то такие идентификаторы должны начинаться со знака подчеркивания. Турбо Ассемблер (TASM) не чувствителен к регистру, другими словами, когда вы транслируете программу с языка ассемблера, то все идентификаторы сохраняются только в заглавных символах. Ис- пользование в TASM опции /mx переводит его в режим "чувствитель- ности регистра" для внешних указателей. Компоновщик Турбо Си де- лает то же самое с помощью идентификатора extern, т.о. эти моменты хорошо совпадают. Вы можете заметить, что в наших приме- рах ключевые слова и директивы записаны в верхнем регистре, а все другие идентификаторы и коды команд - в нижнем (строчными симво- лами); это совпадает со стилем, предлагаемым в руководстве по - 387,388 - TASM. Однако, вы вольны использовать все заглавные (или строчные) или любые смешанные идентификаторы, как вам удобно. Для того чтобы сделать идентификаторы (имена подпрограмм и переменных) видимыми извне вашего ассемблерного модуля, вам нужно объявить их как PUBLIC. Так, например, если вы написали модуль, имеющий целые функ- ции max и min и целые переменные MAXINT,lastmax и lastmin, вы должны вставить в него оператор PUBLIC _max,_min в ваш программный сегмент, а операторы PUBLIC _MAXINT, _lastmax, _lastmin _MAXINT DW 32767 _lastmin DW 0 _lastmax DW 0 в сегмент данных. Порядок вызова Турбо Си из .ASM ----------------------------------------------------------------- Для того чтобы созданный вами модуль на ассемблере мог обра- щаться к функциям и переменным, объявленным в программе на Турбо Си, используйте оператор EXTRN. Указатели на функции ----------------------------------------------------------------- нужно использовать размер наиболее часто встречающегося в структуре элемента. Т.о., если ваша Си-программа имела глобальные переменные int i, jarray [10]; char ch; long result; то вы можете сделать их доступными в вашем модуле с помощью сле- дующего оператора: EXTRN _i:WORD,_jarray:WORD,_ch:BYTE,_result:DWORD И последнее важное замечание. Если вы используете модель па- мяти huge, то операторы EXTRN должны появляться вне любого сег- - 391,392 - мента. Это применимо как к процедурам, так и к переменным. Создание подпрограмм на ассемблере ----------------------------------------------------------------- Теперь вы знаете, каким образом все должно быть определено, и настало время ознакомиться, как вообще записываются функции на ассемблере. Здесь необходимо рассмотреть несколько важных момен- тов: передача параметров, возвращаемые значения и соглашения по регистрам. Предположим, вы хотите написать функцию min, которая, как вы полагаете, имеет следующий прототип в Си: int extern min(int v1, int v2); Вы хотите, чтобы функция min вернула минимальную из двух пе- реданных ей величин. В общем виде запись функции min будет:
PUBLIC _min _min PROC near . . . - 393,394 - _min ENDP Это верно в предположении, конечно, что функция min будет near-функцией. Если это far-функция, то вы должны заменить far на near. Заметим, что min снабжено знаком подчеркивания для того, чтобы компоновщик Турбо Си мог корректно установить связи. Передача параметров ----------------------------------------------------------------- Ваша первая задача - выбрать используемое в подпрограмме соглашение по передаче параметров; учитывая приведенные ранее со- ображения о трудностях использования Паскаль-соглашения, будем избегать его и использовать Си-метод. Это означает, что когда вызвана функция min, стек выглядит следующим образом: SP + 04: v2 SP + 02: v1 SP: адрес возврата Если вы хотите получить параметры, не изменяя содержания стека, то для зтого необходимо сохранить базовый указатель (BP), затем занести стековый указатель (SP) в базовый и использовать его в качестве указателя на стек для извлечения из него необходи- мых вам величин. Заметим, что после занесения BP в стек относи- тельные смещения параметров возрастут на 2, т.к. в стек будут до- бавлены 2 байта. - 395,396 - Управление возвращаемыми величинами ---------------------------------------------------------------- Ваша функция возвращает целую величину; куда вы ее помести- те? Для 16-битных (2-байтных) величин (char, shot, int, enum, near-указатели) вы используете регистр AX; для 32-битных (4-байт- ных), включающих far- и huge-указатели - регистр DX для старшего слова (или для адреса сегмента у указателей) и регистр AX для младшего слова. Величины float, double и long double возвращаются в регистре top-of-stack (TOS) процессора 8087/80287, ST(0); если использует- ся эмулятор 8087/80287, то величина возвращается в TOS регистр эмулятора. Величины структур возвращаются путем занесения их в стати- ческую ячейку памяти и передачей указателя на эту ячейку (в АХ для моделей с малыми данными, в DX:АХ для моделей с большими дан- ными). Вызывающая функция должна затем скопировать эту величину в любом месте, где она необходима. Структуры длиной в 1 и 2 байта возвращаются в АХ (подобно любой обычной целой), а 4-байтные структуры в АХ и DX. В примере с min вы имеете дело с 16-битной величиной, поэто- му можете поместить ответ в АХ. Ваша программа выглядит теперь так: PUBLIC _min _min PROC near push bp ; Сохранить bp в стеке mov bp,sp ; Загрузить sp в bp mov ax,[bp+4] ; Переместить v1 в ax cmp ax,[bp+6] ; Cравнить v1 и v2 jle exit ; если v1 > v2 mov ax,[bp+6] ; Загрузить в ax v2 exit: pop bp ; Восстановить bp v2 mov ax,[bp+4] ; Загрузить в ax v2 exit: pop bp ; Восстановить bp ret 4 ; Очистка стека и возврат MIN ENDP И последний пример, демонстрирующий, почему вам может потре- боваться использовать Си-соглашение по передаче параметров. Пред- положим, вы переопределили min следующим образом: int extern min (int count, int v1, int v2, ...); Теперь min может получать любое количество целых чисел и бу- дет возвращать минимальное из них. Однако, т.к. min не имеет воз- можности автоматически узнавать, сколько значений ей передано, то первым ее параметром будет величина count, указывающая, сколько величин следуют за ней. Например, вы можете записать это так: i = min (5, j, limit, indx, lcount, 0); предполагая, что i, j, limit, indx и lcount - целые (или совмес- тимого с int типа). Стек при входе будет выглядеть так: SP + 08: (и т.д.) SP + 06: v2 SP + 04: v1 SP + 02: count (счетчик) SP: адрес возврата (смещение) Теперь модифицированная версия min имеет вид: - 401,402 -
PUBLIC _min _min PROC near push bp ; Сохранить bp в стеке mov bp,sp ; Загрузить sp в bp mov ax,0 ; Занести в ax 0 mov cx,[bp+4] ; Переместить count в cx cmp cx,ax ; Cравнить cx c 0 jle exit ; если ----------------------------------------------------------------- Конечно,можно использовать другой путь: вызвать подпрограммы Си из модуля, написанного на ассемблере. Но при этом вы должны сделать Си-функцию видимой из модуля, написанного на ассемблере. Мы уже вкратце обсудили, как это делается: функция объявляется как EXTRN, независимо от того, какого она типа - near или far. Например, вы написали следующую Си-функцию: long docalc(int *fact1, int fact2, int fact3); Для простоты предположим, что docalc - это Си-функция (т.е. противоположна Паскаль-функции). Имея в виду использование кро- хотной, малой или компактной модели памяти, вы должны Си-функцию в модуле, написанном на ассемблере, объявить следующим образом: EXTRN _docalc:near Подобно этому, если вы использовали среднюю, большую или ог- ромную модели памяти, то объявите функцию как _docalc:far. docalc определяется тремя параметрами: - адресом ячейки памяти - xval; - значением, хранимым в ячейке - imax; - константой 421 (десятичной). Предположим также, что вам нужно сохранить результат в 32-разрядной ячейке памяти, называемой ans. Эквивалентный вызов в Си будет таким: ans = docalc (xval, imax, 421); Вам необходимо занести в стек сначала 421, затем imax, после этого адрес xval и, наконец, вызвать docalc. При возврате нужно очистить стек, который увеличился на 6 дополнительных байт, и за- тем занести ответ в ячейки памяти ans и ans+2. Программа будет иметь вид: mov ax,421 ; Занести в стек 421 push ax push imax ; Занести в стек imax lea ax,xval ; Занести в стек xval - 407,408 - push ax call _docalc ; Вызов функции docalc add sp,6 ; Очистка стека mov ans,ax ; Занести 32-разрядный результат в ans mov ans+2,dx ; Включая старшее слово А что если docalc будет использовать Паскаль-соглашение по передаче параметров? Тогда вам придется изменить порядок парамет- ров на обратный, и не надо будет беспокоиться о чистке стека при возврате, т.к. это должна сделать вызываемая подпрограмма. Кроме того, вы должны соблюдать Паскаль-соглашение при написании docalc (без символа подчеркивания (_) и в верхнем регистре). Оператор EXTRN будет таким: EXTERN DOCALC:near а программа вызова docalc будет иметь вид: lea ax,xval ; Занести в стек xval push ax push imax ; Занести в стек imax mov ax,421 ; Занести в стек 421 push ax call DOCALC ; Вызов функции docalc mov ans,ax ; Занести 32-разрядный результат в ans mov ans+2,dx ; Включая старшее слово Вот и все, что вам необходимо знать, чтобы вы могли устано- вить связь между языками. - 409,410 - ПРОГРАММИРОВАНИЕ НА НИЗКОМ УРОВНЕ: псевдопеременные, встроенный ассемблер и функции прерывания ----------------------------------------------------------------- Предположим, что вы хотите выполнить какие-то действия на уровне управления машиной, но предпочитаете не испытывать труд- ностей, связанных с созданием отдельного модуля на языке ассемб-
лера. Что поможет вам в этом случае? Турбо Си дает ответ на этот вопрос (фактически три ответа): псевдопеременные, встроенный ас- семблер и функции прерывания. Прочтите эту главу, и вы поймете, как каждый из них поможет выполнить вашу работу. Псевдопеременные ----------------------------------------------------------------- Процессор в вашей ЭВМ (8088/8086/80186/80286) имеет опреде- ленное число регистров (или специальных элементов хранения), ко- торые он использует для манипулирования величинами. В каждом ре- гистре 16 разрядов (2 байта); некоторые из них выполняют специальные функции, хотя часть из них может быть использована и в качестве регистров общего назначения. Вспомните "Модели памя- ти", рассмотренные в начале этой главы, для подробного рассмотре- ния этих регистов процессора. Иногда (в низкоуровневом программировании) вам может понадо- биться обращение непосредственно к этим регистрам из Си-программы для: - загрузки в них значений перед вызовом системных подпрог- рамм; - просмотра значений, содержащихся в них в настоящее время. Турбо Си очень просто позволит вам обратиться к этим регист- рам через псевдопеременные. Псевдопеременные - это просто иденти- фикаторы, которые соответствуют данным регистрам. Их можно ис- пользовать по аналогии с переменными типа unsigned int или unsigned char. В таблице 12.6 приведен полный список псевдопеременных, ко- торые можно использовать, их типы, регистры, которым они соот- ветствуют, и для чего эти регистры обычно используются. - 411,412 - ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН Псевдо - Назначение переменные Тип Регистр регистра ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН _AX unsigned int AX РОН / аккумулятор _AL unsigned char AL младший байт AX _AH unsigned char AH старший байт AX _BX unsigned int BX РОН / индексный _BL unsigned char BL младший байт BX _BH unsigned char BH старший байт BX _CX unsigned int CX РОН/счетчик,счетчик цикла _CL unsigned char CL младший байт CX _CH unsigned char CH старший байт CX _DX unsigned int DX РОН / содержащий данные _DL unsigned char DL младший байт DX _DH unsigned char DH старший байт DX _CS unsigned int CS сегмент кода _DS unsigned int DS сегмент данных _SS unsigned int SS сегмент стека _ES unsigned int ES дополнительный сегмент _SP unsigned int SP указатель стека (смещение относительно SS) _BP unsigned int BP указатель базы (смещение относительно SS) _DI unsigned int DI Используется для регистровых переменных _SI unsigned int SI Используется для регистровых переменных ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН Таблица 12.6. Псевдопеременные Турбо Си Для чего же вам может понадобиться прямое обращение к этим переменным из Турбо Си ? - 413,414 - Для занесения в регистр некоторого значения перед вызовом низкоуровневой подпрограммы. Например, можно вызвать определенную подпрограмму из ПЗУ, используя INT (прерывание), но обязательно с предварительным занесением необходимой информации в определенные регистры, например так: void readchar (unsigned char page, unsigned char *ch, unsigned char *attr); ( - точка с запятой или новая строка, указывающие на окончание оператора asm.
Новый оператор asm может размещатся на той же строке, следуя после точки с запятой, но он не может продолжаться на следующую строку. Точка с запятой не может быть использована для начала ком- ментария (как это возможно в TASM). Для того, чтобы комментиро- вать операторы asm, используют комментарии в стиле Си, т.е. asm mov ax,ds; /*Этот комментарий правильный*/ asm pop ax; asm pop ds; asm iret; /*Комментарий правильный*/ asm push ds; ЭТОТ КОММЕНТАРИЙ НЕПРАВИЛЬНЫЙ!! Заметим, что последняя строка приводит к ошибке, т.к. комен- тарий здесь некорректен. Пара - строковые команды - специальные команды управления строками; - команды перехода - различные команды перехода; - директивы ассемблера - определение и размещение данных. Заметим, что компилятор допускает любые операнды, даже если они ошибочны или недопустимы с точки зрения ассемблера. Компиля- тор не накладывает ограничений на формат операндов. - 423,424 - Команды ----------------------------------------------------------------- Далее следует полный список мнемоники команд, которые могут быть использованы в качестве нормальных команд: ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН aaa fcom fldl2t fsub or aad fcomp fldlg2 fsubp out aam fcompp fldln2 fsubr pop aas fdecstp ** fldlpi fsubrp popa adc fdisi fldz ftst popf add fdivi fmul fwait push and fdivp fmulp fxam pusha bound fdivr fnclex fxch pushf call fdivrp fndisi fxtract rcl cbw feni fneni fyl2x rcr clc ffree ** fninit fyl2xpl ret cld fiadd fnop hlt rol cli ficom fnsave idiv ror cmc ficomp fnstcw imul sahf cmp fidiv fnstenv in sal cwd fidivr fnstsw inc sar daa fild fpatan int sbb das fimul fprem into shl dec fincstr ** fptan iret shr div finit frndint lahf sbc enter fist frstor lds std f2xm1 fistp fsave lea sti fabs fisub fscale leave sub fadd fisubr fsqrt les test faddp fld fst mov wait fbld fld1 fstcw mul xchg fbstp fldcw fstenv neg xlat fchs fldenv fstp not xor fclex fldl2e fstsw ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН Таблица 12.7. Мнемоника кодов операций Замечание: когда вы используете мнемонику команд 80186 в операторах встроенного ассемблера, то должны включить опцию ко- мандной строки -1. Это вызовет вывод в компилятор ассемблера не- обходимых операторов, так что TASM будет правильно понимать мне- - 425,426 - монику команд. Кроме того, если вы используете более ранние версии ассемблера, то эта мнемоника кодов не поддерживается. Другое замечание: если вы используете встроенный ассемблер в подпрограммах с эмуляцией команд с плавающей точкой (опция TCC -f), то операции, помеченные (**), не поддерживаются.
Строковые команды ----------------------------------------------------------------- В дополнение к списку кодов операций следующие строковые ко- манды могут быть использованы самостоятельно или с префиксами повторения: ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН cmps insw movsb outsb scasw cmpsb lods movsbw outsw stos cmpsw lodsb msb scas stosb ins lodsw outs scasb stosw insb movs ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН Таблица 12.8. Строковые команды - 427,428 - Префиксы повторения ----------------------------------------------------------------- Могут быть использованы следующие префиксы повторения: rep repe repne repnz repz Команды перехода ----------------------------------------------------------------- Команды перехода трактуются специально. Tак как метка не мо- жет быть включена в команду сама по себе, то переходы должны быть к Си-меткам (рассмотренным в разделе "Использование команд пере- хода и меток"). Разрешены следующие команды перехода: НННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН ja jge jnc jnp js jae jl jne jns jz jb jle jng jnz loop jbe jmp jnge jo loope jс jna jnl jp loopne jcxz jnae jnle jpe loopnz je jnb jno jpo loopz jg jnbe ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН Таблица 12.9. Команды перехода - 429,430 - Директивы ассемблера ----------------------------------------------------------------- Во встроенном ассемблере Турбо Си разрешены следующие дирек- тивы: db dd dw extrn Указатели встроенного ассемблера к данным и функциям ----------------------------------------------------------------- Вы можете использовать Си-идентификаторы в операторах asm; Турбо Си будет автоматически превращать их в соответствующие опе- ранды ассемблера и ставить знак подчеркивания в именах идентифи- каторов. Могут быть использованы все идентификаторы, включая ав- томатические (локальные) переменные, регистровые переменные и параметры функции. В общем, идентификатор Си может быть использован в любой по- зиции, где допускается адресный операнд. Понятно, что регистровая переменная может быть использованна там, где регистр - допустимый . В этом случае вы имеете дело с переменной и можете хранить в ней или получить из нее значения. Однако, вы так же можете непосредственно описывать элемент (без имени перемен- ной), используя числовую константу. В этой ситуации константа
равна смещению (в байтах) от начала структуры, содержащей этот элемент. Рассмотрим следующий программный фрагмент: struct myStruct ( int a_a; int a_b; int a_c; ) myA ; myfunc() ( ... asm mov ax,myA.a_b - 435,436 - asm mov bx,[di].a_c ... ) Мы объявили тип структуры, названной myStruct, с 3 членами: a_a, a_b, a_c, а также объявили переменную myA типа myStruct. Первый оператор встроенного ассемблера заносит величину из myA.a_b в регистр AX. Второй заносит величину по адресу [di] + смещение (a_c) в регистр BX (оператор берет адрес, сохраненный в DI, и добавляет к нему смещение а_с от начала myStruct). В этой последовательнности ассемблеровские операторы производят следую- щую программу: mov ax, DGROUP : myA+2 mov bx, [di+4] Для чего вам может это понадобиться сделать? Если вы загру- жаете регистр (такой, как DI) адресом структуры типа myStruct, то вы можете использовать имена, для того чтобы непосредственно ука- зывать элементы. Имя элемента может быть использовано в любой по- зиции, где численная константа разрешена в операнде утверждения ассемблера. Элемент структуры должен начинаться с точки (.) для того, чтобы указывать, что используется имя члена структуры, а не нор- мальный идентификатор Си. Имена элементов замещаются в ассембле- ровском коде числовыми смещениями элементов в структуре (числовое смещение а_с есть 4), но тип информации не сохраняется. Поэтому элементы могут быть использованы только во время компиляции ас- семблеровских операторов. Однако, есть одно ограничение: если две структуры используют одно и то же имя элемента, вы должны имя элемента заключить в круглые скобки, сделав его как бы ядром. - 437,438 - Использование команд перехода и меток ----------------------------------------------------------------- Во встроенном ассемблере вы можете использовать любые услов- ные и безусловные команды перехода, а также команды цикла. Все они имеют силу только внутри функции. Так как в операторах asm не могут быть даны метки, команды перехода должны использовать Си-метки операторов goto как объекты перехода. Прямые far-перехо- ды не могут генерироваться. Также допускаются косвенные переходы. Для того, чтобы ис- пользовать косвенный переход, вы можете применить имя регистра как операнд команды перехода. В следующей программе переход идет к Си-метке "а" оператора goto. int x() { a: /* Это goto метка "а" */ . . . asm jmp a /* Переход к метке "а" */ . . . } Функции прерываний ----------------------------------------------------------------- Микропроцессор 8086 резервирует первые 1024 байта памяти для набора 256 удаленных указателей (называемых векторами прерываний) на специальные системные подпрограммы обработки прерываний. Эти подпрограммы вызываются выполнением команды int 8; /* Получить текущие установки управляющего порта */ bits = originalbits = inportb (0x61); for (i = 0; i <= bcount; i++){ /* На время отключить динамик */ outportb(0x61, bits & 0xfc); for (j = 0; j <= 100; j++) ; /* пустой оператор*/ /* Теперь включить его на определенное время */ outportb(0x61, bits | 2); for (j = 0; j <= 100; j++) ; /* другой пустой оператор*/ }
/* Восстановить установки управляющего порта */ outportb(0x61, originalbits); } Далее необходимо написать функцию, которая установит ваш об- - 443,444 - работчик прерывания. Вы передадите ей адрес написанной ранее функции и соответствующий номер прерывания (0 ...255 или 0х00...0 хFF). Эта функция должна выполнить 3 действия: - запретить прерывания, чтобы во время модернизации вектор- ной таблицы не произошло ничего непредвиденного; - сохранить переданный адрес в соответствующем месте; - разрешить прерывания, чтобы все снова правильно работало. Ваша подпрограмма настройки будет выглядеть так: void install(void interrupt (*faddr)(), int inum) { setvect(inum, faddr); } Наконец, вам понадобится вызвать подпрограмму звукового сиг- нала для того, чтобы подвергнуть ее проверке. Это сделает такая функция: void testbeep(unsigned char bcount, int inum) { _AH = bcount; geninterrupt(inum); } Теперь ваша функция main имеет следующий вид: main() { char ch; install(mybeep, 10); testbeep(3, 10); ch = getch(); } - 445,446 - Использование библиотек программ для работы с плавающей точкой ----------------------------------------------------------------- В Си вы работаете с двумя типами чисел: целыми (int, short, long и т.п.) и с плавающей точкой (float, double). Установленный в вашей машине процессор легко работает с целыми значениями, а для работы с плавающей точкой он затрачивает больше времени и ре- сурсов. Однако, семейство процессоров iAPx86 имеет соответствующее семейство математических сопроцессоров 8087 и 80287. 8087 и 80287 (и тот и другой мы называем здесь сопроцессорами) - это специаль- ные арифметические процессоры, которые могут быть установлены в ваш РС. Они выполняют команды с плавающей точкой очень быстро. Если вы работаете с величинами, в которых часто встречается пла- вающая точка, то вам непременно захочется работать с сопроцессо- ром. Центральный процессор в вашем РС взаимодействует с 8087/80287 посредством специальных прерываний. Турбо Си поможет гибко использовать программы непосредствен- но для вашего РС и ваших нужд. - Если вы не будете использовать значения с плавающей точ- кой, сообщите об этом заранее компилятору. - Если вам нужно использовать значения с плавающей точкой, но ваш РС не имеет арифметического сопроцессора (8087/80287), то сообщите Турбо Си, что выполнять компоновку в специальных подп- рограммах можно так же, как если бы у вас был сопроцессор. В том случае, если ваша программа работает в машине с сопроцессором, он будет использоваться автоматически, и программа будет выполняться очень быстро. - Если вы пишите программы для систем, которые имеют арифме- тический сопроцессор, то можете предписать компилятору Турбо Си использовать программу, которая всегда работает с сопроцессором 8087/80287. В следующих примерах для ТСС и TLINK предполагается, что файл TURBOC.CFG существует с корректной установкой маршрутов -L и -I и что библиотека и начальные объектные файлы сохранены в под- каталоге, именуемом LIB. - 447,448 - Эмуляция микросхемы 8087/80287 ----------------------------------------------------------------- Предположим, что вы хотите использовать величины с плавающей точкой или вы имеете программу, использующую величины с плавающей точкой, но ваш компьютер не имеет арифметического сопроцессора, то будет ли выполняться ваша программа? Успокойтесь, Турбо Си по- может в этой ситуации. В соответствии с опцией эмуляции компилятор будет генериро- вать программу так, как если бы 8087/80287 в РС был, но программа будет выполнять компоновку в библиотеке эмуляции (EMU.LIB). При выполнении программа будет использовать 8087/80287, если он есть; в противном случае программа будет использовать специальное прог- раммное обеспечение для 8087/80287 - эмулятор. Библиотека эмуляции работает так: - когда ваша программа начинает выполняться, то начальная Си-программа определяет, есть ли 8087/80287 или нет; - если сопроцессор есть, то программа при помощи специальных прерываний для 8087/80287 разрешит использование микросхемы 8087/80287 для программы пользователя. - если сопроцессора нет, то программа останавливает прерыва- ния и переходит к эмуляционным подпрограммам. Пусть вы модифицировали RATIO.C таким образом: main() { float a,b,ratio; printf(" Введите два значения: "); scanf("%f %f",%a,%b); ratio = a/b; printf("Результат деления будет %0.2f\n",ratio); } Если вы используете ТС (версию пользовательского интерфей- са), то нужно выйти в меню Options, выбрать Compiler, выбрать Code generation и затем выбирать элемент Floating-point до тех - 449,450 - пор, пока на следующем поле не прочтете Emulation. Когда вы ком- пилируете и компонуете свою программу, Турбо Си автоматически вы- бирает для нее соответствующие опции и библиотеки. Если вы используете ТСС (автономный компилятор), то команд- ная строка будет выглядеть следующим образом: tcc -mX ratio Если вы выполняете компоновку результирующей программы с ко- мандной строки, то должны точно определить как соответствующую математическую библиотеку (зависящую от размера модели), так и файл EMU.LIB. Опция эмуляции (-f) устанавливается по умолчанию, так что не нужно задавать ее, если ваш файл TURBOC.CFG не включа- ет один из переключателей плавающей точки (-f- или -f87). Вызов TLINK будет выглядеть так: tlink lib\c0X ratio, ratio, ratio, lib\emu.lib lib\mathX.lib lib\cX.lib где X - буква, обозначающая соответствующую модель библиотеки. Замечание: команда tlink записывается на одной строке. Также помните, что порядок библиотек очень важен. - 451,452 -
Использование микросхемы математического сопроцессора 8087/80287 ----------------------------------------------------------------- Если вы абсолютно уверены, что ваша программа будет работать только на машинах, имеющих микросхему 8087 или 80287, то можете создать программу, которая будет использовать преимущества этой микросхемы. В этом случае результирующие .ЕХЕ файлы будут меньше, так как Турбо Си не будет включать подпрограммы эмуляции 8087/80287 (EMU.LIB). Если вы используете интегрированную версию Турбо Си, то сле- дует перейти в меню Options, выбрать Compiler, выбрать Code generation и затем выбирать элемент Floating point до тех пор, пока на следующем поле не появится надпись "8087/80287". Когда вы компилируете и компонуете свою программу, Турбо Си будет автома- тически выбирать для нее нужные опции и библиотеки. Если вы используете ТСС (автономный компилятор), то нужно в командной строке использовать опцию - f87, то есть: tcc -f87 -mX ratio Это даст указание Турбо Си генерировать встроенные вызовы к микросхеме 8087/80287. Когда TLINK вызвана, файлы FP87.LIB и MATHx.LIB будут скомпонованы. Если вы выполняете компоновку результирующей программы вруч- ную, то должны определить как соответствующую математическую биб- лиотеку (зависящую от размера модели), так и библиотеку процессо- ра FP87 следующим образом: tlink lib\c0X ratio, ratio, ratio, lib\fp87.lib lib\mathX.lib lib\cX.lib где Х, как всегда, обозначает нужную модель библиотеки. - 453,454 - Если вы не используете плавающую точку... ----------------------------------------------------------------- Если ваша программа не использует никакие подпрограммы с плавающей точкой, то компоновщик не будет задействовать соответс- твующие библиотеки (EMU.LIB или FP87.LIB совместно с MATHx.LIB), даже если вы запишете их в командной строке. Не включив эти биб- лиотеки в командную строку компоновщика, вы можете оптимизировать шаг выполнения компоновки (если в программе не используются опе- рации над числами с плавающей точкой). Предположим, вы хотите компилировать и компоновать следующую программу (записанную в RATIO.C): main() { int a,b,ratio; printf ("Введите два значения: "); scanf ("%d %d", %a,%b); ratio = a/b; printf ("Результат деления будет %d\n",ratio); } Так как эта программа не использует подпрограммы с плавающей точкой, то вы можете выбирать, компилировать ли ее с эмуляцией плавающей точки или без плавающей точки вообще. Если вы используете интегрированную версию Турбо Си и решили компилировать с эмуляцией, то выберите Compile to OBJ из меню Compile (Эмуляция включена по умолчанию). Компоновщик будет вклю- чать библиотеки с плавающей точкой на шаге установки связей, но ни одна связь не будет реально установлена. Если вы хотите ускорить процесс компоновки, то можете уста- новить режим "без плавающей точки". Перейдите в меню Options, вы- берите Сompiler, выберите Code generation, а затем - положение Floating point. Нажимая несколько раз клавишу "Ввод" в циклах этой команды, вы пройдете 3 опции: None, Emulation и 8087/80287. Вам нужна оп- ция None. Можете нажать "Esc" 3 раза, чтобы вернуться в стро- ку-линейку меню (или нажмите F10). - 455,456 - Когда вы компилируете и компонуете эту же программу в режиме SET 87=Y Установка переменной среды 87 в N (для No) указывает началь- ной программе, что вы не хотите использовать 8087/80287 (даже ес- ли он присутствует в системе). И, наоборот, установка переменной среды 87 в Y (для Yes) оз- начает, что сопроцессор имеется, и вы хотите, чтобы ваша програм- ма использовала его. Предостережение программисту! Если вы уста- навливаете 87=Y, а на самом деле 8087/80287 отсутствует в машине, то программа потерпит крах и сгорит в логическом аду. - 459,460 - Переменная среды 87 в состоянии замещать устанавливаемый по умолчанию алгоритм автообнаружения потому, что когда вы начинаете выполнять свою программу, начальная программа первым долгом про- веряет, определена ли переменная среды 87. - Если переменная среды 87 определена, то начальная програм- ма не смотрит далее, и ваша программа выполняется в описанном ре-
жиме. - Если переменная среды 87 не определена, то начальная прог- рамма проходит через свой алгоритм автообнаружения для того, что- бы проверить, доступна ли микросхема 8087/80287, и программа ра- ботает в соответствии с результатом этой проверки. Если переменная среды 87 определена (любым значением), но вы хотите переопределять ее, то введите следующее выражение в приг- лашение ДОС: C> SET 87= (Это означает нажатие клавиши "Ввод" сразу после знака равенства) Регистры и 8087/80287 ----------------------------------------------------------------- Существует 2 момента, относящихся к регистрам, которые вы должны учитывать, когда используете плавающую точку. Во-первых, в режиме эмуляции 8087/80287 регистровый цикли- ческий переход не поддерживается. Во-вторых, если вы используете плавающую точку совместно со встроенным ассемблером, то необходимо быть особенно внимательным при использовании регистров, потому что регистр 8087/80287 очища- ется перед Турбо Си-вызовами функции. Вам понадобится извлечь и сохранить регистры 8087/80287 до вызова функций, использующей сопроцессор, даже если вы уверенны, что существует достаточное количество свободных регистров. - 461,462 - Использование matherr с плавающей точкой ----------------------------------------------------------------- Если во время выполнения программы обнаруживается ошибка в одной из подпрограмм с плавающей точкой, то эта подпрограмма ав- томатически вызывает _matherr с несколькими аргументами. Затем _matherr заполняет своими аргументами особую структуру (опреде- ленную в math.h) и вызывает matherr с указателем на эту структу- ру. matherr - это зацепка, которая позволяет вам написать свою собственную подпрограмму анализа ошибки. По умолчанию, matherr только возвращает 0, и ничего более. Однако, вы можете изменить matherr соответствующим образом для того, чтобы создать подпрог- рамму обработки ошибок плавающей точки. Такая модифицированная matherr возвращает величину, отличную от 0, если ошибка была исп- равлена, и 0 в противном случае. Для получения более подробной информации о matherr и _matherr смотри описание matherr в части 2 "Справочного руководс- тва по Турбо Си". Предостережения и советы ------------------------ Как Турбо Си использует RAM ----------------------------------------------------------------- Во время компиляции Турбо Си не хранит на диске промежуточ- ные структуры данных (на диск Турбо Си записывает только объект- ные файлы .OBJ). Для всех промежуточных структур данных Турбо Си использует RAM. Поэтому вы можете столкнуться с сообщением OUT OF MEMORY..., если для компилятора не достаточно памяти. Чтобы решить эту проблему, нужно сделать ваши функции меньше или разделить файл, который имеет большие функции. Можно также стереть любые резидентные программы, установленные вами, чтобы освободить большую память для Турбо Си. - 463,464 - Нужно ли вам использовать Паскаль-соглашения? ----------------------------------------------------------------- Нет, если только вы прочли эту главу и хорошо разобрались в ней. Запомните, что если вы устанавливаете связи главного файла с помощью Паскаль-соглашений по вызову, то должны объявить main как Си-функцию: cdecl main(int argc, char * argv[], char * envp[]) Заключение ----------------------------------------------------------------- Вы увидели, как использовать все 3 аспекта низкоуровневого программирования в Турбо Си (псевдопеременные, встроенный ассемб- лер, функции прерывания); вы также изучили связь с другими языка- ми, включая ассемблер; вы были ознакомлены с некоторыми деталями использования подпрограмм с плавающей точкой; вы поняли, как вза- имодействуют различные модели памяти в 8086. Теперь вы можете ис- пользовать все эти приемы для получения полного контроля над ва- шим компьютером. Наилучших вам пожеланий!