Иван Синтюрин
spinoza@mail.ru
В очередной раз подняли вопрос о распределении памяти под win32... У меня по этому поводу кое-что накипело, это можно выразить следующей фразой: "С операционной системой следует говорить на ее родном языке!". Конечно, не надо возводить это правило в абсолют, но на самом деле в некоторых случаях лучше не пользоваться языковыми средствами распределения памяти (new, delete, malloc, free, GetMem, FreeMem и т.д.), т.к. их эффективность зависит от конкретного компилятора и может быть неудовлетворительной, а использовать функции из win32 api, тогда глюков будет меньше (если не считать ваши собственные ;), а скорость работы увеличится. Кроме этого вы можете не просто задействовать сервис из win32 api, но и написать свой собственный менеджер памяти, который в вашем конкретном случае будет обеспечивать максимальную эффективность (последнее не описывается в данной статье, читайте книжки ;-).
В win32 используется три механизма управления памятью:
Если вы программировали для 16-разрядной Windows, Вам знакомы два типа куч: локальные и глобальные. У каждого процесса и DLL-модуля в ней собственная локальная куча, а единственная глобальная - доступна всем процессам. В win32 есть принципиальные отличия в схеме управления динамически распределяемой памятью:
Куча (анлг. heap) - регион зарегистрированного адресного пространства. Первоначально большей его части физическая память не передается. По мере того, как программа занимает эту область под данные, специальный диспетчер, управляющий кучами (heap manager), передает ей физическую память постранично. А при освобождении страниц в куче диспетчер возвращает физическую память системе.
Кучи бывают двух видов:
Для удобства при написании программ (или при написании
портируемых программ) можно переопределить стандартные операторы в C/C++
(btw, размер плагинов также уменьшается, это очевидно...). Например, можно
использовать следующее:
typedef unsigned size_t;
HANDLE heapNew_SR; /*эту кучу я создаю в самом начале работы программы*/
void *malloc(size_t size)
{
return HeapAlloc(heapNew_SR,HEAP_ZERO_MEMORY,size);
}
void *realloc(void *block, size_t size)
{
void *p=NULL;
if(heapNew_SR!=NULL)
{
if(!size) HeapFree(heapNew_SR,0,block);
else
{
if(block==NULL) p=HeapAlloc(heapNew_SR,HEAP_ZERO_MEMORY,size);
else p=HeapReAlloc(heapNew_SR,HEAP_ZERO_MEMORY,block,size);
}
}
return p;
}
void free(void *block)
{
if(block!=NULL && heapNew_SR) HeapFree(heapNew_SR,0,block);
}
void *operator new(size_t size)
{
size=size?size:1;
return malloc(size);
}
void *operator new[](size_t size) {return ::operator new(size);}
void *operator new(size_t size, void *p) {return p;}
void operator delete(void *p)
{
free(p);
}
void operator delete[](void *ptr) {::operator delete(ptr);}
Многие скажут, а как же GlobalAlloc , GlobalFree , GlobalLock и прочие? Ответ прост - это старье и его лучше не использовать в своих программах (можно, если вы пишете под 16-разрядные винды)! Windows поддерживает эти функции только для совместимости со старыми программами. Они работают хуже и медленнее win32-функций, управляющих кучами, и требуют для своей работы дополнительной памяти.
Приведу пример для тех, кому не достаточно просто утверждения. Для поддержки 16-разрядных функций, управляющих глобальными и локальными кучами, каждому процессу при инициализации предоставляется своя таблица описателей и своя стандартная куча, в которой выделяются участки для глобальной и локальной памяти. Эта таблица описателей нужна win32 для управления выделением глобальной и локальной памяти, она является массивом структур, каждый элемент массива указывает на блок памяти, выделенной из стандартной кучи процесса. При вызове GlobalAlloc система выделяет блок из этой кучи и отыскивает свободный элемент в таблице описателей, принадлежащей процессу. Затем сохраняет адрес выделенного блока в таблице описателей и возвращает адрес элемента этой таблицы. Последнее значение, по сути, описатель блока памяти. И когда вы вызовете GlobalLock , система просмотрит таблицу описателей и просто вернет адрес блока памяти, выделенного в стандартной куче процесса. Первоначально win32 отводит небольшой объем памяти, достаточный для хранения буквально нескольких элементов таблицы описателей. По мере необходимости в нее добавляются новые элементы; при этом win32 может увеличивать объем памяти, занимаемый таблицей описателей. Только из-за одних дополнительных операций с таблицей описателей старые 16-разрядные функции работают медленнее, чем win32-функции, управляющие кучами...
That's all, folks :-)
28.05.2000
Rev. 20.12.2001
P.S. При написании этой статьи кроме личного опыта использовался материал книги Джеффри Рихтера "Windows для профессионалов. 3-е издание." (кстати, по сети гуляет 4-е английское издание этой замечательной книжки, если вам сложно купить ее в магазине, то поищите в любимом поисковике файл Richter_book_withoutIE5.zip).