[Части этого являются чисто деталями реализации, а не вещами, на которые ваше приложение должно полагаться, поэтому используйте их только в информационных целях, а не в качестве официальной документации или контракта любого рода. Тем не менее, есть некоторая ценность в понимании того, как все реализовано внутри, хотя бы для целей отладки.]
Да, функция VirtualAlloc () - это функция рабочей лошадки для выделения памяти в Windows. Это низкоуровневая функция, которую операционная система предоставляет вам, если вам нужны ее функции, но также и функция, которую система использует для внутренних целей. (Чтобы быть точным, вероятно, он не вызывает VirtualAlloc () напрямую, а скорее вызывает функцию еще более низкого уровня, которую VirtualAlloc () также вызывает, например NtAllocateVirtualMemory (), но это просто семантика и не меняет наблюдаемое поведение. )
Следовательно, HeapAlloc () построен поверх VirtualAlloc (), как и GlobalAlloc () и LocalAlloc () (хотя последние два устарели в 32-битной Windows и в основном никогда не должны использоваться приложениями - предпочитайте явный вызов HeapAlloc () ).
Конечно, HeapAlloc () - это не просто оболочка вокруг VirtualAlloc (). Это добавляет некоторую собственную логику. VirtualAlloc () всегда выделяет память большими фрагментами, определяемыми степенью детализации распределения системы, которая зависит от оборудования (может быть получена путем вызова GetSystemInfo () и чтения значения SYSTEM_INFO.dwAllocationGranularity
). HeapAlloc () позволяет выделять меньшие фрагменты памяти с любой необходимой степенью детализации, что гораздо больше подходит для типичного программирования приложений. Внутренне HeapAlloc () обрабатывает вызов VirtualAlloc () для получения большого фрагмента и затем разделяет его по мере необходимости. Это не только упрощает API, но и повышает его эффективность.
Обратите внимание, что функции распределения памяти, предоставляемые библиотекой времени выполнения C (CRT) - а именно, malloc () в C и новый оператор C ++ - пока находятся на более высоком уровне. Они построены на основе HeapAlloc () (по крайней мере, в реализации CRT от Microsoft). Внутри они выделяют значительный кусок памяти, который в основном служит «главным» блоком памяти для вашего приложения, а затем по запросу делят его на более мелкие блоки. Когда вы освобождаете / удаляете эти отдельные блоки, они возвращаются в пул. Опять же, этот дополнительный уровень обеспечивает упрощенный интерфейс (и, в частности, возможность писать независимый от платформы код), а также повышенную эффективность в общем случае.
Отображенные в память файлы и другие функциональные возможности, предоставляемые различными API-интерфейсами ОС, также построены на подсистеме виртуальной памяти и поэтому внутренне вызывают VirtualAlloc () (или эквивалент более низкого уровня).
Так что да, по сути, процедура выделения памяти самого низкого уровня для обычного приложения Windows - это VirtualAlloc (). Но это не значит, что это функция «рабочая лошадка», которую обычно следует использовать для выделения памяти. Вызывайте VirtualAlloc () только в том случае, если вам действительно нужны его дополнительные функции. В противном случае либо используйте процедуры выделения памяти из стандартной библиотеки, либо, если у вас есть веские причины избегать их (например, не связываться с CRT или создавать собственный пул памяти), вызовите HeapAlloc ().
Также обратите внимание, что вы всегда должны освобождать / освобождать память, используя механизм, соответствующий тому, который вы использовали для выделения памяти. Тот факт, что все функции выделения памяти в конечном итоге вызывают VirtualAlloc (), не означает, что вы можете освободить эту память, вызвав VirtualFree (). Как обсуждалось выше, эти другие функции реализуют дополнительную логику поверх VirtualAlloc () и, следовательно, требуют, чтобы вы вызывали их собственные процедуры для освобождения памяти. Вызывайте VirtualFree () только в том случае, если вы выделили память самостоятельно с помощью вызова VirtualAlloc (). Если память была выделена с помощью HeapAlloc (), вызовите HeapFree (). Для malloc () вызовите free (); для новых, вызовите удаление.
Что касается конкретного сценария, описанного в вашем вопросе, мне непонятно, почему вы беспокоитесь об этом. Важно помнить о различии между зарезервированной памятью и выделенной памятью. Зарезервировано просто означает, что этот конкретный блок в адресном пространстве зарезервирован для использования процессом. Зарезервированные блоки использовать нельзя. Чтобы использовать блок памяти, он должен быть зафиксирован, что относится к процессу выделения резервного хранилища для памяти либо в файле подкачки, либо в физической памяти. Это также иногда называют сопоставлением. Резервирование и фиксация могут выполняться как два отдельных шага или одновременно. Например, вы можете захотеть зарезервировать непрерывное адресное пространство для использования в будущем, но на самом деле оно вам пока не нужно, поэтому вы не фиксируете его. Память, которая была зарезервирована, но не зафиксирована, фактически не выделяется.
Фактически, вся эта зарезервированная память вовсе не может быть утечкой. Довольно распространенная стратегия, используемая при отладке, состоит в том, чтобы зарезервировать определенный диапазон адресов памяти без их фиксации, чтобы перехватить попытки доступа к памяти в этом диапазоне с исключением «нарушения прав доступа». Тот факт, что ваша DLL не делает этих больших резервов при компиляции в режиме Release, предполагает, что, действительно, это может быть стратегия отладки. И это также предлагает лучший способ определения источника: вместо того, чтобы сканировать ваш код в поисках всех процедур распределения памяти, просканируйте свой код в поисках условного кода, который зависит от конфигурации сборки. Если вы делаете что-то другое, когда определены DEBUG
или _DEBUG
, то, вероятно, именно здесь происходит волшебство.
Другое возможное объяснение - это реализация malloc () или new в CRT. Когда вы выделяете небольшой кусок памяти (скажем, несколько КБ), CRT фактически резервирует гораздо больший блок, но фиксирует только кусок запрошенного размера. Когда вы впоследствии освободите / удалите этот небольшой кусок памяти, он будет списан, но больший блок не будет возвращен обратно в ОС. Причина в том, чтобы позволить будущим вызовам malloc / new повторно использовать этот зарезервированный блок памяти. Если последующий запрос предназначен для блока большего размера, чем может быть удовлетворено текущим зарезервированным адресным пространством, он резервирует дополнительное адресное пространство. Если при отладке сборок вы постоянно выделяете и освобождаете все большие блоки памяти, то, что вы видите, может быть результатом фрагментации памяти. Но на самом деле это не проблема, если не считать незначительного снижения производительности, о котором действительно не стоит беспокоиться при отладке сборок.
person
Cody Gray
schedule
30.11.2016