Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Сабуров С.В. - Язык программирования C и C++ - 2006

.pdf
Скачиваний:
312
Добавлен:
13.08.2013
Размер:
1.42 Mб
Скачать

Справочник по работе с DOS

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

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

Объявление дальних объектов

Borland С++ позволяет объявлять дальние (far) объекты. Например:

int far x = 5; int far z;

extern int far y = 4; static long j;

Компилятор Borland C++ создает для каждого дальнего объекта отдельный сегмент. Параметры компилятора командной строки zE, zF и zH (которые могут также задаваться директивой #pragma option) влияют на имя, класс и группу дальнего сегмента, соответственно. Изменяя эти значения при помощи указания #pragma option, вы тем самым распространяете новые установки на все объявления дальних объектов. Таким образом, для создания в конкретном сегменте дальнего объекта, можно использовать следующую последовательность:

#pragma option zEmysegment zHmygroup zFmyclass int far x;

#pragma option zE* =zH* zF*

Тем самым x будет помещен в сегмент MYSEGMENT с классом MYCLASS в группе MYGROUP, после чего все дальние объекты будут сброшены в значения, используемые по умолчанию. Отметим, что при использовании этих параметров можно поместить несколько дальних объектов в один сегмент:

#pragma option zEcombined zFmyclass int far x;

187

Справочник по работе с DOS

double far y;

#pragma option zE* zF*

И x, и y окажутся в сегменте COMBINED 'MYCLASS', без группы.

Объявление ближних или дальних функций

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

double power(double x,int exp)

{

if (exp <= 0) return(1);

else

return(x * power(x, exp 1));

}

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

double __near power(double x,int exp)

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

Это означает, что при использовании большой модели памяти (medium, large или huge) функцию power можно вызывать только из того модуля, в котором она определена. Прочие модули имеют свои собственные кодовые сегменты и не могут вызывать функции near из других модулей. Более того, ближняя функция до первого к ней обращения должна быть либо определена, либо объявлена, иначе компилятор не знает о необходимости генерировать ближний вызов.

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

188

Справочник по работе с DOS

Вернемся к примеру функции power. Хорошо также объявить power как static, поскольку предусматривается вызывать ее только из текущего модуля. Если функция будет объявлена как static, то имя ее не будет доступно ни одной функции вне данного модуля.

Объявление указателей near, far или huge

Только что были рассмотрены случаи, в которых может понадобиться объявить функцию с другой моделью памяти, нежели остальная часть программы. Зачем то же самое может понадобиться для указателей? По тем же причинам, что и для функций: либо для улучшения характеристик быстродействия (объявив __near там, где по умолчанию было бы __far), либо для ссылки за пределы сегмента по умолчанию (объявив __far или __huge там, где по умолчанию бывает __near).

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

void myputs(s) char *s;

{

int i;

for (i = 0; s[i] != 0; i++) putc(s[i]);

}

main()

{

char near *mystr;

mystr = "Hello, world\n"; myputs(mystr);

}

Эта программа работает удовлетворительно, хотя объявление mystr как __near избыточно, поскольку все указатели, как кода, так и данных, будут ближними (near) по умолчанию.

Однако, что произойдет, если перекомпилировать эту программу с моделью памяти compact (либо large или huge)? Указатель mystr в функции main останется ближним (16 битовым). Однако, указатель s в функции myputs теперь будет дальним (far), поскольку по умолчанию теперь используется far. Это означает, что попытка создания дальнего указателя приведет

189

Справочник по работе с DOS

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

Как избежать этой проблемы? Решение состоит в том, чтобы определить myputs в современном стиле Си:

void myputs(char *s)

{

/* тело myputs */

}

Теперь при компиляции вашей программы Borland C++ знает, что myputs ожидает указатель на char. Поскольку компиляция выполняется с моделью large, то известно, что указатель должен быть __far. Вследствие этого Borland C++ поместит в стек регистр сегмента данных (DS) и 16 битовое значение mystr, образуя тем самым дальний указатель.

Если вы собираетесь явно объявлять указатели как far или near, не забывайте использовать прототипы тех функций, которые могут работать с этими указателями.

Как быть в обратном случае: когда аргументы myputs объявлены как __far, а компиляция выполняется с моделью памяти small? И в этом случае без прототипа функции у вас возникнут проблемы, поскольку функция main будет помещать в стек и смещение, и адрес сегмента, тогда как myputs будет ожидать приема только одного смещения. При наличии определений функций в прототипах main будет помещать в стек только смещение.

Создание указателя данного адреса «сегмент:смещение»

Как создать дальний указатель на конкретный адрес памяти (конкретный адрес «сегмент:смещение»)? Для этого можно воспользоваться встроенной библиотечной подпрограммой MK_FP, которая в качестве аргумента воспринимает сегмент и смещение, и возвращает дальний указатель. Например:

MK_FP(segment_value, offset_value)

Имея дальний указатель fp, вы можете получить значение сегмента полного адреса с помощью FP_SEG(fp) и значение смещения с помощью FP_OFF(fp).

Использование библиотечных файлов

190

Справочник по работе с DOS

Borland C++ предлагает для каждой из шести моделей памяти собственную версию библиотеки стандартных подпрограмм. Компилятор Borland C++ при этом проявляет достаточно «интеллекта», чтобы при последующей компоновке брать нужные библиотеки и в нужной последовательности, в зависимости от выбранной вами модели памяти. Однако, при непосредственном использовании компоновщика Borland C++ TLINK (как автономного компоновщика) вы должны явно указывать используемые библиотеки.

Компоновка смешанных модулей

Что произойдет, если вы компилируете один модуль с использованием модели памяти small (малая), второй — модели large (большая), и затем хотите скомпоновать их? Что при этом произойдет?

Файлы скомпонуются удовлетворительно, но при этом вы столкнетесь с проблемами. Если функция модуля с моделью small вызывает функцию в модуле с моделью large, она будет использовать при этом ближний вызов, что даст абсолютно неверные результаты. Кроме того, у вас возникнут проблемы с указателями, поскольку функция в модуле small ожидает, что принимаемые и передаваемые ей указатели будут __near, тогда как функция в модуле large ожидает работу с указателями __far.

И снова решение заключается в использовании прототипов функций. Предположим, что вы поместили myputs в отдельный модуль и скомпилировали его с моделью памяти large. Затем вы создаете файл заголовка myputs.h (либо с любым другим именем и расширением .h), который содержит следующий прототип функции:

void far myputs(char far *s);

Теперь, если поместить функцию main в отдельный модуль (MYMAIN.C) и выполнить следующие установки:

#include <stdio.h> #include "myputs.h" main()

{

char near *mystr;

mystr = "Hello, world\n"; myputs(mystr);

}

191

Справочник по работе с DOS

то при компиляции данной программы Borland C++ считает прототип функции из файла MYPUTS.H и увидит, что это функция __far, ожидающая указатель __far. В результате этого даже при модели памяти small при компиляции будет сгенерирован правильный вызывающий код.

Как быть, если помимо этого вам требуется компоновка с библиотечными подпрограммами? Лучший подход здесь заключается в том, чтобы выбрать одну из библиотек с моделью large и объявить все как far. Для этого сделайте копии всех файлов заголовка, которые вы обычно включаете (таких, как stdio.h) и переименуйте эти копии (например, fstdio.h).

Затем отредактируйте копии прототипов функций таким образом, чтобы там было явно указано far, например:

int far cdecl printf(char far* format, ...);

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

#include <fstdio.h> main()

{

char near *mystr;

mystr = "Hello, world\n"; printf(mystr);

}

Скомпилируйте вашу программу при помощи компилятора BCC, затем скомпонуйте ее при помощью утилиты TLINK, указав библиотеки с моделью памяти large, например CL.LIB.

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

Оверлеи (VROOMM)

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

192

Справочник по работе с DOS

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

Оверлеи используются только в 16 разрядных программах DOS. В приложениях Windows для сокращения объема используемой памяти вы можете пометить сегменты как Discardable (выгружаемые).

Работа программ с оверлеями

Программа управления оверлеями (VROOMM, или Virtual Run time Object Oriented Memory Manager) выполняет за вас большую часть работы по организации оверлеев. В обычных оверлейных системах модули группируются в базовый и набор оверлейных модулей. Подпрограммы в данном оверлейном модуле могут вызывать подпрограммы из этого же модуля и из базового модуля, но не из других модулей. Оверлейные модули перекрывают друг друга, т.е. одновременно в памяти может находиться только один оверлейный модуль, и все они при активизации занимают один и тот же участок физической памяти. Общий объем памяти, необходимой для запуска данной программы, определяется размером базового, плюс максимального оверлейного модуля. Эта обычная схема не обеспечивает достаточной гибкости. Она требует полного учета всех возможных обращений между модулями программы и, соответственно, планируемой вами, группировки оверлеев. Если вы не можете разбить вашу программу в соответствии со взаимозависимостью обращений между ее модулями, то вы не сможете и разбить ее на оверлеи.

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

193

Справочник по работе с DOS

этом другие сегменты. Это мощное средство — подобное виртуальной программной памяти. От вас больше не требуется разбивать код на статические, отдельные оверлейные блоки. Вы просто запускаете программу!

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

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

После загрузки оверлея в память он помещается в оверлейный буфер, который расположен в памяти между сегментом стека и дальней динамически распределяемой областью. По умолчанию размер оверлейного буфера вычисляется и устанавливается при загрузке программы, но его можно изменить при помощи глобальной переменной _ovrbuffer. Если достаточный размер памяти недоступен, то появляется либо сообщение об ошибке DOS («Program too big to fit in memory» — «Программа слишком велика для имеющейся памяти»).

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

194

Справочник по работе с DOS

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

Распределение памяти для оверлейных структур

Оптимальное использования оверлеев Borland C++

195

Справочник по работе с DOS

Для полного использования преимуществ оверлейных структур, создаваемых Borland C++, сделайте следующее:

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

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

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

Требования

При создании оверлеев следует помнить несколько простых правил, а именно:

Минимальная часть программы, которая может выделяться в качестве оверлея, это сегмент.

Прикладные программы с оверлейной структурой должны иметь одну из трех следующих моделей памяти: medium, large или huge; модели tiny, small и compact

оверлеи не поддерживают.

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

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

196

Соседние файлы в предмете Программирование на C++