2. Управление памятью

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

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

Защищенный режим работы процессора накладывает на приложения свои ограничения. Если программа, составленная для MS-DOS, имеет доступ к любому участку основной, расширенной или дополнительной памяти, а также к любому оборудованию компьютера (через порты ввода/вывода), приложение Windows поставлено в жесткие рамки. Например, приложение может работать только с теми блоками памяти, которые ей принадлежат или получены от операционной системы. Приложение не может выполнять запись в сегмент кода и не может передавать управление в сегмент данных. Что же касается портов ввода/вывода, то в расширенном режиме работы приложение, как правило, не имеет к ним непосредственного доступа. Если приложение попытается вывести данные в порт, это приведет к переключению задачи. Операционная система Windows проверит номер порта и, возможно, предоставит вам возможность что-нибудь в него записать. А возможно, что и не предоставит, выполнив вашу команду ввода/вывода как команду NOP.

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

Однако приложение Windows, тем не менее, может делать все что угодно, если оно работает вместе с так называемым виртуальным драйвером или, как его еще называют, драйвером виртуального устройства ввода/вывода. Виртуальный драйвер - это особая разновидность драйверов в Windows. Виртуальные драйверы предназначены для создания виртуальных устройств ввода/вывода, предоставляемых в пользование параллельно работающим приложениям Windows и виртуальным машинам MS-DOS. Так как виртуальные драйверы работают непосредственно с аппаратурой, для них не действует большинство ограничений, накладываемых на приложения Windows. В частности, виртуальный драйвер может обращаться к любой области памяти, выполнять команды ввода/вывода и привилегированные команды процессора.

Поэтому, если создаваемое вами приложение должно работать с нестандартной аппаратурой в режиме реального времени, использовать канал прямого доступа к памяти или аппаратные прерывания, вы должны создать собственный виртуальный драйвер и поставлять его вместе с приложением. Для разработки драйверов вам необходимо приобрести Microsoft Driver Development Kit for Windows 3.1, в состав которого, кроме документации и исходных текстов некоторых стандартных драйверов входит специальная версия ассемблера и редактора связей, а также утилиты, необходимые для создания виртуальных драйверов.

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

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

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

2.1. Особенности защищенного режима работы процессора

В томе 6 "Библиотеки системного программиста", который называется "Защищенный режим процессоров Intel 80286/80386/80486", мы подробно рассмотрели работу процессора 80286 в защищенном режиме и особенности адресации памяти процессорами 80386 и 80486. Там же приведены примеры программ, переключающие процессор в защищенный режим работы и выполняющие в этом режиме обращение к аппаратуре компьютера. Однако, учитывая, что в вашем распоряжении может не оказаться указанного выше тома, мы приведем самые необходимые сведения о защищенном режиме в этой главе.

Адресация памяти в реальном режиме

Для адресации байта памяти в реальном режиме работы используются две 16-разрядные компоненты адреса - сегмент и смещение. Физический адрес , который попадает на шину адреса системной платы компьютера, складывается (в буквальном смысле этого слова) из сдвинутой влево на четыре бита и дополненной справа четырьмя нулевыми битами сегментной компоненты и компоненты смещения. Перед сложением компонента смещения расширяется до 20 бит так, что в старшие четыре бита записываются нули (рис. 2.1).

Рис. 2.1. Получение физического адреса в реальном режиме

Задавая произвольные значения для сегмента и смещения мы можем сконструировать физический адрес для обращения к памяти размером 1 Мбайт плюс 64 Кбайт (и минус 16 байт).

Адрес, состоящий из сегмента и селектора, мы будем называть логическим адресом реального режима . Диапазон логических адресов от 0000h:0000h до FFFFh:000Fh соответствует диапазону физических адресов от 00000h до FFFFFh. Этот диапазон адресов соответствует первому мегабайту оперативной памяти.

Диапазон логических адресов от FFFFh:0010h до FFFFh:FFFFh соответствует так называемой области старшей памяти (High Memory Area). Размер области старшей памяти равен 64 Кбайта без 16 байт, и эта память доступна в реальном режиме для процессора модели 80286 и более старших моделей. Если вы работаете с операционной системой MS-DOS версии 5.0 или 6.2, имеет смысл загрузить ядро MS-DOS в область старших адресов, указав в файле config.sys команду:

DOS=HIGH

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

Рис. 2.2. Адресация памяти в MS-DOS

На заре развития персональных компьютеров оперативная память размером в 1 Мбайт считалась достаточно большой для решения любых мыслимых задач. Однако с появлением Windows и внедрением графического пользовательского интерфейса критерии оценки объема памяти резко изменились. Теперь минимальный объем памяти для нормальной работы приложений Windows составляет 4 Мбайта, а для некоторых приложений (например, для системы разработки Borland C++ for Windows версии 4.0 или Microsoft Visual C++) требуется 8 Мбайт или даже 16 Мбайт. Схема адресации реального режима непригодна для работы с такими большими объемами памяти, так как в этой схеме для физического адреса предусмотрено всего 20 разрядов.

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

Если в мультизадачной среде одна задача может писать данные в область памяти, отведенной другой задаче, она может разрушить и эту задачу, и ядро операционной системы. Поэтому в мультизадачных операционных системах, разработанных для процессоров серии Intel 80xxx или Pentium, применяется только защищенный режим работы процессора.

Адресация памяти в защищенном режиме

В защищенном режиме, как и в реальном, логический адрес состоит из двух компонент. Однако эти компоненты называются не сегмент и смещение, а селектор и смещение . Для вычисления физического адреса в процессоре 80286 используются также две таблицы дескрипторов - глобальная таблица дескрипторов GDT (Global Descriptor Table ) и локальная таблица дескрипторов LDT (Local Descroptor Table ). Селектор используется для адресации ячейки одной из таблиц дескрипторов, содержащей помимо прочей информации базовый 24-разрядный адрес сегментов. Для получения физического адреса базовый адрес складывается со смещением, расширенным до 24 разрядов (рис. 2.3).

Рис. 2.3. получение физического адреса в процессоре 80286

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

Таким образом, несмотря на то, что компоненты адреса остались, как и в реальном режиме, 16-разрядными, новая схема адресации защищенного режима процессора 80286 позволяет адресовать до 16 Мбайт памяти, так как в результате преобразования получается 24-разрядный физический адрес.

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

Рис. 2.4. Формат селектора

Поле TI (Table Indicator ) используется для выбора таблицы дескрипторов. Как мы уже говорили, существуют таблицы дескрипторов двух типов. В любой момент времени может использоваться одна глобальная таблица дескрипторов и одна локальная таблица дескрипторов. Если бит TI равен 0, для выборки базового адреса используется глобальная таблица дескрипторов GDT, если 1 - локальная LDT.

Зачем нужно использовать дескрипторные таблицы двух типов?

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

Поле RPL (Requested Privilege Level ) селектора содержит уровень привилегий, запрошенный программой при обращении к сегменту. Программа может обращаться только к таким сегментам, которые имеют соответствующий уровень привилегий. Поэтому программа не может, например, воспользоваться глобальной таблицей дескрипторов для получения доступа к описанным в ней системным сегментам, если она не обладает достаточным уровнем привилегий. На этом основана защита системных данных от разрушения (преднамеренного или в результате программной ошибки) со стороны прикладных программ.

Таблица дескрипторов содержит, помимо базового адреса сегмента, другую информацию, описывающую сегмент (рис. 2.5). Точный формат дескриптора, а также других структур данных и системных регистров, имеющих отношение к работе в защищенном режиме, вы можете найти в 6 томе "Библиотеки системного программиста".

Рис. 2.5. Формат дескриптора сегмента процессора 80286

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

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

Что же касается привилегий, в процессорах 80ххх и Pentium существуют четыре уровня привилегий - от 0 до 3, причем наибольшие привилегии соответствуют уровню 0. Уровни привилегий часто называют также кольцами защиты (рис. 2.6).

Рис. 2.6. Кольца защиты

В кольце 0 обычно работает ядро операционной системы. Кольцо 1 соответствует уровню привилегий драйверов, кольцо 2 - системам, таким как системы управления базами данных. В наименее привилегированном кольце 3 располагаются прикладные программы, запускаемые пользователем.

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

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

Преобразование адреса в этих процессорах является многоступенчатым. Программы адресуют память с помощью логического адреса, состоящего из 16-разрядного селектора и 32-разрядного смещения. Так же, как и в процессоре 80286, селектор используется для выборки базового адреса сегмента из глобальной или локальной таблицы дескрипторов. Отличие заключается в том, что во-первых, используются 32-разрядные базовый адрес и смещение, а во-вторых, результат сложения называется линейным адресом и используется не для непосредственной адресации памяти, а для дальнейших преобразований (рис. 2.7).

Рис. 2.7. Преобразование логического адреса в линейный

Старшие десять бит линейного адреса используются как индекс в каталоге таблиц страниц (рис. 2.8).

Рис. 2.8. Преобразование линейного адреса в физический

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

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

Таблица страниц может описывать до 1024 страниц размером 4096 байт.

Младшие двенадцать бит линейного адреса содержат смещение адресуемого байта внутри страницы.

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

Операционная система Microsoft Windows версии 3.1 может работать, как вы знаете, в стандартном и расширенном режиме . В первом случае используется схема адресации процессора 80286, даже если в компьютере установлен процессор 80386. Если Windows работает на процессоре 80386, 80486 или Pentium, при наличии достаточного объема оперативной памяти (больше 2 Мбайт) по умолчанию используется расширенный режим работы и, соответственно, схема преобразования адресов процессора 80386.

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

Описанная выше схема адресации в защищенном режиме накладывает ограничения на операции, которое приложение Windows может выполнять над селекторами.

Приложение Windows не должно выполнять над селекторами арифметические операции и операции сравнения

Программируя для реального режима операционной системы MS-DOS, вы, возможно, при адресации блока памяти большого размера (больше 64 Кбайт) изменяли содержимое сегментных регистров. В защищенном режиме вы не можете делать никаких предположений относительно базового адреса следующего или предыдущего дескриптора в локальной или глобальной таблице дескрипторов.

Сказанное не означает, что приложения Windows не могут работать с блоками памяти, занимающими несколько сегментов. В этом случае для адресации вам нужно использовать специальные методы. Однако, если вы составляете приложение на языке программирования С или С++, при определении указателей на блоки памяти размером больше, чем 64 Кбайт, можно воспользоваться ключевым словом huge. Для таких указателей при необходимости будет автоматически выполняться переключение на нужные селекторы.

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

Приведем исходный текст приложения SELECTOR, с помощью которого вы сможете проанализировать структуру селектора сегмента кода и сегмента данных, взятых из регистров CS и DS (листинг 2.1).

Листинг 2.1. Файл selector/selector.cpp

#define STRICT
#include <windows.h>

#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance,
   HINSTANCE hPrevInstance,
   LPSTR     lpszCmdLine, int nCmdShow)
{
  UINT uSelCS, uSelDS, uTICS, uTIDS;
  BYTE szBuf[100];

  // Получаем селектор сегмента кода
  asm mov ax, cs
  asm mov uSelCS, ax

  // Получаем селектор сегмента данных
  asm mov ax, ds
  asm mov uSelDS, ax

  // Выделяем бит TI. Если этот бит
  // равен 1, для адресации используется
  // глобальная таблица дескрипторов,
  // если 0 - локальная
  uTICS = (uSelCS & 4) >> 2;
  uTIDS = (uSelDS & 4) >> 2;

  // Выводим значения селекторов для сегментов
  // кода и данных, значения поля TI и номер
  // кольца защиты
  wsprintf(szBuf, "CS=%0X \tTI=%d\tRING=%d"
                "\nDS=%0X \tTI=%d\tRING=%d",
     uSelCS, uTICS, uSelCS & 3,
     uSelDS, uTIDS, uSelDS & 3);

  MessageBox(NULL, (LPSTR)szBuf,
     "CS & DS selector's", MB_OK);
  return 0;
}

Это приложение переписывает текущее содержимое регистров процессора CS и DS в переменные uSelCS и uSelDS. Далее содержимое бита TI селекторов, взятых из регистров DS и CS, переписывается в переменные uTICS и uTIDS, соответственно.

Запустив это нехитрое приложение, вы сможете убедиться, что операционная система Windows версии 3.1 предоставляет приложениям самый низкий уровень привилегий, располагая их в третьем кольце защиты (рис. 2.9).

Рис. 2.9. Содержимое регистров CS и DS

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

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

Используйте только те селекторы, которые получены приложением от операционной системы Windows.

Файл определения модуля приложения SELECTOR ничем не примечателен и приведен в листинге 2.2.

Листинг 2.2. Файл selector/selector.def

; =============================
; Файл определения модуля
; =============================
NAME        SELECTOR
DESCRIPTION 'Приложение SELECTOR, (C) 1994, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   8120
HEAPSIZE    1024
CODE        preload moveable discardable
DATA        preload moveable multiple

Обработка прерываний в защищенном режиме

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

Как вы знаете, существуют аппаратные и программные прерывания. Аппаратные прерывания вырабатываются периферийными устройствами, как правило, при завершении ими операции ввода/вывода. Эти прерывания являются асинхронными по отношению к запущенным программам. Программные прерывания вызываются командой INT. Программные прерывания являются синхронными, так как они инициируются самой программой.

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

Программы MS-DOS широко используют программные прерывания для получения обслуживания от MS-DOS и BIOS.

Механизм обработки прерываний в защищенном режиме намного сложнее. Для определения адресов обработчиков прерываний в защищенном режиме используется дескрипторная таблица прерываний IDT (Interrupt Descriptor Table ), расположение которой определяется содержимым специального системного регистра. Эта таблица содержит дескрипторы специальных типов - вентили прерываний, вентили исключений и вентили задач.

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

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

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

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

2.2. Память в различных режимах работы Windows

В зависимости от режима работы (стандартный или расширенный) операционная система Windows использует то или иное распределение памяти. В любом случае процессор работает в защищенном режиме.

Операционная система Windows версии 3.0 могла работать и в реальном режиме (для чего ее надо было запускать с параметром /r), однако версия 3.1 этот режим больше не поддерживает.

Сетевой вариант операционной системы Windows - Windows for Workgroups версии 3.11 работает только в расширенном режиме.

Стандартный режим работы

Для работы Windows версии 3.1 в стандартном режиме в компьютере должен быть установлен процессор 80286 или 80386, а также не менее 1-2 Мбайт оперативной памяти.

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

В пределах первых 640 Кбайт стандартной памяти находится операционная система MS-DOS, драйверы и резидентные программы, запущенные до загрузки Windows.

В стандартном режиме Windows версии 3.1 не может адресовать более 16 Мбайт памяти. Такое ограничение накладывается схемой адресации процессора 80286. Даже если вы запустили Windows в стандартном режиме на процессоре 80386, указанное ограничение продолжает действовать, так как в этом случае процессор 80386 будет работать с памятью как его предшественник, процессор 80286.

Расширенный режим работы

Расширенный режим работы Windows доступен в том случае, если в компьютере установлен процессор 80386, 80486, Pentium, и имеется по крайней мере 2 Мбайт расширенной оперативной памяти.

В расширенном режиме включается механизм виртуальной памяти, позволяющий теоретически адресовать до 512 Мбайт памяти. При этом в Windows версии 3.1 можно использовать до 256 Мбайт физической оперативной памяти и создать файл виртуальной памяти размером до 256 Мбайт.

Схема адресации памяти процессора 80386 накладывает ограничение на количество дескрипторов в глобальной и локальной таблице дескрипторов - можно создать не более 8192 дескрипторов в каждой из таблиц. Так как все приложения Windows версии 3.1 используют одну общую локальную таблицу дескрипторов, всего для приложений Windows можно создать не более 8192 дескриптора, описывающих сегменты размером до 64 Кбайт. Однако реально создаются сегменты меньшего размера, поэтому Windows не позволяет приложениям использовать все 512 Мбайт памяти.

Сегодня такие требования к объему оперативной памяти могут показаться излишними. Однако не так давно память объемом 1024 Кбайт (т. е. целый мегабайт!) удовлетворяла всех (или почти всех) пользователей персонального компьютера IMB PC. С широким внедрением персональных компьютеров в область обработки видеоинформации (в том числе в реальном времени) требования к объему оперативной памяти и быстродействию всех системы существенно возрастают. В любом случае уже сейчас многие приложения Windows соглашаются работать только в расширенном режиме.

Глобальная и локальная область памяти

Свободное пространство в области стандартной памяти и расширенная память используются операционной системой Windows. Она как бы объединяет всю свободную память в одну глобальную область памяти (global heap ) и использует эту область для себя и для запуска приложений Windows.

Помимо глобальной области памяти, для каждого приложения Windows выделяется собственная локальная область памяти (local heap ). Размер этой области ограничен величиной 64 Кбайт. Если для приложения требуются блоки памяти большего размера, оно может их получить из глобальной области памяти

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

Типы сегментов

В операционной системе MS-DOS с точки зрения процессора все сегменты памяти были одинаковыми. Деление их на сегменты кода и сегменты данных достаточно условное, так как в MS-DOS нет никаких препятствий для того чтобы загрузить в CS:IP адрес любого сегмента, например, сегмента данных программы. Точно также программа могла выполнять любые операции в своем (или чужом) сегменте кода, или в сегменте, который принадлежит операционной системе. Так как в MS-DOS одновременно может работать только одна программа, вся оперативная память отдается ей в полное распоряжение.

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

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

К счастью, операционная система Windows умеет объединять свободные блоки памяти, используя механизм перемещаемых (moveable ) и удаляемых (discardable ) сегментов.

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

Согласно этой схеме на первом шаге получения доступа приложение заказывает для себя блок памяти, расположенный в фиксированном или перемещаемом сегменте. Фиксированный (fixed ) сегмент имеет постоянный логический адрес <селектор:смещение> и не никогда не перемещается в адресном пространстве. Если ваше приложение заказывает для себя память при помощи функции malloc , она получает память в фиксированном сегменте.

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

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

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

После использования блок памяти следует расфиксировать, для того чтобы Windows могла его перемещать.

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

Таким образом, даже если ваше приложение, работающее в защищенном режиме, заказало фиксированный блок памяти, он все равно остается перемещаемым! Поэтому, создавая приложения для Windows версии 3.1 вы можете использовать фиксированные блоки памяти почти без ущерба для эффективности работы системы дефрагментации (так как логический адрес фиксированного блока не изменяется, это может привести к невозможности освобождения непрерывного пространства в таблице дескрипторов, нужного для адресации блоков памяти размером, большим 64 Кбайт).

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

Фиксирование линейного адреса и отмена страничного обмена - крайняя мера. Обычные приложения Windows не должны вызывать функции GlobalFix и GlobalPageLock, так как это может привести к снижению производительности работы операционной системы и приложений.

Логический адрес перемещаемого блока памяти, состоящий из селектора и смещения, может произвольно изменяться операционной системой Windows. Для фиксирования блоков памяти в логическом адресном пространстве необходимо использовать функции GlobalLock или LocalLock.

Операционная система Windows версии 3.1 перемещает зафиксированные блоки памяти, изменяя базовый адрес в локальной таблице дескрипторов, поэтому такое перемещение не приводит к изменению логического адреса <селектор:смещение> и незаметно для приложений. Даже если приложение заказывает фиксированный блок памяти, но не вызывает функцию GlobalFix, этот блок памяти будет перемещаемым.

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

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

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

CODE preload moveable discardable
DATA preload moveable multiple

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

2.3. Работа с памятью в приложениях Windows

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

Глобальная динамическая память

В Windows версии 3.1 область глобальной памяти общая для всех приложений Windows. Теоретически одно приложение может заказать для себя блок памяти из глобальной области и передать его идентификатор другому приложению, однако такая практика не приветствуется, так как в следующих версиях Windows адресные пространства приложений могут быть разделены (для передачи данных между приложениями необходимо использовать механизм динамической передачи данных DDE, который будет описан в одном из следующих томов "Библиотеки системного программиста").

Получение глобального блока памяти

Для получения глобального блока памяти вы должны использовать функцию GlobalAlloc :

HGLOBAL WINAPI GlobalAlloc(UINT fuAlloc, DWORD cbAlloc);

Параметр fuAlloc определяет тип выделяемой памяти. Размер блока памяти в байтах должен передаваться через параметр cbAlloc, причем вы можете заказать блок памяти размером больше, чем 64 Кбайт. Для стандартного режима работы Windows можно заказать блок памяти размером до 1 Мбайт без 80 байт, для расширенного - до 16 Мбайт без 64 Кбайт.

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

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

Флаг Описание
GMEM_DDESHARE Блок памяти будет использоваться совместно несколькими приложениями при помощи механизма динамического обмена данными DDE
GMEM_DISCARDABLE Заказывается удаляемый блок памяти. Этот флаг должен использоваться совместно с флагом GMEM_MOVEABLE
GMEM_FIXED Заказывается фиксированный блок памяти. Этот флаг несовместим с флагом GMEM_MOVEABLE.При работе в среде Windows версии 3.1 в защищенном режиме фиксированный сегмент, созданный с использованием флага GMEM_FIXED, является перемещаемым. Для такого сегмента в процессе перемещения логический адрес не изменяется, но линейный (и, следовательно, физический) может изменяться
GMEM_LOWER Синоним для GMEM_NOT_BANKED. Не используется в Windows версии 3.1
GMEM_MOVEABLE Заказывается перемещаемый блок памяти. Логический адрес перемещаемого блока памяти может изменяться. Этот флаг несовместим с флагом GMEM_FIXED
GMEM_NOCOMPACT Для удовлетворения запроса памяти не следует выполнять объединение всех свободных участков памяти в один и удалять блоки памяти, отмеченные как удаляемые
GMEM_NODISCARD Для удовлетворения запроса памяти не следует выполнять объединение всех свободных участков памяти в один
GMEM_NOT_BANKED Получить блок памяти вне фрейма дополнительной памяти EMS. Не используется в Windows версии 3.1
GMEM_NOTIFY Если заказанный объект будет удален, требуется вызов процедуры извещения. Процедура извещения назначается функцией GlobalNotify и должна располагаться в фиксированном сегменте кода в библиотеке DLL. С ее помощью приложение может разрешить или запретить Windows удалять блок данных
GMEM_SHARE Синоним для GMEM_DDESHARE
GMEM_ZEROINIT Во все байты блока необходимо записать нулевые значения
GHDN Синоним для комбинации флагов GMEM_MOVEABLE и GMEM_ZEROINIT
GPTR Синоним для комбинации флагов GMEM_FIXED и GMEM_ZEROINIT

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

Приведем для примера фрагмент кода, в котором мы получаем перемещаемый блок памяти размером 200000 байт, причем во все байты полученного блока записываются нулевые значения:

hmemGlobal = GlobalAlloc(GHND, 200000l);

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

hmemGlobalDisc = GlobalAlloc(
  GMEM_MOVEABLE | GMEM_DISCARDABLE, 200000l);

Фиксирование и расфиксирование блока памяти

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

void FAR* WINAPI GlobalLock(HGLOBAL hglb);

Функция GlobalLock фиксирует блок памяти, идентификатор которого передается ей через параметр hglb и возвращает логический адрес зафиксированного блока или NULL, если указанный блок удален или произошла ошибка.

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

Для каждого блока памяти Windows поддерживает счетчик фиксирований. Этот счетчик увеличивается при вызове функции GlobalLock и уменьшается при расфиксировании блока функцией GlobalUnlock :

BOOL WINAPI GlobalUnlock(HGLOBAL hglb);

Если содержимое счетчика уменьшилось до нуля, функция возвращает значение FALSE. В противном случае возвращается TRUE.

Файл windowsx.h содержит макрокоманды, облегчающие работу с глобальными блоками памяти. Например, макрокоманда GlobalAllocPtr получает блок памяти и фиксирует его:

#define GlobalAllocPtr(flags, cb) \
  (GlobalLock(GlobalAlloc((flags), (cb))))

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

Определение идентификатора блока памяти по его адресу

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

DWORD WINAPI GlobalHandle(UINT uGlobalSel);

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

Младшее слово возвращаемого значения содержит идентификатор блока памяти, старшее - селектор блока памяти. В случае ошибки возвращается нулевое значение.

В файле windowsx.h определена макрокоманда GlobalPtrHandle , упрощающая получение идентификатора блока памяти по его логическому адресу:

#define GlobalPtrHandle(lp) \
  ((HGLOBAL)LOWORD(GlobalHandle(SELECTOROF(lp))))

Макрокоманда SELECTOROF определена в файле windows.h и предназначена для получения селекторной компоненты логического адреса:

#define SELECTOROF(lp) HIWORD(lp)

В файле windows.h есть также определения для макрокоманды OFFSETOF , возвращающей компоненту смещения, и макрокоманда MAKELP , конструирующая указатель из компонент смещения и селектора:

#define OFFSETOF(lp)   LOWORD(lp)
#define MAKELP(sel, off) ((void FAR*)MAKELONG((off), (sel)))
#define MAKELONG (low, high) ((LONG)(((WORD)(low)) | \
  (((DWORD)((WORD)(high))) << 16)))

Если вы работаете с транслятором Borland C++ for Windows, вместо этих макрокоманд можете использовать знакомые вам макрокоманды FP_SEG , FP_OFF и MK_FP , описанные в файле dos.h:

#define FP_SEG(fp)((unsigned)(void _seg*)(void far*)(fp))
#define FP_OFF(fp)((unsigned)(fp))
#define MK_FP(seg,ofs)((void _seg*)(seg)+(void near*)(ofs))

Работа с удаляемыми блоками памяти

Для того чтобы заказать удаляемый блок памяти, вы должны указать флаги GMEM_DISCARDABLE и GMEM_MOVEABLE , например:

hmemGlDiscard =
  GlobalAlloc(GMEM_MOVEABLE | GMEM_DISCARDABLE, 200000l);

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

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

Приведем прототип функции GlobalFlags:

UINT WINAPI GlobalFlags(HGLOBAL hglb);

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

Если установлен флаг GMEM_DISCARDABLE, проверяемый блок памяти может быть удален Windows в процессе дефрагментации свободной области памяти. Если же установлен флаг GMEM_DISCARDED, удаление блока памяти уже произошло.

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

Приведем прототип функции GlobalReAlloc :

HGLOBAL WINAPI GlobalReAlloc(HGLOBAL hglb, 
  DWORD cbNewSize, UINT fuAlloc);

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

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

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

Флаг Описание
GMEM_DISCARDABLE Если блок памяти был перемещаемый, то теперь дополнительно он будет и удаляемый. Этот флаг должен использоваться совместно с флагом GMEM_MODIFY
GMEM_MODIFY Выполняется изменение характеристик существующего блока памяти. этот флаг необходимо указывать вместе с флагами GMEM_DISCARDABLE и GMEM_MOVEABLE
GMEM_MOVEABLE Если раньше указанный блок памяти был перемещаемым и удаляемым, содержимое счетчика фиксирования для него равно нулю и новый размер блока указан равным нулю, данный блок будет удален. Если же параметр cbNewSize равен нулю, но блок памяти не является перемещаемым или удаляемым, функция вернет признак ошибки.Для фиксированного блока памяти ненулевой длины данный флаг разрешает перемещение, т. е. преобразует фиксированный блок в перемещаемый.
GMEM_NODISCARD Блок памяти не может быть удален операционной системой. Этот флаг должен использоваться совместно с флагом GMEM_MODIFY
GMEM_ZEROINIT Если размер блока памяти увеличивается, во все байты дополнительной памяти необходимо записать нулевые значения. Этот флаг несовместим с флагом GMEM_MODIFY

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

Изменение блока памяти

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

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

hmemGlobal = GlobalReAlloc(hmemGlobal, 51200,
 GMEM_MODIFY | GMEM_DISCARDABLE | GMEM_MOVEABLE |
 GMEM_ZEROINIT);

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

Учтите, что при увеличении размера блока может возникнуть ситуация нехватки памяти, поэтому проверяйте значение идентификатора, возвращаемой функцией GlobalReAlloc, на неравенство константе NULL.

Вы можете инициировать удаление блока памяти при помощи функции GlobalReAlloc, если укажите нулевой размер блока и флаг GMEM_MOVEABLE. В файле windows.h имеется определение макрокоманды GlobalDiscard, при помощи которой приложение может принудительно удалить блок из памяти:

#define GlobalDiscard(h) GlobalReAlloc(h, 0L, GMEM_MOVEABLE)

Определение размера блока памяти

С помощью функции GlobalSize вы можете определить размер блока памяти по его идентификатору:

DWORD WINAPI GlobalSize(HGLOBAL hglb);

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

Дефрагментация памяти

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

DWORD WINAPI GlobalCompact(DWORD dwMinFree);

Функция возвращает размер самого большого доступного непрерывного блока памяти, причем, если параметр на равен 0 или -1, выполняется дефрагментация памяти и удаление блоков, отмеченных как удаляемые. Если параметр функции указан как 0 или -1, функция не выполняет дефрагментацию памяти, но возвращает правильное значение с учетом возможного выполнения дефрагментации.

Получение памяти в первом мегабайте адресного пространства

Если у вас возникает необходимость заказать память, доступную приложениям MS-DOS, и располагающуюся в первом мегабайте адресного пространства, воспользуйтесь функцией GlobalDosAlloc :

DWORD WINAPI GlobalDosAlloc(DWORD cbAlloc);

Параметр cbAlloc определяет размер блока в байтах.

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

Младшее слово возвращаемого значения содержит селектор для доступа к полученному блоку в защищенном режиме.

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

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

UINT WINAPI GlobalDosFree(UINT uSelector);

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

В случае успешного завершения возвращаемое значение равно нулю. При ошибке возвращается селектор, указанный в параметре uSelector.

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

Освобождение глобального блока памяти

Для освобождения глобального блока памяти, полученного от функции GlobalAlloc, вы должны использовать функцию GlobalFree :

HGLOBAL WINAPI GlobalFree(HGLOBAL hglb);

Идентификатор освобождаемого блока передается функции в качестве ее единственного параметра.

Функция возвращает NULL при успешном завершении или значение hglb при ошибке.

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

Для освобождения памяти, полученной при помощи макрокоманды GlobalAllocPtr, удобно использовать макрокоманду GlobalFreePtr , описанную в файле windowsx.h:

#define GlobalFreePtr(lp) \
  (GlobalUnlockPtr(lp),(BOOL)GlobalFree(GlobalPtrHandle(lp)))

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

Фиксирование линейного адреса блока памяти

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

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

void WINAPI GlobalFix(HGLOBAL hglb);

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

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

void WINAPI GlobalUnfix(HGLOBAL hglb);

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

Фиксирование страниц блока памяти

В расширенном режиме работы операционной системы Windows версии 3.1 используется виртуальная память. Как мы уже говорили, при использовании виртуальной памяти вся глобальная область памяти делится на страницы размером 4 Кбайт. Эти страницы могут располагаться в физической оперативной памяти или на диске в специальном файле виртуальной памяти. Если приложение обращается к странице, которая отсутствует в физической оперативной памяти, она загружается туда из файла виртуальной памяти.

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

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

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

UINT WINAPI GlobalPageLock(HGLOBAL hglb);

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

Операционная система Windows поддерживает счетчик блокирования страничного обмена. Содержимое этого счетчика увеличивается на единицу при каждом вызове функции GlobalPageLock.

Функция GlobalPageLock возвращает новое значение счетчика или ноль при ошибке.

Как только надобность в блокировке страничного обмена отпадает, следует вызвать функцию GlobalPageUnlock :

UINT WINAPI GlobalPageUnlock(HGLOBAL hglb);

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

Приложение GMEM

Для демонстрации использования основных функций управления глобальной памятью мы приведем исходный текст приложения GMEM (листинг 2.3).

Листинг 2.3. Файл gmem/gmem.cpp

#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <dos.h>

#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance,
   HINSTANCE hPrevInstance,
   LPSTR     lpszCmdLine, int nCmdShow)
{
  BYTE    szBuf[100];
  HGLOBAL hmemGlobal;
  HGLOBAL hmemGlDiscard;
  LPVOID  lpvoidGlobal;
  LPVOID  lpvoidGlDiscard;
  DWORD   dwMaxFreeMem;

  // Определяем размер доступной памяти
  dwMaxFreeMem = GlobalCompact(-1l);

  wsprintf(szBuf, "Доступно памяти:\t%lu\n",
        dwMaxFreeMem);
  MessageBox(NULL, (LPSTR)szBuf, "Global Block", MB_OK);

  // --------------------------------------------------------
  // Работаем с перемещаемым блоком памяти
  // --------------------------------------------------------

  // Дефрагментируем память для получения блока
  // размером 100000 байт
  dwMaxFreeMem = GlobalCompact(100000l);

  // Заказываем буфер размером 100000 байт
  hmemGlobal = GlobalAlloc(GHND, 100000l);

  if(hmemGlobal != NULL)
  {
    // Если буфер получен, фиксируем его в памяти
    lpvoidGlobal = GlobalLock(hmemGlobal);
    if(lpvoidGlobal != (LPVOID) NULL)
    {
      // Если блок успешно зафиксирован,
      // выводим значения идентификатора блока
      // и логический адрес блока
      wsprintf(szBuf, "hmemGlobal=\t%04.4X\n"
        "lpvoidGlobal=\t%04.4X:%04.4X",
        hmemGlobal,
        FP_SEG(lpvoidGlobal), FP_OFF(lpvoidGlobal));

      MessageBox(NULL, (LPSTR)szBuf, "Global Block", MB_OK);

      // -----------------------------------------
      // Можно работать с полученным блоком памяти
      // Записываем в первый байт блока символ S
      *(LPSTR)lpvoidGlobal = 'S';
      // -----------------------------------------

      // Разрешаем перемещение блока
      GlobalUnlock(hmemGlobal);
    }
    else
    {
      MessageBox(NULL, "Ошибка при фиксировании блока",
        "Global Block", MB_OK);
    }
    // Отдаем блок памяти операционной системе
    GlobalFree(hmemGlobal);
  }
  else
  {
    MessageBox(NULL, "Мало памяти для перемещаемого блока",
      "Global Block", MB_OK);
  }

  // --------------------------------------------------------
  // Работаем с удаляемым блоком памяти
  // --------------------------------------------------------

  // Заказываем удаляемый блок памяти размером 200000 байт
  hmemGlDiscard =
      GlobalAlloc(GMEM_MOVEABLE | GMEM_DISCARDABLE, 200000l);

  if(hmemGlDiscard != NULL)
  {
    // Если мы его получили, удаляем блок
    GlobalDiscard(hmemGlDiscard);

    // Пытаемся зафиксировать блок памяти
    lpvoidGlDiscard = GlobalLock(hmemGlDiscard);

    if(lpvoidGlDiscard != (LPVOID) NULL)
    {
      // Если удалось (чего не должно быть, так как
      // мы только что удалили блок), выводим
      // идентификатор блока и логический адрес
      wsprintf(szBuf, "hmemGlDiscard=\t%04.4X\n"
        "lpvoidGlDiscard=\t%04.4X:%04.4X",
        hmemGlDiscard,
        FP_SEG(lpvoidGlDiscard), FP_OFF(lpvoidGlDiscard));
      MessageBox(NULL, (LPSTR)szBuf, "Global Block", MB_OK);

      // Разрешаем перемещение блока
      GlobalUnlock(hmemGlDiscard);
    }
    else
    {
      // Если блок памяти не удалось зафиксировать,
      // проверяем, не был ли он удален
      if(GlobalFlags(hmemGlDiscard) & GMEM_DISCARDED)
      {
         MessageBox(NULL, 
           "Блок удален и мы его восстанавливаем",
           "Global Block", MB_OK);

         // Восстанавливаем удаленный блок памяти
         hmemGlDiscard = 
           GlobalReAlloc(hmemGlDiscard, 200000l,
           GMEM_MOVEABLE | GMEM_DISCARDABLE);

         // Фиксируем блок памяти
         lpvoidGlDiscard = GlobalLock(hmemGlDiscard);

         if(lpvoidGlDiscard != (LPVOID) NULL)
         {
           // Выводим идентификатор и логический адрес
           // зафиксированного блока памяти
           wsprintf(szBuf, "hmemGlDiscard=\t%04.4X\n"
             "lpvoidGlDiscard=\t%04.4X:%04.4X",
             hmemGlDiscard,
             FP_SEG(lpvoidGlDiscard),
             FP_OFF(lpvoidGlDiscard));
           MessageBox(NULL, (LPSTR)szBuf,
             "Global Block", MB_OK);

           // Освобождаем блок памяти
           GlobalUnlock(hmemGlDiscard);
         }
         else
         {
           MessageBox(NULL, "Ошибка при фиксировании блока",
           "Global Block", MB_OK);
         }
      }
    }

    // Отдаем удаляемый блок памяти операционной системе
    GlobalFree(hmemGlDiscard);
  }
  else
  {
    MessageBox(NULL, "Мало памяти для удаляемого блока",
      "Global Block", MB_OK);
  }

  return 0;
}

Перед началом работы приложение определяет объем свободной памяти, вызывая функцию GlobalCompact со значением параметра, равным -1. Определенное значение выводится на экран при помощи функции MessageBox.

Далее приложение вызывает функцию GlobalCompact еще раз для освобождения непрерывного блока свободной памяти размером 100000 байт.

После этого приложение заказывает буфер размером 100000 байт, вызывая функцию GlobalAlloc. В качестве первого параметра этой функции указана константа GHND, соответствующее перемещаемой памяти, инициализированной нулевым значением.

В случае успешного получения блока памяти он фиксируется и на экран выводится значение идентификатора блока и его логический адрес. После этого в первый байт полученного блока записывается код символа 'S' и блок расфиксируется. При ошибке выдается сообщение.

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

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

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

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

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

Файл определения модуля приложения GMEM приведен в листинге 2.4.

Листинг 2.4. Файл gmem/gmem.def

; =============================
; Файл определения модуля
; =============================
NAME        GMEM
DESCRIPTION 'Приложение GMEM, (C) 1994, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
CODE        preload moveable discardable
DATA        preload moveable multiple

Локальная динамическая память

Для каждого приложения Windows создается автоматический сегмент данных размером 64 Кбайт, в котором располагаются статические данные , стек и локальная область данных (local heap ). Кроме этого, автоматический сегмент данных имеет заголовок размером 16 байт (рис. 2.10).

Рис. 2.10. Автоматический сегмент данных приложения Windows

Размер стека определяется оператором STACKSIZE в файле определения модуля:

STACKSIZE   8120

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

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

HEAPSIZE    1024

Вы можете указать любое отличное от нуля значение.

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

Получение локального блока памяти

Для получения локального блока памяти вы должны использовать функцию LocalAlloc :

HLOCAL WINAPI LocalAlloc(UINT fuAlloc, UINT cbAlloc);

Параметр fuAlloc определяет тип выделяемой памяти. Размер блока памяти в байтах должен передаваться через параметр cbAlloc.

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

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

Флаг Описание
LMEM_DISCARDABLE Заказывается удаляемый блок памяти. Этот флаг должен использоваться совместно с флагом LMEM_MOVEABLE
LMEM_FIXED Заказывается фиксированный блок памяти (в защищенном режиме работы блок памяти будет перемещаемым, даже если он заказан с использованием флага LMEM_FIXED, однако в процессе перемещения будет изменяться только линейный адрес, но не логический). Этот флаг несовместим с флагом LMEM_MOVEABLE
LMEM_MOVEABLE Заказывается перемещаемый блок памяти. Логический адрес такого блока может изменяться в процессе перемещения. Этот флаг несовместим с флагом LMEM_FIXED
LMEM_NOCOMPACT Для удовлетворения запроса памяти не следует выполнять объединение всех свободных участков памяти в один и удалять блоки памяти, отмеченные как удаляемые
LMEM_NODISCARD Для удовлетворения запроса памяти не следует выполнять объединение всех свободных участков памяти в один
LMEM_ZEROINIT Во все байты блока необходимо записать нулевые значения
NONZEROLHND Синоним для LMEM_MOVEABLE
NONZEROLPTR Синоним для LMEM_FIXED
LHDN Синоним для комбинации флагов LMEM_MOVEABLE и LMEM_ZEROINIT
LPTR Синоним для комбинации флагов LMEM_FIXED и LMEM_ZEROINIT

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

hmemLocal = LocalAlloc(LHND, 2000);

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

hmemLocalDisc = LocalAlloc(
  LMEM_MOVEABLE | LMEM_DISCARDABLE, 200);

Фиксирование и расфиксирование блока памяти

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

void NEAR* WINAPI LocalLock(HLOCAL hloc);

Функция LocalLock фиксирует блок памяти, идентификатор которого передается ей через параметр hloc и возвращает логический адрес зафиксированного блока или NULL, если указанный блок удален или произошла ошибка.

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

Для каждого блока памяти Windows поддерживает счетчик фиксирования. Этот счетчик увеличивается при вызове функции LocalLock и уменьшается при расфиксировании блока функцией LocalUnlock :

BOOL WINAPI LocalUnlock(HLOCAL hloc);

Если содержимое счетчика уменьшилось до нуля, функция возвращает значение FALSE. В противном случае возвращается TRUE.

Определение идентификатора блока памяти по его адресу

С помощью функции LocalHandle вы можете определить идентификатор локального блока памяти:

HLOCAL WINAPI LocalHandle(void NEAR* pvMem);

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

Возвращаемое значение содержит идентификатор локального блока памяти. В случае ошибки возвращается нулевое значение.

Работа с удаляемыми блоками памяти

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

Приведем прототип функции LocalReAlloc :

HLOCAL WINAPI LocalReAlloc(HLOCAL hloc, 
  UINT cbNewSize, UINT fuAlloc);

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

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

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

Флаг Описание
LMEM_DISCARDABLE Если блок памяти был перемещаемый, то теперь дополнительно он будет и удаляемый. Этот флаг должен использоваться совместно с флагом LMEM_MODIFY
LMEM_MODIFY Выполняется изменение характеристик существующего блока памяти. этот флаг необходимо указывать вместе с флагами LMEM_DISCARDABLE и LMEM_MOVEABLE
LMEM_MOVEABLE Если раньше указанный блок памяти был перемещаемым и удаляемым, содержимое счетчика фиксирования для него равно нулю и новый размер блока указан равным нулю, данный блок будет удален. Если же параметр cbNewSize равен нулю, но блок памяти не является перемещаемым или удаляемым, функция вернет признак ошибки.Для фиксированного блока памяти ненулевой длины данный флаг разрешает перемещение, т. е. преобразует фиксированный блок в перемещаемый.
LMEM_NODISCARD Блок памяти не может быть удален операционной системой. Этот флаг должен использоваться совместно с флагом LMEM_MODIFY
LMEM_ZEROINIT Если размер блока памяти увеличивается, во все байты дополнительной памяти необходимо записать нулевые значения. Этот флаг несовместим с флагом LMEM_MODIFY

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

Определение характеристик локального блока памяти

Для определения характеристик локального блока памяти предназначена функция LocalFlags , аналогичная рассмотренной нами ранее функции GlobalFlags:

UINT WINAPI LocalFlags(HLOCAL hloc);

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

Если установлен флаг LMEM_DISCARDABLE, проверяемый блок памяти может быть удален Windows в процессе дефрагментации свободной области памяти. Если же установлен флаг LMEM_DISCARDED, удаление блока памяти уже произошло.

Определение размера блока памяти

С помощью функции LocalSize вы можете определить размер блока памяти по его идентификатору:

UINT WINAPI LocalSize(HLOCAL hloc);

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

Дефрагментация локального блока памяти

Функция LocalCompact выполняет дефрагментацию свободного пространства в локальной области данных:

UINT WINAPI LocalCompact(UINT uMinFree);

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

Уменьшение размера локального блока памяти

Для уменьшения размера существующего локального блока памяти можно использовать функцию LocalShrink :

UINT WINAPI LocalShrink(HLOCAL hloc, UINT cbNewSize);

Параметр hloc указывает идентификатор изменяемого локального блока памяти. Новые размеры блока памяти задаются параметром cbNewSize.

Возвращаемое значение в случае успеха равно новому размеру блока памяти.

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

Освобождение локального блока памяти

Для освобождения локального блока памяти, полученного от функции LocalAlloc, вы должны использовать функцию LocalFree :

HLOCAL WINAPI LocalFree(HLOCAL hloc);

Идентификатор освобождаемого блока передается функции в качестве ее единственного параметра.

Функция возвращает NULL при успешном завершении или значение hloc при ошибке.

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

Инициализация локальной области данных в заданном сегменте

Для создания и инициализации локальной области данных в заданном сегменте вы можете воспользоваться функцией LocalInit :

BOOL WINAPI LocalInit(UINT uSegment, 
  UINT uStartAddr, UINT uEndAddr);

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

Параметр uStartAddr определяет начальный адрес локальной области данных, а параметр uEndAddr - конечный адрес локальной области данных.

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

Приложение LMEM

Приведем исходный текст приложения LMEM, которое работает аналогично приложению GMEM, но с использованием локальной области памяти (листинг 2.5).

Листинг 2.5. Файл lmem/lmem.cpp

#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <dos.h>

#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance,
   HINSTANCE hPrevInstance,
   LPSTR     lpszCmdLine, int nCmdShow)
{
  BYTE szBuf[100];
  HLOCAL hmemLocal;
  HLOCAL hmemLoDiscard;
  void*  pLocal;
  void*  pLoDiscard;
  UINT   uMaxFreeMem;

  // Определяем размер доступной памяти
  uMaxFreeMem = LocalCompact(0);

  wsprintf(szBuf, "Доступно памяти:\t%u\n",
        uMaxFreeMem);
  MessageBox(NULL, (LPSTR)szBuf, "Local Block", MB_OK);

  // --------------------------------------------------------
  // Работаем с перемещаемым блоком памяти
  // --------------------------------------------------------

  // Дефрагментируем память для получения блока
  // размером 1000 байт
  uMaxFreeMem = LocalCompact(1000);

  // Заказываем буфер размером 1000 байт
  hmemLocal = LocalAlloc(GHND, 1000);

  if(hmemLocal != NULL)
  {
    // Если буфер получен, фиксируем его в памяти
    pLocal = LocalLock(hmemLocal);
    if(pLocal != NULL)
    {
      // Если блок успешно зафиксирован,
      // выводим значения идентификатора блока
      // и логический адрес блока
      wsprintf(szBuf, "hmemLocal=\t%04.4X\n"
        "pLocal=\t\t%04.4X",
        hmemLocal, pLocal);

      MessageBox(NULL, (LPSTR)szBuf, "Local Block", MB_OK);

      // -----------------------------------------
      // Можно работать с полученным блоком памяти
      // Записываем в первый байт блока символ S
      *(PSTR)pLocal = 'S';
      // -----------------------------------------

      // Разрешаем перемещение блока
      LocalUnlock(hmemLocal);
    }
    else
    {
      MessageBox(NULL, "Ошибка при фиксировании блока",
        "Local Block", MB_OK);
    }
    // Отдаем блок памяти операционной системе
    LocalFree(hmemLocal);
  }
  else
  {
    MessageBox(NULL, "Мало памяти для перемещаемого блока",
      "Local Block", MB_OK);
  }

  // --------------------------------------------------------
  // Работаем с удаляемым блоком памяти
  // --------------------------------------------------------

  // Заказываем удаляемый блок памяти размером 2000 байт
  hmemLoDiscard =
      LocalAlloc(LMEM_MOVEABLE | LMEM_DISCARDABLE, 2000);

  if(hmemLoDiscard != NULL)
  {
    // Если мы его получили, удаляем блок
    LocalDiscard(hmemLoDiscard);

    // Пытаемся зафиксировать блок памяти
    pLoDiscard = LocalLock(hmemLoDiscard);

    if(pLoDiscard != NULL)
    {
      wsprintf(szBuf, "hmemLoDiscard=\t%04.4X\n"
        "pLoDiscard=\t%04.4X",
        hmemLoDiscard, pLoDiscard);
      MessageBox(NULL, (LPSTR)szBuf, "Local Block", MB_OK);

      // Разрешаем перемещение блока
      LocalUnlock(hmemLoDiscard);
    }
    else
    {
      // Если блок памяти не удалось зафиксировать,
      // проверяем, не был ли он удален
      if(LocalFlags(hmemLoDiscard) & LMEM_DISCARDED)
      {
         MessageBox(NULL, 
           "Блок удален и мы его восстанавливаем",
           "Local Block", MB_OK);

         // Восстанавливаем удаленный блок памяти
         hmemLoDiscard = LocalReAlloc(hmemLoDiscard, 256,
           LMEM_MOVEABLE | LMEM_DISCARDABLE);

         // Фиксируем блок памяти
         pLoDiscard = LocalLock(hmemLoDiscard);

         if(pLoDiscard != NULL)
         {
           // Выводим идентификатор и логический адрес
           // зафиксированного блока памяти
           wsprintf(szBuf, "hmemLoDiscard=\t%04.4X\n"
             "pLoDiscard=\t%04.4X",
             hmemLoDiscard, pLoDiscard);
           MessageBox(NULL, (LPSTR)szBuf, 
             "Local Block", MB_OK);

           // Освобождаем блок памяти
           LocalUnlock(hmemLoDiscard);
         }
         else
         {
           MessageBox(NULL, "Ошибка при фиксировании блока",
           "Local Block", MB_OK);
         }
      }
    }

    // Отдаем удаляемый блок памяти операционной системе
    LocalFree(hmemLoDiscard);
  }
  else
  {
    MessageBox(NULL, "Мало памяти для удаляемого блока",
      "Local Block", MB_OK);
  }

  return 0;
}

Файл определения модуля приложения LMEM приведен в листинге 2.6.

Листинг 2.6. Файл lmem/lmem.def

; =============================
; Файл определения модуля
; =============================
NAME        LMEM
DESCRIPTION 'Приложение LMEM, (C) 1994, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   8120
HEAPSIZE    4096
CODE        preload moveable discardable
DATA        preload moveable multiple

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

Статическая память

Статические данные, описанные в приложении Windows с использованием ключевого слова static или объявленные как внешние переменные располагаются в автоматическом сегменте данных приложения (рис. 2.10).

В документации к SDK не рекомендуется в моделях памяти small и medium использовать дальние указатели на статические данные (См. раздел 16.5 руководства, который называется Traps to Avoid When Managing Program Data).

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

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

static LPSTR lpstrDlgName = "MyDlg";
........
hDlg = CreateDialog(hInst, 
           lpstrDlgName,
           hWndParent,
           (DLGPROC) lpDialogProc);

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

Рекомендуется в указанной выше ситуации использовать ближний статический указатель с явным преобразованием типа к LPSTR:

static PSTR pstrDlgName = "MyDlg";
........
hDlg = CreateDialog(hInst, 
           (LPSTR)pstrDlgName,
           hWndParent,
           (DLGPROC) lpDialogProc);

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

Автоматическая память

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

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

Дополнительная память в структуре класса окна

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

Для работы с этой дополнительной памятью предназначены функции SetClassWord, SetClassLong, GetClassWord, GetClassLong.

Функция SetClassWord устанавливает в структуре, описывающей класс для окна hwnd, новое значение wNewWord, при этом смещение устанавливаемого слова определяется параметром offset:

WORD WINAPI SetClassWord(HWND hwnd, int offset,
   WORD wNewWord);

Для параметра offset вы должны использовать значения от нуля до указанного в поле cbClsExtra минус 2 или следующие значения:

Значение Описание
GCW_HBRBACKGROUND Идентификатор кисти для закрашивания фона окна
GCW_HCURSOR Идентификатор курсора
GCW_HICON Идентификатор пиктограммы
GCW_STYLE Стили окна

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

В случае ошибки функция SetClassWord возвращает нулевое значение.

Функция GetClassWord позволяет вам прочитать содержимое слова дополнительной области памяти со смещением offset:

WORD WINAPI GetClassWord(HWND hwnd, int offset);

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

Значение Описание
GCW_CBCSLEXTRA Размер дополнительной области памяти в структуре класса окна
GCW_CBWNDEXTRA Размер дополнительной области памяти в структуре окна
GCW_HBRBACKGROUND Идентификатор кисти для закрашивания фона окна
GCW_HCURSOR Идентификатор курсора
GCW_HICON Идентификатор пиктограммы
GCW_STYLE Стили окна

Функция GetClassWord возвращает значение указанного слова из структуры класса окна или нулевое значение при ошибке.

Функция SetClassLong аналогична функции SetClassWord, но работает с двойными словами:

LONG WINAPI SetClassLong(HWND hwnd, int offset, LONG nVal);

Для параметра offset дополнительно можно указать значение GCL_WNDPROC, при этом функция заменит адрес функции окна. Мы пользовались этим приемом в приложении SMARTPAD, перехватывая управление у стандартной функции окна органа управления класса "edit".

В случае ошибки функция SetClassLong возвращает нулевое значение.

С помощью функции GetClassLong вы можете получить из структуры класса окна значение двойного слова, расположенного со смещением offset:

LONG WINAPI GetClassLong(HWND hwnd, int offset);

Для этой функции можно указать положительное смещение или одну из двух констант - GCL_WNDPROC и GCL_MENUNAME. В первом случае функция GetClassLong возвратит адрес функции окна для данного класса, во втором - указатель на строку имени меню, указанного при регистрации класса.

Дополнительная память в структуре окна

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

Для работы с этой дополнительной памятью предназначены функции SetWindowWord, SetWindowLong, GetWindowWord, GetWindowLong.

Функция SetWindowWord устанавливает в структуре, описывающей окно hwnd, новое значение wNewWord, при этом смещение устанавливаемого слова определяется параметром offset:

WORD WINAPI SetWindowWord(HWND hwnd, int offset,
   WORD wNewWord);

Для параметра offset вы должны использовать значения от нуля до указанного в поле cbClsExtra минус 2 или следующие значения:

Значение Описание
GWW_HINSTANCE Идентификатор приложения, владеющего данным окном
GWW_ID Идентификатор дочернего окна

В случае ошибки функция SetWindowWord возвращает нулевое значение.

Функция GetWindowWord позволяет вам прочитать содержимое слова дополнительной области памяти в структуре окна со смещением offset:

WORD WINAPI GetWIndowWord(HWND hwnd, int offset);

Для этой функции вы можете использовать следующие значения:

Значение Описание
GWW_HINSTANCE Идентификатор приложения, владеющего данным окном
GWW_HWNDPARENT Идентификатор родительского окна
GWW_ID Идентификатор дочернего окна

Функция GetWindowWord возвращает значение указанного слова из структуры класса окна или нулевое значение при ошибке.

Функция SetWindowLong аналогична функции SetWindowWord, но работает с двойными словами:

LONG WINAPI SetWindowLong(HWND hwnd, int offset, LONG nVal);

Для параметра offset дополнительно можно указать следующие значения:

Значение Описание
GWL_EXSTYLE Расширенный стиль окна
GWL_STYLE Стиль окна
GWL_WNDPROC Указатель на функцию окна

Если параметр hwnd содержит идентификатор диалоговой панели, вы можете использовать еще несколько значений:

Значение Описание
DWL_MSGRESULT Значение, возвращенное при обработке сообщения в функции диалоговой панели
DWL_USER Дополнительная информация, имеющая отношение к приложению, такая как идентификаторы или указатели
DWL_DLGPROC Указатель на функцию диалоговой панели

В случае ошибки функция SetWindowLong возвращает нулевое значение.

С помощью функции GetWindowLong вы можете получить из структуры окна значение двойного слова, расположенного со смещением offset:

LONG WINAPI GetWindowLong(HWND hwnd, int offset);

Для этой функции можно указать положительное смещение или одну из констант, описанных выше для функции SetWindowLong .

Ресурсы приложения

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

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

Загрузка ресурсов в оперативную память выполняется такими функциями, как LoadIcon или CreateDialog . Для загрузки ресурсов, имеющих нестандартный формат, вы должны использовать функции FindResource (поиск ресурса и получение идентификатора ресурса) и LoadResource (загрузка ресурса и получение идентификатора блока памяти, в который загружен найденный ресурс).

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

Приведем прототип функции FindResource :

HRSRC WINAPI FindResource(HINSTANCE hInst, 
   LPCSTR lpszName, LPCSTR lpszType);

Параметр hInst является идентификатором модуля, содержащего ресурс. Для извлечения ресурса из приложения вы должны указать его идентификатор, передаваемый функции WinMain через параметр hInstance.

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

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

Идентификатор ресурса Название ресурса
RT_ACCELERATOR Таблица акселераторов
RT_BITMAP Изображение bitmap
RT_CURSOR Курсор
RT_DIALOG Диалоговая панель
RT_FONT Шрифт
RT_FONTDIR Каталог шрифтов
RT_ICON Пиктограмма
RT_MENU Меню
RT_RCDATA Произвольные данные
RT_STRING Таблица строк

Вы можете использовать функцию FindResource для загрузки таких ресурсов, как пиктограммы или курсоры, указав ей тип ресурса, соответственно, RT_ICON или RT_CURSOR. Однако в документации к SDK сказано, что загрузку предопределенных ресурсов, таких как пиктограммы и курсоры, следует выполнять специально предназначенными для этого функциями (LoadIcon, LoadCursor и т. д.).

После того как ресурс найден, его следует загрузить, вызвав функцию LoadResource :

HGLOBAL WINAPI LoadResource(HINSTANCE hinst, HRSRC hrsrc);

Параметр hinst представляет собой идентификатор модуля, из файла которого загружается ресурс. Если ресурс загружается из файла вашего приложения, используйте значение hInstance, полученное через соответствующий параметр функции WinMain.

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

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

void FAR* WINAPI LockResource(HGLOBAL  hGlb);

В качестве параметра hGlb функции LockResource следует передать идентификатор ресурса, полученный от функции LoadResource.

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

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

BOOL WINAPI GlobalUnlock(HGLOBAL hGlb);
#define UnlockResource(h) GlobalUnlock(h)

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

BOOL WINAPI FreeResource(HGLOBAL hGlb);

В качестве параметра hGlb следует передать идентификатор ресурса, полученный от функции LoadResource.

2.4. Функция malloc и farmalloc

Для получения блока памяти приложения Windows могут использовать функцию malloc.

void *malloc(size_t size);

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

С помощью этой функции вы, однако, не сможете получить блок памяти размером большим, чем 64 Кбайт, так как тип size_t отображается на 16-разрядное целое.

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

void free(void *block); 

Если вам надо получить большой блок памяти, можно воспользоваться функцией farmalloc , которая входит в состав стандартной библиотеки Borland C++:

void far *farmalloc(unsigned long nbytes); 

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

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

void farfree(void far *block); 

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

Как вы знаете, локальная таблица дескрипторов может содержать не более чем 8192 дескриптора. Значительная часть локальной таблицы дескрипторов, которая в Windows версии 3.1 одна на все приложения, может быть занята самой операционной системой или другими приложениями. Если ваше приложение заказывает большое количество глобальных блоков памяти, используя функцию GlobalAlloc, таблица дескрипторов может быстро переполниться, так как для каждого блока памяти в локальной дескрипторной таблице создается отдельный дескриптор.

Для функции farmalloc используется другой метод. В локальной таблице дескрипторов создается один дескриптор, который адресует область памяти, используемую одновременно для выделения нескольких блоков памяти. Например, если вы заказали блок памяти размером 100 Кбайт, а затем из них освободили 50 Кбайт, при повторном запросе памяти она будет выделена из заказанного ранее блока без создания нового дескриптора. Таким образом, функция farmalloc позволяет экономить свободное пространство в локальной таблице дескрипторов.

2.5. Работа с локальной таблицей дескрипторов

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

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

UINT WINAPI AllocSelector(UINT uSelector);

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

Если созданный дескриптор больше не нужен, его следует удалить при помощи функции FreeSelector :

UINT WINAPI FreeSelector(UINT uSelector);

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

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

Для установки базового адреса следует воспользоваться функцией SetSelectorBase , которая впервые появилась в составе программного интерфейса Windows версии 3.1:

UINT WINAPI SetSelectorBase(UINT uSelector, DWORD dwBase);

Для дескриптора, соответствующего селектору uSelector, устанавливается значение линейного адреса, равное dwBase.

Если функция выполнилась успешно, она возвращает значение параметра uSelector. В случае ошибки возвращается нулевое значение.

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

DWORD WINAPI GetSelectorBase(UINT uSelector);

При помощи функции SetSelectorLimit вы можете определить предел сегмента, адресуемого селектором uSelector:

UINT WINAPI SetSelectorLimit(UINT uSelector, DWORD dwLimit);

Предел сегмента задается параметром dwLimit. Для процессора 80286 предел не должен превосходить величины 0x10000. О пределах сегментов вы можете прочитать в шестом томе "Библиотеки системного программиста", который называется "Защищенный режим процессоров Intel 80286/80386/80486".

Функция GetSelectorLimit позволяет вам для заданного селектора uSelector определить предел сегмента:

DWORD WINAPI GetSelectorLimit(UINT uSelector);

С помощью функции LockSegment вы можете зафиксировать сегмент, соответствующий селектору uSelector:

HGLOBAL WINAPI LockSegment(UINT uSelector);

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

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

После использования зафиксированный сегмент необходимо расфиксировать при помощи функции UnlockSegment :

void  WINAPI UnlockSegment(UINT uSelector);

В файле windows.h определены макрокоманды LockData и UnlockData , предназначенные, соответственно, для фиксирования и расфиксирования текущего сегмента данных:

#define LockData(dummy)   LockSegment((UINT)-1)
#define UnlockData(dummy) UnlockSegment((UINT)-1)

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

UINT WINAPI AllocDStoCSAlias(UINT uSelector);

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

Функция PrestoChangoSelector создает селектор кодового сегмента, соответствующий селектору сегменту данных или наоборот, селектор сегмента данных, соответствующий сегменту кода:

UINT WINAPI PrestoChangoSelector(
   UINT sourceSel, UINT destSel);

Параметр sourceSel задает исходный селектор, который будет преобразован.

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

2.6. Работа с большими массивами данных

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

Рис. 2.11. Адресация большого блока памяти

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

Для работы с блоками памяти размером более 64 Кбайт приложения, составленные на языках программирования С и С++ должны использовать указатели типа huge . Для таких указателей переключение селекторов, необходимое для получения доступа ко всему блоку памяти, выполняется автоматически.

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

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

2.7. Утилита HEAPWALK

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

Главное окно утилиты HEAPWALK представлено на рис. 2.12.

Рис. 2.12. Главное окно утилиты HEAPWALK

В главном окне находится список, каждая строка которого соответствует одному блоку памяти.

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

Название Описание
ADDRESS Адрес блока памяти
HANDLE Идентификатор блока памяти
SIZE Размер блока памяти в байтах
LOCK Содержимое счетчика фиксирования блока памяти. Если для блока памяти запрещен страничный обмен, в этом поле есть буква "P". Если блок памяти зафиксирован и не может быть удален, он обозначается буквой "L"
FLG Если блок памяти удаляемый (discardable), в этом поле находится буква "D", если фиксированный - буква "F"
HEAP Если объект имеет локальную область памяти, в этом столбце находится буква "Y"
OWNER Имя модуля (или приложения), владеющего блоком памяти
TYPE Тип объекта (сегмент кода, сегмент данных, ресурс и т. д.)

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

Если в меню "Walk" выбрать строку "Walk Heap", в главном окне утилиты HEAPWALK будет отображаться информация о всех объектах, расположенных в глобальной области памяти.

Для того чтобы просмотреть все удаляемые объекты, выберите из этого меню строку "Walk LRU List". Те объекты, которые давно не использовались, будут расположены в верхней части списка.

Если выбрать из меню "Walk" строку "GC(0) and Walk", утилита выполнит дефрагментацию глобальной области памяти, запросит блок памяти размером 0 байт и отобразит список объектов.

С помощью строки "GC(-1) and Walk" вы можете предпринять попытку удалить все удаляемые сегменты и просмотреть список объектов.

Остальные строки меню "Walk" описаны в руководстве по утилитам, входящим в состав SDK.

Для удобства отображения вы можете отсортировать блоки памяти в списке при помощи меню "Sort". С помощью этого меню можно выполнить сортировку по адресам блоков памяти (строка "Address" меню "Sort"), по именам модулей, которым принадлежат блоки памяти (строка "Module"), по размеру блоков памяти (строка "Size"), типу объектов (строка "Type"). С помощью строки "Refresh Seg Names" вы можете просмотреть имена сегментов, загруженных в память после запуска утилиты HEAPWALK.

Вы можете выбрать любую строку в списке объектов и просмотреть соответствующий ему блок памяти с помощью меню "Object".

Строка "Show" меню "Object" позволяет просмотреть содержимое блока памяти в виде шестнадцатеричного дампа памяти или ресурса (пиктограммы, изображения bitmap, меню, диалоговой панели и т. д.). На рис. 2.13 в окне "Resource Bitmap" можно увидеть изображение пиктограммы, соответствующей выбранному блоку памяти.

Рис. 2.13. Просмотр пиктограммы

С помощью строки "Discard" можно удалить выбранный объект из памяти.

Объект может быть отмечен как ближайший кандидат на удаление (строка "Oldest") или как объект, который должен быть удален в последнюю очередь (строка "Newest").

Строка "LocalWalk" позволяет вам просмотреть локальную область памяти для выбранного объекта (если у этого объекта есть локальная область памяти). Эта область памяти отображается в отдельном окне (рис. 2.14).

Рис. 2.14. Просмотр локальной области памяти

С помощью меню "Alloc" вы можете заказывать всю свободную память (строка "Allocate All of Memory") и освобождать блоки памяти различного размера (строки "Free All", "Free 1K", ..., "Free 50K", "Free XK").

2.8. Модели памяти

Для приложений Windows вы можете выбрать одну из четырех моделей памяти: small, medium, compact или large.

Все приложения, рассмотренные нами ранее, были подготовлены в модели памяти small. Для этой модели при загрузке приложения в память создается два сегмента - сегмент кода и автоматический сегмент данных. Перед тем как передать управление приложению, Windows записывает адрес сегмента кода в регистр CS, адрес автоматического сегмента данных - в регистры DS и SS. Таким образом, в этой модели памяти для стека и автоматического сегмента данных используется один и тот же сегмент.

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

CODE preload moveable discardable
DATA preload moveable multiple

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

Для сложных приложений Windows удобно использовать модель памяти medium, в которой создается один сегмент данных и несколько сегментов кода. Однако вызов дальней функции (а в модели памяти medium все функции вызываются как дальние) выполняется дольше, чем в MS-DOS. Это связано с наличием в Windows механизма перемещения сегментов.

Чаще, однако, работают со смешанными моделями памяти, используя при описании функций и данных ключевые слова FAR и NEAR. Заметим, что даже если ваше приложение было подготовлено в модели памяти small, оно на самом деле пользуется смешанной моделью памяти, так как все функции программного интерфейса Windows определены как дальние.

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

Если ваше приложение создано в модели памяти medium, имеет смысл сгруппировать различные функции в несколько сегментов и для каждого сегмента определить свои атрибуты. Например, функции инициализации приложения следует расположить в сегменте с атрибутами PRELOAD и DISCARDABLE. В этом случае эти функции будут загружены в памяти в процессе запуска приложения и впоследствии будут удалены. Функции, обрабатывающие сообщения, должны быть загружены в память при инициализации приложения и находиться там постоянно, поэтому для них подойдет атрибут PRELOAD. Те функции, которые требуются эпизодически, можно загружать при необходимости и удалять после использования, поэтому для них следует указать атрибуты LOADONCALL и DISCARDABLE.

Для назначения атрибутов сегментам приложения файл определения модуля должен содержать оператор SEGMENTS:

CODE preload moveable discardable
DATA preload moveable multiple
SEGMENTS
  CODESEG1 moveable discardable
  CODESEG2 preload
  CODESEG3 loadoncall discardable

Для изменения имени сегмента кода в системах разработки Borland C++ версии 3.1 и Borland C++ for Windows версии 4.01 можно использовать параметр командной строки -zCname, где name - новое имя сегмента кода. По умолчанию сегмент кода имеет имя _TEXT. Для того чтобы восстановить имя сегмента кода, можно использовать параметр -zC*.

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

#pragma options -zCCODESEG1
// Тело функции
...............
#pragma options -zC*

Если вы создаете приложения с помощью Microsoft C++ версии 7.0 или Microsoft Visual C++, исходные тексты всех функций, которые должны находиться в одном сегменте, следует расположить в одном файле. Для изменения имени сегмента следует воспользоваться параметром /NT:

cl /u /c /As /Gsw /Oas /Zpe /NT CODESEG2 wndproc.c