Автор работы: Пользователь скрыл имя, 26 Октября 2010 в 22:57, Не определен
Реферат
void*
p = HeapAlloc(s hHeap, 0, size);
if (p != NULL)
{
// память
выделена успешно; увеличиваем
счетчик объектов CSomeClass в куче
s_uNumAllocsInHeap++;
}
// возвращаем адрес созданного объекта CSomeClass
return(p);
}
Заметьте, что
сначала я объявил два статических
элемента данных, s_hHeap и s_uNumAllocsInHeap, а затем
инициализировал их значениями NULL и 0 соответственно.
Оператор new принимает
один параметр — size, указывающий число
байтов, нуж ных для хранения CSomeClass
Первым делом он создает кучу, если
таковой нет Для проверки анализируется
значение переменной s_bHeap: если оно NULL,
кучи нет, и тогда она создается функцией
HeapCreate, а описатель, возвращаемый функцией,
со храняется в переменной s_bHeap, чтобы
при следующем вызове оператора new ис пользовать
существующую кучу, а не создавать еще
одну.
Вызывая HeapCreate,
я указал флаг HEAP_NO_SERIALIZE, потому что
данная про грамма построена как
однопоточная. Остальные параметры,
указанные при вызове HeapCreate, определяют
начальный и максимальный размер
кучи. Я подставил на их место по нулю.
Первый нуль означает, что у кучи нет начального
размера, второй — что куча должна расширяться
по мере необходимости.
Hе исключено,
что Вам показалось, будто параметр
size оператора new стоит пе редать
в HeapCreatc как второй параметр. Вроде бы
тогда можно инициализировать кучу так,
чтобы она была достаточно большой для
размещения одного экземпляра класса.
И в таком случае функция HeapAlloc при первом
вызове работала бы быстрее, так как не
пришлось бы изменять размер кучи под
экземпляр класса. Увы, мир устро ен не
так, как хотелось бы. Из-за того, что с
каждым выделенным внутри кучи блоком
памяти связан свой заголовок, при вызове
HeapAlloc все равно пришлось бы менять размер
кучи, чтобы в нее поместился не только
экземпляр класса, но и связанный с ним
загловок.
После создания
кучи из нее можно выделять память
под новые объекты CSomeClass с помощью
функции HeapAlloc. Первый параметр — описатель
кучи, второй — раз мер объекта
CSomeClass. Функция возвращает адрес выделенного
блока.
Если выделение
прошло успешно, я увеличиваю переменную-счетчик
s_uNum AllocsInHeap, чтобы знать число
выделенных блоков в куче. Наконец,
оператор new возвращает адрес только
что созданного объекта CSomeClass.
Вот так происходит
создание нового объекта CSomeClasb. Теперь
рассмотрим, как этот объект разрушается,
— если он больше не нужен программе. Эта
задача возлага ется на функцию, переопределяющую
оператор delete.
void CSomeClass::operator delete (void* p)
{
if (HeapFrce(s_hHcap, 0, p))
{
// объект удален успешно
s_uNumAllocsInKeap--;
}
if (s_uNumAllocsInHeap == 0)
{
//
если в куче больше нет
if (HeapDestroy(s_hHeap))
{
// описатель кучи приравниваем NULL, чтобы оператор new
// мог создать новую кучу при создании нового объекта
CSomeClass
s_hHeap = NULL;
}
}
}
Оператор delete принимает
только один параметр: адрес удаляемого
объекта. Сна чала он вызывает HeapFree
и передает ей описатель кучи и
адрес высвобождаемого объекта.
Если объект освобожден успешно, s_uNumAllocslnHeap
уменьшается, показы вая, что одним объектом
CSomeClass в куче стало меньше. Далее оператор
проверяет: не равна ли эта переменная
0, и, если да, вызывает HeapDestroy, передавая
ей описа тель кучи. Если куча уничтожена,
s_hHeap присваивается NULL. Это важно: ведь
в бу дущем наша программа может попытаться
создать другой объект CSomeClass. При этом
будет вызван оператор new, который проверит
значение s_hHeap, чтобы опре делить, нужно
ли использовать существующую кучу или
создать новую.
Данный пример
иллюстрируеn очень удобную схему
работы с несколькими куча ми. Этот
код легко подстроить и включить
в Ваши классы. Но сначала, может
быть, стоит поразмыслить над проблемой
наследования. Если при создании нового
класса Вы используете класс CSomeClass как
базовый, то производный класс унаследует
опе раторы new и delete, принадлежащие классу
CSomeClass. Новый класс унаследует и его кучу,
а это значит, что применение оператора
new к производному классу повлечет выделение
памяти для объекта этого класса из той
же кучи, которую использует и класс CSomeClass.
Хорошо это или нет, зависит от конкретной
ситуации. Если объек ты сильно различаются
размерами, это может привести к фрагментации
кучи, что зятруднит выявление таких ошибок
в коде, о которых я рассказывал в разделах
«За щита компонентов» и «Более эффективное
управление памятью».
Если Вы хотите
использовать отдельную кучу для
производных классов, нужно продублировать
все, что я сделал для класса CSomeClass.
Л конкретнее - включить еще один набор
переменных s_hHeap и s_uNumAllocsInHeap и повторить
еще раз код для операторов new и delete. Компилятор
увидит, что Вы переопределили в производном
классе операторы new и delete, и сформирует
обращение именно к ним, а не к тем, которые
содержатся в базовом классе.
Если Вы не будете
создавать отдельные кучи для
каждого класса, то получите един ственное
преимущество: Вам не придется выделять
память под каждую кучу и соот ветствующие
заголовки. Но кучи и заголовки не
занимают значительных объемов
памяти, так что даже это преимущество весьма сомнительно Неплохо, конечно, если каждый класс, используя свою кучу, в то же время имеет доступ к куче базового клас са. Но делать так стоит лишь после полной отладки приложения. И, кстати, проблему фрагментации куч это не снимает.
Другие функции
управления кучами
Кроме уже упомянутых,
в Windows есть еще несколько функций,
предназначенных для управления
кучами. В этом рязделс я вкратце
расскажу Вам о них.
ToolHelp-функции
(упомянутые в конце главы 4) дают возможность
перечислять кучи процесса, а также выделенные
внутри них блоки памяти. За более подробной
информацией я отсылаю Вас к документации
Plarform SDK: ищите разделы по функ циям Heap32Ftrst,
Heap32Next, Heap32ListFirst и Heap32ListNext. Самое ценное
в этих функциях то, что они доступны как
в Windows 98, так и в Windows 2000. Прочие функ ции,
о которых пойдет речь в этом разделе,
есть только в Windows 2000.
В адресном пространстве
процесса может быть несколько куч,
и функция GetPro cessHeaps позволяет получить
их описатели "одним махом":
DWORD
GetProcessHeaps( DWORD dwNumHeaps, PHANDLE pHeaps);
Предварительно
Вы должны создать массив описателей,
а затем вызвать функцию так,
как показано ниже.
HANDLE
hHeaps[25];
DWORD
dwHeaps = GetProcessHeaps(25, hHeaps);
if (dwHeaps > 25)
{
//
у процесса больше куч, чем
мы ожидали
}
else
{
// элеметы от hHeaps[0] до hHeaps[dwHeaps - 1]
//
идентифицируют существующие
}
Имейте в виду,
что описатель стандартной кучи
процесса тоже включается в этот массив
описателей, возвращаемый функцией GetProcessHeaps.
Целостность кучи позволяет проверить
функция HeapValidate:
BOOL
HeapValidate( HANDLE hHeap, DWORD fdwFlags, LPCVOID pvMem);
Обычно ее вызывают,
передавая в hHeap описатель кучи, в dwFlags
— 0 (этот па раметр допускает еще флаг
HEAP_NO_SERIALIZE), а в pvMem — NULL. Функция про сматривает
все блоки в куче, чтобы убедиться в отсутствии
поврежденных блоков. Чтобы она работала
быстрее, в параметре pvMem можно передать
адрес конкретного блока, Тогда функция
проверит только этот блок.
Для объединения
свободных блоков в куче, а также
для возврата системе любых страниц
памяти, на которых нет выделенных
блоков, предназначена функция Heap Compact:
UINT
HeapCompact( HANDLE hHeap, DWORD fdwFlags);
Обычно в параметре
fdwFlags передают 0, но можно передать и
HEAP_NO_SE RIALIZE
Следующие две
функции — HeapLock и HeapUnlock — используются
парно
BOOL Hpaplock(HANDLE hHeap);
BOOL
HeapUnlock(HANDLE hHeap);
Они предназначены
для синхронизации потоков После успешного
вызова Heap Lock поток, который вьзывал эту
функцию становится владельцем указанной
кучи Ьсли другой поток обращается к этой
куче, указывая тот же описатель кучи,
система приостанавливает его выполнение
до тех пор, пока куча не будет разблокирована
вызовом HeapUnlock
Функции HeapAlloc, HeapSize,
HeapFree и другие — все обращаются
к HeapLock и HeapUnlock, чтобы обеспечить последовательный
доступ к куче Самостоятельно вы зывать
эти функции Вам вряд ли понадобится
Последняя функция,
предназначенная для работы с кучами,
— HeapWalk
BOOL
HeapWalk( HANDLE hHeap, PPROCESS_HEAP_ENTRY pHoapEntry);
Она предназначена
только для отладки и позволяет
просматривать содержимое кучи Обычно
ее вызывают по несколько paз, передавая
адрес структуры PROCESS_ HEAP_ENTRY (Вы должны
сами создать ее экземпляр и инициализировать)
typedef struct _PROCESS_HEAP_ENTRY
{
PVOID lpData;
DWORD cbData;
BYlE cbOverhead;
BYTE iRegionIndex;
WORD
wFlags;
union
{
struct
{
HANDLE
hMem; DWORD dwReserved[ 3 ];
}
Block;
struct
{
DWORD dwCornmittedSize;
DWORD dwUnCommittedSize;
LPVOID lpFirstBlock;
LPVOID
lpLastBlock;
}
Region;
};
} PROCESS_HEAP_ENTRY, *LPPROCESS_HEAP_ENTRY,
*PPROCESS_HEAP_ENTRY;
Прежде чем
перечислять блоки в куче, присвойте
NULL элементу lpData, и это заставит функцию
HeapWalk инициализировать все элементы структуры
Чтобы пе рейти к следующему блоку, вызовите
HeapWalk еще раз, переддв сй тот же описатель
кучи и адрес той же структуры PROCESS_HFAPENTRY
Если HeapWalk вернет FALSE, значит, блоков в
куче больше нет Подробное описание элементов
структуры PRO CESS_HEAP_ENTRY см. в документации
PlatformSDK
Обычно вызовы функции HeapWalk "обрамляют" вызовами HeapLock и HeapUnlock, чтобы посторонние потоки не портили картину, создавая или удаляя блоки в просматриваемой куче