Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Программирование для Windows NT

© Александр Фролов, Григорий Фролов
Том 26, часть 1, М.: Диалог-МИФИ, 1996, 272 стр.

[Назад] [Содеожание] [Дальше]

Работа с пулами памяти

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

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

Пулы памяти в Microsoft Windows NT

Как мы уже говорили, приложения Microsoft Windows версии 3.1 могли заказывать память из двух областей или двух пулов - из глобального пула, доступного всем приложениям, и локального, создаваемого для каждого приложения.

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

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

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

Во-первых, параметры стандартного пула можно задать в параметре /HEAP редактора связи:


/HEAP: 0x2000000, 0x10000

В данном случае для стандартного пула будет зарезервировано 2 Мбайта памяти, причем сразу после загрузки приложения 10 Кбайт памяти будет получено в пользование.

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


HEAPSIZE 0x2000000 0x10000

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

Функции для работы с пулами памяти

Итак, в распоряжении приложения Microsoft Windows NT имеется один стандартный пул и произвольное количество динамических пулов памяти.

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

Получение идентификатора стандартного пула

Идентификатор стандартного пула получить очень просто. Этот идентификатор возвращает функция GetProcessHeap XE "GetProcessHeap" , не имеющая параметров:


HANDLE GetProcessHeap XE GetProcessHeap (VOID);

Создание динамического пула

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


HANDLE HeapCreate(
  DWORD  flOptions,     // флаг создания пула 
  DWORD  dwInitialSize, // первоначальный размер пула в байтах 
  DWORD  dwMaximumSize);// максимальный размер пула в байтах

Параметры dwMaximumSize и dwInitialSize определяют, соответственно, размер зарезервированной для пула памяти и размер памяти, полученной для использования.

Через параметр flOptions вы можете передать нулевое значение, а также значения HEAP_NO_SERIALIZE XE "HEAP_NO_SERIALIZE" и HEAP_GENERATE_EXCEPTIONS XE "HEAP_GENERATE_EXCEPTIONS" .

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

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

В случае удачи функция HeapCreate XE "HeapCreate" возвращает идентификатор созданного динамического пула памяти. При ошибке возвращается значение NULL (либо возникает исключение, если указан флаг HEAP_GENERATE_EXCEPTIONS).

Удаление динамического пула

Для удаления динамического пула памяти, созданного функцией HeapCreate XE "HeapCreate" , вы должны использовать функцию HeapDestroy XE "HeapDestroy" :


BOOL HeapDestroy XE HeapDestroy (HANDLE hHeap);

Через единственный параметр этой функции передается идентификатор удаляемого динамического пула. Заметим, что вам не следует удалять стандартный пул, передавая этой функции значение, полученное от функции GetProcessHeap XE "GetProcessHeap" .

Функция HeapDestroy XE "HeapDestroy" выполняет безусловное удаление пула памяти, даже если из него были получены блоки памяти и на момент удаления пула они не были возвращены системе.

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

Для получения памяти из стандартного или динамического пула приложение должно воспользоваться функцией HeapAlloc XE "HeapAlloc" , прототип которой мы привели ниже:


LPVOID HeapAlloc(
  HANDLE hHeap,    // идентификатор пула 
  DWORD  dwFlags,  // управляющие флаги 
  DWORD  dwBytes); // объем получаемой памяти в байтах

Что касается параметра hHeap, то для него вы можете использовать либо идентификатор страндартного пула памяти, полученного от функции GetProcessHeap XE "GetProcessHeap" , либо идентификатор динамического пула, созданного приложением при помощи функции HeapCreate XE "HeapCreate" .

Параметр dwBytes определяет нужный приложению объем памяти в байтах.

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

 Значение

 Описание

 HEAP_GENERATE_EXCEPTIONS XE "HEAP_GENERATE_EXCEPTIONS"

 Если при выполнении функции произойдет ошибка, возникнет исключение

 HEAP_NO_SERIALIZE XE "HEAP_NO_SERIALIZE"

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

 HEAP_ZERO_MEMORY XE "HEAP_ZERO_MEMORY"

 Выделенная память заполняется нулями

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

С помощью функции HeapReAlloc XE "HeapReAlloc" приложение может изменить размер блока памяти, выделенного ранее функцией HeapAlloc XE "HeapAlloc" , уменьшив или увеличив его. Прототип функции HeapReAlloc приведен ниже:


LPVOID HeapReAlloc(
  HANDLE hHeap,    // идентификатор пула 
  DWORD  dwFlags,  // флаг изменения размера блока памяти 
  LPVOID lpMem,    // адрес блока памяти 
  DWORD  dwBytes); // новый размер блока памяти в байтах

Для пула hHeap эта функция изменяет размер блока памяти, расположенного по адресу lpMem. Новый размер составит dwBytes байт.

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

Через параметр dwFlags вы можете передавать те же параметры, что и через аналогичный параметр для функции HeapAlloc XE "HeapAlloc" . Дополнительно можно указать параметр HEAP_REALLOC_IN_PLACE_ONLY XE "HEAP_REALLOC_IN_PLACE_ONLY" , определяющий, что при изменении размера блока памяти его нужно оставить на прежнем месте адресного пространства. Очевидно, что если указан этот параметр, в случае успешного завершения функция HeapReAlloc XE "HeapReAlloc" вернет то же значение, что было передано ей через параметр lpMem.

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

Зная адрес блока памяти, полученного из пула, вы можете определить его размер при помощи функции HeapSize XE "HeapSize" :


DWORD HeapSize(
  HANDLE  hHeap,   // идентификатор пула 
  DWORD   dwFlags, // управляющие флаги 
  LPCVOID lpMem);  // адрес проверяемого блока памяти

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

Если блоком памяти пользуется только одна задача процесса, вы можете передать через параметр dwFlags значение HEAP_NO_SERIALIZE XE "HEAP_NO_SERIALIZE" .

Освобождение памяти

Память, выделенную с помощью функции HeapAlloc XE "HeapAlloc" , следует освободить, как только в ней отпадет надобность. Это нужно сделать при помощи функции HeapFree XE "HeapFree" :


BOOL HeapFree(
  HANDLE hHeap,   // идентификатор пула 
  DWORD  dwFlags, // флаги освобождения памяти 
  LPVOID lpMem);  // адрес освобождаемого блока памяти

Если блоком памяти пользуется только одна задача процесса, вы можете передать через параметр dwFlags значение HEAP_NO_SERIALIZE XE "HEAP_NO_SERIALIZE" .

Если размер блока памяти, выделенного функцией HeapAlloc XE "HeapAlloc" , был изменен функцией HeapReAlloc XE "HeapReAlloc" , для освобождения такого блока памяти вы все равно должны использовать функцию HeapFree XE "HeapFree" .

Использование функций malloc XE "malloc" и free XE "free"

В библиотеке Microsoft Visual C++ имеются стандартные функции, предназначенные для динамического получения и освобождения памяти, такие как malloc XE "malloc" и free XE "free" .

У нас есть хорошая новость для вас - в среде Microsoft Windows NT вы можете использовать эти функции с той же эффективностью, что и функции, предназначенные для работы с пулами - HeapAlloc XE "HeapAlloc" , HeapFree XE "HeapFree" и так далее. Правда, эти функции получают память только из стандартного пула.

Одно из преимуществ функций malloc XE "malloc" и free XE "free" заключается в возможности их использования на других платформах, отличных от Microsoft Windows NT.

Старые функции управления памятью

В 32-разрядных приложениях Microsoft Windows NT вы можете пользоваться многими функциями управления памятью операционной системы Microsoft Windows версии 3.1, которые оставлены в новой операционной системе для совместимости. Мы подробно рассмотрели эти функции в главе “Управление памятью” 13 тома “Библиотеки системного программиста”.

Напомним, что в 16-разрядном программном интерфейсе Microsoft Windows версии 3.1 существует два набора функций (глобальные и локальные), предназначенных для работы с глобальным и локальным пулом памяти. Это такие функции, как GlobalAlloc XE "GlobalAlloc" , LocalAlloc XE "LocalAlloc" , GlobalFree XE "GlobalFree" , LocalFree XE "LocalFree" и так далее. В 32-разрядных приложениях Microsoft Windows NT вы можете пользоваться как глобальными, так и локальными функциями, причем результат будет совершенно одинаковый. Причина этого заключается в том, что все эти функции пользуются функциями программного интерфейса Microsoft Windows NT, предназначенными для работы со стандартным пулом памяти: HeapAlloc XE "HeapAlloc" , HeapReAlloc XE "HeapReAlloc" , HeapFree XE "HeapFree" и так далее.

Вот список функций старого программного интерфейса, доступных приложениям Microsoft Windows NT:

 Имя функции

 Описание

 GlobalAlloc XE "GlobalAlloc" , LocalAlloc XE "LocalAlloc"

 Получение глобального (локального для функции LocalAlloc XE "LocalAlloc" ) блока памяти

 GlobalReAlloc XE "GlobalReAlloc" , LocalReAlloc XE "LocalReAlloc"

 Изменение размера глобального (локального) блока памяти

 GlobalFree XE "GlobalFree" , LocalFree XE "LocalFree"

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

 GlobalLock XE "GlobalLock" , LocalLock XE "LocalLock"

 Фиксирование глобального (локального) блока памяти

 GlobalUnlock XE "GlobalUnlock" , LocalUnlock

 Расфиксирование глобального (локального) блока памяти

 GlobalSize XE "GlobalSize" , LocalSize XE "LocalSize"

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

 GlobalDiscard XE "GlobalDiscard" , LocalDiscard XE "LocalDiscard"

 Принудительное удаление глобального (локального) блока памяти

 GlobalFlags XE "GlobalFlags" , LocalFlags XE "LocalFlags"

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

 GlobalHandle XE "GlobalHandle" , LocalHandle XE "LocalHandle"

 Определение идентификатора глобального (локального) блока памяти

Заметим, что хотя при получении памяти с помощью функции GlobalAlloc XE "GlobalAlloc" вы по-прежнему можете указывать флаг GMEM_DDESHARE XE "GMEM_DDESHARE" , другие приложения, запущенные в среде Microsoft Windows NT, не будут иметь к этой памяти доступ. Причина очевидна - адресные пространства приложений изолированы. Однако в документации SDK сказано, что этот флаг можно использовать для увеличения производительности приложений, использующих механизм динамической передачи сообщений DDE XE "механизм динамической передачи сообщений DDE" . Этот механизм мы подробно описали в главе “Обмен данными через DDE XE "DDE" ” в 17 томе “Библиотеки системного программиста”, который называется “Операционная система Microsoft Windows 3.1. Дополнительные главы”.

Обратим ваше внимание также на то, что в среде Microsoft Windows версии 3.1 вы могли получать фиксированную (fixed), перемещаемую (moveable) и удаляемую (discardable) память.

В среде Microsoft Windows NT вы по-прежнему можете пользоваться различными типами памяти, если для получения блоков памяти используете функции GlobalAlloc XE "GlobalAlloc" или LocalAlloc XE "LocalAlloc" . Однако теперь вам едва ли потребуется перемещаемая память, так как новая система управления памятью выполняет операцию перемещения с помощью механизма страничной адресации, не изменяя значение логического адреса.

В том случае, если вы все же решили получить блок перемещаемой памяти, перед использованием его необходимо зафиксировать функцией GlobalLock XE "GlobalLock" или LocalLock XE "LocalLock" (соответственно, для блоков памяти, полученных функциями GlobalAlloc XE "GlobalAlloc" и LocalAlloc XE "LocalAlloc" ). Это нужно сделать потому что если вы заказываете перемещаемый блок памяти, функции GlobalAlloc и LocalAlloc возвращают не адрес блока памяти, а его идентификатор.

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

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

[Назад] [Содеожание] [Дальше]