Куча Windows и семейство функций GlobalAlloc

Куча (heap) — это пул памяти какого-либо процесса. Когда программе требуется блок памяти, онавызывает функцию, выделяющую память из кучи, а чтобы освободить ранее выделенную память, — функцию, парную первой. Выравнивание по границам, кратным 4 Кб, в этом случае не производится; диспетчер кучи использует пространство на выделенных ранее страницах или обращается к VirtualAlloc, чтобы получить дополнительные страницы. Одна из двух куч, с которыми программа работает, — куча Windows. Для выделения из нее памяти служит функция HeapAlloc, а для освобождения — функция HeapFree. HeapAlloc особенно удобна для выделения "крупных" блоков памяти.

Может быть, приложение и не будет явно вызывать НеарАlloc, но за него это сделает функция GlobalAlloc, унаследованная от Winl6. В идеальном мире 32-разрядных программ функция GlobalAlloc не понадобилась бы, но мы имеем дело с реальным миром. Все еще остается колоссальный объем кода, перенесенного из Winl6, в том числе OLE, в котором вместо 32-разрядных адресов применяются параметры типа "описатель памяти" (HGLOBAL).

Работа функции GlobalAlloc зависит от передаваемых ей атрибутов. Если ей указывается GMEM_FIXED, она просто вызывает НеарАlloс и возвращает адрес, приводя тип данных к 32-битному значению HGLOBAL. Если же ей передается GMEM_MOVEABLE, возвращаемое значение HGLOBAL является указателем на элемент "таблицы описателей" в данном процессе. В этом элементе содержится указатель на память, выделенную функцией HeapAlloc.

Зачем нужна "перемещаемая" (moveable) память, если она вводит еще один слой "непрямого управления"? Здесь мы встречаемся с наследием Winl6, в котором операционная система когда-то действительно перемещала блоки памяти. В Win32 перемещаемые блоки памяти существуют лишь для поддержки GlobalReAlloc, которая выделяет новый блок памяти, копирует в него содержимое старого блока, освобождает последний и помещает адрес нового блока в существующий элемент таблицы описателей.

К сожалению, многие библиотечные функции используют в качестве параметров и возвращаемых значений HGLOBAL, а не адреса памяти. Если такая функция возвращает HGLOBAL, приложение должно считать, что данная память выделена с атрибутом GMEM_MOVEABLE, и, следовательно, чтобы получить адрес памяти, надо вызвать функцию GlobalLock. (В случае фиксированной памяти GlobalLock просто возвращает переданный ей описатель как адрес.) Если от приложения требуется передать параметр HGLOBAL, то следует получить это значение с помощью GlobalAlloc(GMEM_MOVEABLE,...) — на тот случай, если вызываемая функция обращается к GlobalReAlloc и ожидает, что значение описателя не изменится.

Буфер обмена Windows использует фиксированную память, тем самым можно передавать адреса, возвращенные HeapAlloc, и приводить возвращаемые значения HGLOBAL к void-указателям. Можно также передавать фиксированный адрес OLE-функции, которая принимает параметр типа HGLOBAL, однако значения HGLOBAL, возвращаемые OLE-функциями, являются перемещаемыми, поэтому приложение должны вызывать GlobalLock.

Куча библиотеки С периода выполнения, _heapmin и С++-операторы new и delete

Однако, куча Windows (и функция HeapAlloc) – это не та куча, с которой приложение будете работать чаще всего. Существует еще одна куча — ею управляет библиотека С периода выполнения (С RunTime library, CRT). Доступ к CRT-куче реализуется функциями malloc и free, напрямую вызываемыми операторами C++ new и delete. Эта куча оптимизирована для выделения блоков малого размера.

Конечно, функция malloc вызывает VirtualAlloc, но делает это очень хитро. При первом вызове она резервирует регион размером 1 Мб (в будущих версиях Visual C++ эти значения могут измениться) и передает блок памяти, размер которого кратен 64 Кб (если malloc вызван чтобы выделить память размером 64 Кб или менее, выделяется один 64-килобайтный блок. При последующих вызовах память выделяется по возможности из этого блока; в ином случае диспетчер кучи вызывает VirtualAlloc, чтобы передать дополнительную память. После того, как весь регион размером 1 Мб израсходован, malloc резервирует еще один регион размер 2 Мб, потом другой, но уже размером 4 Мб и т.д., передавая память по мере необходимости.

При вызове функции free диспетчер кучи помещает дескрипторы блоков памяти в односвязный циклический список свободных блоков памяти (free list), находящийся вне CRT кучи. Функция malloc использует этот список для последующего выделения памяти (если это возможно). Так как данный список весьма компактен, поиск свободных страниц ocyществляется быстро, без просмотра большого числа страниц виртуальной памяти.

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