winapi memory heap new-operator

winapi - ¿Cuáles son las diferencias entre VirtualAlloc y HeapAlloc?



memory new-operator (6)

Hay muchos métodos para asignar memoria en el entorno de Windows, como VirtualAlloc , HeapAlloc , malloc , new .

Por lo tanto, ¿cuál es la diferencia entre ellos?


Cada API es para diferentes usos. Cada uno también requiere que use la función de desasignación / liberación correcta cuando haya terminado con la memoria.

VirtualAlloc

Una API de Windows de bajo nivel que ofrece muchas opciones, pero que es principalmente útil para personas en situaciones bastante específicas. Solo puede asignar memoria en (editar: no 4 KB) fragmentos más grandes. Hay situaciones en las que lo necesita, pero sabrá cuándo se encuentra en una de estas situaciones. Una de las más comunes es si tiene que compartir memoria directamente con otro proceso. No lo use para la asignación de memoria de propósito general. Use VirtualFree para desasignar.

HeapAlloc

Asigna cualquier tamaño de memoria que pidas, no en grandes porciones que VirtualAlloc . HeapAlloc sabe cuándo necesita llamar a VirtualAlloc y lo hace automáticamente. Como malloc , pero solo para Windows, y ofrece un par de opciones más. Adecuado para asignar trozos generales de memoria. Algunas API de Windows pueden requerir que use esto para asignar la memoria que les pasa, o usar su HeapFree complementario para liberar la memoria que le devuelven.

malloc

La forma C de asignar memoria. Prefiera esto si está escribiendo en C en lugar de en C ++, y quiere que su código también funcione en computadoras Unix, o si alguien específicamente dice que necesita usarlo. No inicializa la memoria. Adecuado para asignar trozos generales de memoria, como HeapAlloc . Una API simple Use free para desasignar. HeapAlloc Visual C ++ llama a HeapAlloc .

nuevo

La forma C ++ de asignar memoria. Prefiero esto si estás escribiendo en C ++. También coloca un objeto u objetos en la memoria asignada. Use delete para desasignar (o delete[] para matrices). Las new llamadas de Visual Studio HeapAlloc , y luego tal vez HeapAlloc los objetos, dependiendo de cómo lo llame.

En los estándares recientes de C ++ (C ++ 11 y superiores), si tiene que usar manualmente delete , lo está haciendo mal y, en su lugar, debe usar un puntero inteligente como unique_ptr . Desde C ++ 14 en adelante, lo mismo puede decirse de lo new (reemplazado por funciones como make_unique() ).

También hay un par de otras funciones similares como SysAllocString que se le puede decir que debe usar en circunstancias específicas.


En resumen:

  • VirtualAlloc, HeapAlloc, etc. son API de Windows que asignan memoria de varios tipos directamente desde el sistema operativo. VirtualAlloc administra páginas en el sistema de memoria virtual de Windows, mientras que HeapAlloc asigna desde un montón de sistema operativo específico. Francamente, es poco probable que necesites usar ninguno de ellos.

  • malloc es una función de biblioteca estándar C (y C ++) que asigna memoria a su proceso. Las implementaciones de malloc suelen utilizar una de las API del sistema operativo para crear un grupo de memoria cuando se inicia la aplicación y luego asignarla a medida que realiza solicitudes malloc.

  • new es un operador estándar de C ++ que asigna memoria y luego llama apropiadamente a los constructores en esa memoria. Se puede implementar en términos de malloc o en términos de las API de OS, en cuyo caso también creará típicamente un grupo de memoria al inicio de la aplicación.


Es muy importante entender la distinción entre las API de asignación de memoria (en Windows) si planea usar un lenguaje que requiera administración de memoria (como C o C ++). Y la mejor manera de ilustrarlo en mi humilde opinión es con un diagrama:

Tenga en cuenta que esta es una vista muy simplificada, específica de Windows.

La forma de entender este diagrama es que cuanto más alto en el diagrama se encuentra un método de asignación de memoria, más alto nivel de implementación usa. Pero comencemos desde abajo.

Kernel-Mode Memory Manager

Proporciona todas las reservas de memoria y asignaciones para el sistema operativo, así como compatibilidad con archivos mapeados en memoria , memoria compartida , operaciones de copiado sobre escritura , etc. No se puede acceder directamente desde el código de modo de usuario, así que omito aquí.

VirtualAlloc / VirtualFree

Estas son las API de nivel más bajo disponibles desde el modo de usuario . La función VirtualAlloc básicamente invoca ZwAllocateVirtualMemory que a su vez hace una syscall rápida al ring0 para relegar el procesamiento posterior al administrador de memoria kernel. También es el método más rápido para reservar / asignar bloques de memoria nueva de todos los disponibles en el modo de usuario.

Pero viene con dos condiciones principales:

  • Solo asigna bloques de memoria alineados en el límite de granularidad del sistema.

  • Solo asigna bloques de memoria del tamaño que es el múltiplo de la granularidad del sistema.

Entonces, ¿qué es la granularidad de este sistema ? Puede obtenerlo llamando a GetSystemInfo . Se devuelve como el parámetro dwAllocationGranularity . Su valor es la implementación (y posiblemente el hardware) específico, pero en muchos sistemas Windows de 64 bits está configurado en 0x10000 bytes, o 64K .

Entonces, ¿qué significa todo esto? Si intenta asignar, digamos solo un bloque de memoria de 8 bytes con VirtualAlloc :

void* pAddress = VirtualAlloc(NULL, 8, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

Si tiene éxito, pAddress se alineará en el límite de 0x10000 bytes. Y a pesar de que solicitó solo 8 bytes, el bloque de memoria real que obtendrá será toda la page (o algo así como 4K bytes. El tamaño de página exacto se devuelve en el parámetro dwAllocationGranularity ). Pero, además de eso, el todo el bloque de memoria que abarca 0x10000 bytes (o 64K en la mayoría de los casos) de pAddress no estará disponible para otras asignaciones. Entonces, en cierto sentido, al asignar 8 bytes, podrías estar pidiendo 65536.

Entonces, la moraleja de la historia aquí no es sustituir VirtualAlloc por asignaciones genéricas de memoria en su aplicación. Debe usarse para casos muy específicos, como se hace con el montón a continuación. (Por lo general, para reservar / asignar grandes bloques de memoria).

Usar VirtualAlloc incorrectamente puede conducir a una fragmentación de memoria grave.

HeapCreate / HeapAlloc / HeapFree / HeapDestroy

En pocas palabras, las funciones de montón son básicamente un envoltorio para la función VirtualAlloc . Otras respuestas aquí proporcionan un concepto bastante bueno de eso. Añadiré que, en una visión muy simplista, la forma en que funciona Heap es la siguiente:

  • HeapCreate reserva un gran bloque de memoria virtual llamando internamente a VirtualAlloc (o ZwAllocateVirtualMemory para ser específico). También establece una estructura de datos interna que puede rastrear asignaciones de tamaño más pequeño dentro del bloque reservado de memoria virtual.

  • Cualquier llamada a HeapAlloc y HeapFree realmente no asigna / libera ninguna memoria nueva (a menos que, por supuesto, la solicitud exceda lo que ya se ha reservado en HeapCreate ) sino que HeapCreate (o commit ) un gran fragmento previamente reservado, diseccionando en bloques de memoria más pequeños que un usuario solicita.

  • HeapDestroy a su vez llama VirtualFree que realmente libera la memoria virtual.

Por lo tanto, todo esto hace que las funciones de montón sean candidatos perfectos para las asignaciones de memoria genéricas en su aplicación. Es ideal para asignaciones de memoria de tamaño arbitrario. Pero un pequeño precio a pagar por la conveniencia de las funciones de montón es que introducen una ligera sobrecarga sobre VirtualAlloc cuando se reservan bloques de memoria más grandes.

Otra cosa buena de Heap es que realmente no necesitas crear uno. Por lo general, se crea para usted cuando comienza su proceso. Entonces uno puede acceder llamando a la función GetProcessHeap .

malloc / libre

Es un contenedor específico del idioma para las funciones de montón . A diferencia de HeapAlloc , HeapFree , etc. estas funciones funcionarán no solo si su código está compilado para Windows, sino también para otros sistemas operativos (como Linux, etc.)

Esta es una forma recomendada de asignar / liberar memoria si programa en C. (A menos que esté codificando un controlador de dispositivo en modo kernel específico).

new / eliminar

Venga como un operador de gestión de memoria de alto nivel (bueno, para C++ ). Son específicos para el C++ , y como malloc para C , también son los contenedores para las funciones de heap . También tienen un montón de su propio código que trata la inicialización específica de C++ de constructores, la desasignación en destructores, etc.

Estas funciones son una forma recomendada de asignar / liberar memoria y objetos si programa en C++ .

Por último, un comentario que quiero hacer sobre lo que se ha dicho en otras respuestas sobre el uso de VirtualAlloc para compartir memoria entre procesos. VirtualAlloc por sí solo no permite compartir su memoria reservada / asignada con otros procesos. Para eso se necesita usar la API CreateFileMapping que puede crear un bloque de memoria virtual con nombre que se puede compartir con otros procesos. También puede asignar un archivo en el disco a la memoria virtual para acceso de lectura / escritura. Pero ese es otro tema.


VirtualAlloc => Asigna directamente a la memoria virtual, usted reserva / confirma en bloques. Esto es ideal para grandes asignaciones, por ejemplo, grandes matrices.

HeapAlloc / new => asigna la memoria en el montón predeterminado (o cualquier otro montón que pueda crear). Esto se asigna por objeto y es ideal para objetos más pequeños. El montón predeterminado es serializable, por lo tanto, tiene una asignación de hilos garantizada (esto puede causar algunos problemas en escenarios de alto rendimiento y es por eso que puedes crear tus propios montones).

malloc => usa el montón de tiempo de ejecución de C, similar a HeapAlloc pero es común para los escenarios de compatibilidad.

En pocas palabras, el montón es solo una porción de memoria virtual que está gobernada por un administrador de montón (en lugar de memoria virtual sin procesar)

El último modelo en el mundo de la memoria es la asignación de archivos de memoria, este escenario es ideal para grandes cantidades de datos (como archivos de gran tamaño). Esto se usa internamente cuando abre un EXE (no carga el EXE en la memoria, solo crea un archivo mapeado en la memoria).


VirtualAlloc ===> sbrk() en UNIX

HeapAlloc > malloc() en UNIX


VirtualAlloc es una asignación especializada del sistema de memoria virtual (VM) del sistema operativo. Las asignaciones en el sistema VM deben realizarse en una granularidad de asignación que (la granularidad de asignación) depende de la arquitectura. La asignación en el sistema VM es una de las formas más básicas de asignación de memoria. Las asignaciones de VM pueden tomar varias formas, la memoria no está necesariamente dedicada o respaldada físicamente en la RAM (aunque puede ser). La asignación de VM suele ser un tipo de asignación de propósito especial , ya sea porque la asignación tiene que ser

  • ser muy grande,
  • necesita ser compartido,
  • debe estar alineado en un valor particular (razones de rendimiento) o
  • la persona que llama no necesita usar toda esta memoria a la vez ...
  • etc ...

HeapAlloc es esencialmente lo que finalmente llaman malloc y new . Está diseñado para ser muy rápido y utilizable bajo muchos tipos diferentes de escenarios de una asignación de propósito general. Es el "Heap" en un sentido clásico. Los montones son realmente configurados por un VirtualAlloc , que es lo que se utiliza inicialmente para reservar espacio de asignación del sistema operativo. Después de que VirtualAlloc inicialice el espacio, se configuran varias tablas, listas y otras estructuras de datos para mantener y controlar el funcionamiento del HEAP. Parte de esa operación es en forma de dimensionamiento dinámico (crecimiento y contracción) del montón, adaptando el montón a usos particulares (asignaciones frecuentes de algún tamaño), etc.

new y malloc son algo iguales, malloc es esencialmente una llamada exacta a HeapAlloc( heap-id-default ) ; sin embargo, puede configurar [adicionalmente] la memoria asignada para objetos C ++. Para un objeto dado, C ++ almacenará tablas virtuales en el montón para cada llamante. Estos vtables son redireccionamientos para la ejecución y forman parte de lo que le da a C ++ sus características de OO, como herencia, sobrecarga de funciones, etc.

Algunos otros métodos de asignación comunes como _alloca() y _malloca() se basan en la pila ; Las asignaciones de archivos están realmente asignadas con VirtualAlloc y configuradas con banderas de bits particulares que designan esas asignaciones para que sean de tipo FILE .

La mayoría de las veces, debe asignar memoria de manera coherente con el uso de esa memoria;). new en C ++, malloc para C, VirtualAlloc para casos masivos o IPC.

*** Tenga en cuenta que las asignaciones de memoria grandes realizadas por HeapAlloc realidad se envían a VirtualAlloc después de cierto tamaño (un par de cientos k o 16 MB o algo que se me olvida, pero bastante grande :)).

*** EDIT brevemente comenté sobre IPC y VirtualAlloc , también hay algo muy claro sobre un VirtualAlloc relacionado que ninguno de los que respondieron a esta pregunta ha discutido.

VirtualAlloc Ex es lo que un proceso puede usar para asignar memoria en un espacio de direcciones de un proceso diferente . Más típicamente, esto se usa en combinación para obtener una ejecución remota en el contexto de otro proceso a través de CreateRemoteThread (similar a CreateThread , el hilo solo se ejecuta en el otro proceso).