Очень важно понимать различие между API-интерфейсами выделения памяти (в Windows), если вы планируете использовать язык, требующий управления памятью (например, C или C ++). И лучший способ проиллюстрировать это IMHO - диаграмма:
Обратите внимание, что это очень упрощенное, специфичное для Windows представление.
Способ понять эту диаграмму состоит в том, что чем выше на диаграмме метод выделения памяти, тем выше используется реализация более высокого уровня . Но начнем снизу.
Диспетчер памяти в режиме ядра
Он обеспечивает все резервирования и выделения памяти для операционной системы, а также поддерживает сопоставленные с памятью файлы , общая память , копирование при записи операции и т. д. Он не доступен напрямую из кода пользовательского режима, поэтому я пропущу его здесь.
Это API-интерфейсы самого низкого уровня , доступные в пользовательском режиме . Функция VirtualAlloc
в основном вызывает ZwAllocateVirtualMemory , которая, в свою очередь, выполняет быстрый системный вызов до ring0
, чтобы передать дальнейшую обработку менеджеру памяти ядра. Это также самый быстрый способ зарезервировать / выделить блок новой памяти из всех доступных в пользовательском режиме.
Но у него есть два основных условия:
Распределяет только блоки памяти, выровненные по границе гранулярности системы.
Распределяет блоки памяти только того размера, который кратен степени детализации системы.
Так что же это за гранулярность системы ? Вы можете получить его, позвонив GetSystemInfo . Возвращается как параметр dwAllocationGranularity
. Его значение зависит от реализации (и, возможно, от аппаратного обеспечения), но во многих 64-разрядных системах Windows оно установлено на 0x10000
байт или 64K
.
Так что все это означает, что если вы попытаетесь выделить, скажем, просто 8-байтовый блок памяти с VirtualAlloc
:
void* pAddress = VirtualAlloc(NULL, 8, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
В случае успеха pAddress
будет выровнено по границе байта 0x10000
. И даже несмотря на то, что вы запросили только 8 байтов, фактический блок памяти, который вы получите, будет содержать все page
(или что-то вроде 4K
байтов. Точный размер страницы возвращается в dwPageSize
параметр.) Но, кроме того, весь блок памяти, охватывающий 0x10000
байт (или 64K
в большинстве случаев) из pAddress
, не будет доступным для дальнейшего распределения. Таким образом, в некотором смысле, выделив 8 байт, вы могли бы также запросить 65536.
Таким образом, мораль этой истории заключается не в том, чтобы заменить VirtualAlloc
общим распределением памяти в вашем приложении. Он должен использоваться для очень специфических случаев, как это делается с heap ниже. (Обычно для резервирования / выделения больших блоков памяти.)
Неправильное использование VirtualAlloc
может привести к серьезной фрагментации памяти.
В двух словах, функции heap в основном являются оболочкой для функции VirtualAlloc
. Другие ответы здесь дают довольно хорошее представление об этом. Я добавлю, что в очень упрощенном виде способ heap работает так:
HeapCreate
резервирует большой блок виртуальной памяти, вызывая VirtualAlloc
для внутреннего использования (или ZwAllocateVirtualMemory
для уточнения). Он также устанавливает внутреннюю структуру данных, которая может отслеживать дальнейшие распределения меньшего размера в зарезервированном блоке виртуальной памяти.
Любые вызовы HeapAlloc
и HeapFree
фактически не выделяют / освобождают какую-либо новую память (если, конечно, запрос не превышает то, что уже зарезервировано в HeapCreate
), а вместо этого они метра * (или commit
) ранее зарезервированного большого блока, разбивая его на меньшие блоки памяти, которые запрашивает пользователь.
HeapDestroy
в свою очередь вызывает VirtualFree
, что фактически освобождает виртуальную память.
Таким образом, все это делает функции heap идеальными кандидатами для общего распределения памяти в вашем приложении. Это отлично подходит для произвольного размера памяти. Но небольшая цена за удобство функций heap заключается в том, что они вносят небольшие издержки по сравнению с VirtualAlloc
при резервировании больших блоков памяти.
Еще одна хорошая вещь в куче заключается в том, что вам не нужно создавать ее. Обычно он создается для вас, когда начинается ваш процесс. Таким образом, можно получить к нему доступ, вызвав функцию GetProcessHeap .
Является языковой оболочкой для функций heap . В отличие от HeapAlloc
, HeapFree
и т. Д. Эти функции будут работать не только в том случае, если ваш код скомпилирован для Windows, но и для других операционных систем (таких как Linux и т. Д.)
Это рекомендуемый способ выделения / освобождения памяти, если вы программируете на языке C. (Если только вы не кодируете определенный драйвер устройства режима ядра.)
Приходите в качестве операторов высокого уровня (ну, для C++
) операторов управления памятью. Они специфичны для языка C++
и, подобно malloc
для C
, также являются оболочками для функций heap
. У них также есть целый набор собственного кода, который имеет дело с C++
-специфической инициализацией конструкторов, освобождением в деструкторах и т. Д.
Эти функции являются рекомендуемым способом выделения / освобождения памяти и объектов, если вы программируете на C++
.
Наконец, я хочу сделать один комментарий о том, что было сказано в других ответах об использовании VirtualAlloc
для разделения памяти между процессами. VirtualAlloc
само по себе не позволяет делиться своей зарезервированной / выделенной памятью с другими процессами. Для этого необходимо использовать API CreateFileMapping
, который может создавать именованный блок виртуальной памяти, который может использоваться другими процессами. Он также может отобразить файл на диске в виртуальную память для доступа на чтение / запись. Но это уже другая тема.