Objetos móviles y fijos

El primer atributo se refiere a la movilidad del objeto. De nuevo estamos ante un concepto que ha perdido sentido en el API32. En versiones de Windows de 16 bits cada objeto podría tener el atributo de movilidad activado o desactivado.

¿Por qué crear objetos móviles? Debido al modo en que se organiza la memoria en Windows de 16 bits, después de un tiempo de funcionamiento del sistema, la memoria podía estar muy fragmentada, con pequeños bloques de memoria reservados para distintos objetos, y pequeños huecos resultantes de la destrucción de objetos innecesarios. Esto puede provocar que, a pesar de existir suficiente memoria libre en el sistema, sea imposible conseguir un bloque del tamaño necesario para satisfacer una nueva petición.

La solución es que el sistema pueda trasladar algunos de los objetos existentes a otras posiciones, de modo que se desfragmente la memoria y las partes libres queden contiguas.

Pero esto crea un conflicto, ya que Windows es un sistema multitarea, habrá aplicaciones que estén usando ciertos objetos, de modo que tales objetos no pueden ser movidos. O bien, aunque esos objetos no se estén usando, los punteros que los manejan tienen valores constantes, o deben conservar sus valores mientras el proceso que los ha creado siga funcionando. De otro modo sería imposible manejarlos y liberarlos.

Cuando se crea un objeto de memoria fijo el sistema proporciona un puntero de 32 bits, y se pueden manejar como punteros corrientes:

  1. Creación: se crea un objeto fijo de memoria, y se obtiene un puntero.
  2. Uso: podemos trabajar con él ya que el valor del puntero es fijo.
  3. Destrucción: cuando ya no sea necesario destruimos el objeto, liberando la memoria asociada.

Sin embargo, los objetos de memoria móviles creados usando el API de Windows no proporcionan una dirección fija, sino un manipulador de memoria. El proceso es el siguiente:

  1. Creación: se crea un objeto movible de memoria, y se obtiene un manipulador.
  2. Bloqueo: cuando se va a usar el objeto se bloquea el objeto, y se obtiene una dirección para la memoria correspondiente.
  3. Uso: mientras permanece bloqueado, el objeto no será movido por el sistema, y podemos trabajar con él como si el valor del puntero fuese fijo.
  4. Desbloqueo: cuando no necesitamos manipular el objeto, lo desbloqueamos, y el sistema podrá moverlo si lo considera necesario.
  5. Destrucción: cuando ya no sea necesario, y si no está bloqueado, destruimos el objeto, liberando la memoria asociada y el manipulador.

Por supuesto, crear objetos móviles tiene la ventaja de que el sistema puede gestionar la memoria de un modo mucho más eficaz, pero siempre es posible, si nuestro programa lo requiere, crear objetos fijos. La diferencia es que en este caso, el sistema no podrá mover tales objetos para desfragmentar la memoria.

Objetos descartables y no descartables

El otro atributo está relacionado con el mismo problema.

Cuando se debe desfragmenta la memoria para conseguir bloques libres lo suficientemente largos, es necesario mover el contenido de cada objeto, de modo que los valores que contienen no se pierdan. El concepto de memoria descartable va un paso más allá. Si el contenido de cierto objeto móvil es fácilmente recuperable o se puede reconstruir, sin necesidad de almacenarlo de forma permanente, el sistema de gestión de memoria no necesita almacenarlo permanentemente, y puede mantener sólo los manipuladores, sin necesidad de mantener el contenido de tales objetos.

Por ejemplo, tenemos un mapa de bits en memoria creado a partir de un recurso. El contenido de la memoria correspondiente a ese mapa de bits puede ser recuperado del recurso tantas veces como sea necesario, pero, siempre que sea posible, nos conviene mantenerlo en memoria, ya que recuperar ese recurso requiere cierto tiempo. Si creamos ese objeto como descartable, el sistema puede borrar la memoria asociada cuando lo considere necesario, haciendo innecesario tanto mantener esa memoria como copiarla.

El proceso, cuando se trabaja con objetos descartables es algo más complicado:

  1. Creación: se crea un objeto descartable de memoria, y se obtiene un manipulador.
  2. Bloqueo: cuando se va a usar el objeto se bloquea el objeto, y se obtiene una dirección para la memoria correspondiente.
  3. Restitución: se averigua si la memoria del objeto ha sido descartado por el sistema, y en ese caso se restituye su valor, ya sea por cálculo o por carga desde un fichero.
  4. Uso: mientras permanece bloqueado, el objeto no será movido por el sistema, y podemos trabajar con él como si el valor del puntero fuese fijo.
  5. Desbloqueo: cuando no necesitamos manipular el objeto, lo desbloqueamos, y el sistema podrá moverlo o descartarlo si lo considera necesario.
  6. Destrucción: cuando ya no sea necesario destruimos el objeto, liberando la memoria asociada y el manipulador.

Cada vez que bloqueemos el objeto hay que verificar si su memoria ha sido descartada o no. Un objeto cuya memoria haya sido descartada contendrá basura.

Tampoco es posible crear objetos fijos descartables. Los objetos descartables siempre deben tener el atributo de movilidad.

Funciones clásicas para manejo de memoria

Disponemos de nueve funciones para manejar memoria local y global. Los nombres son los mismos, cambiando el prefijo "Local" y "Global".

A modo de resumen, ya que no será frecuente que usemos estas funciones, podemos considerar esta tabla:

Local Global
Reservar memoria LocalAlloc GlobalAlloc
Descartar objeto de memoria LocalDiscard GlobalDiscard
Información sobre objeto de memoria LocalFlags GlobalFlags
Liberar objeto de memoria LocalFree GlobalFree
Obtener un manipulador de objeto LocalHandle GlobalHandle
Bloquear objeto LocalLock GlobalLock
Reubicar objeto de memoria LocalRealloc GlobalRealloc
Tamaño de un objeto LocalSize GlobalSize
Desbloquear un objeto LocalUnlock GlobalUnlock

Como complemento disponemos de la función GlobalMemoryStatus para obtener información sobre la memoria disponible en el sistema. Esta función usa una estructura MEMORYSTATUS para almacenar la información.

Ejemplo

Existen además, otras funciones útiles para manejar memoria:

Desventajas de este modelo de memoria

Afortunadamente, esto pertenece al pasado, salvo que tengas que crear aplicaciones para versiones de Windows de 16 bits, claro.

El modelo de memoria virtual hace que todo este tema de memoria móvil carezca de sentido, ya que nuestras aplicaciones no trabajan con memoria real, el sistema siempre puede mover cualquier página de memoria para liberar recursos, sin que eso afecte en modo alguno a las direcciones de memoria virtuales.

La memoria descartable aún puede resultar útil, ya que permite liberar recursos que se usan poco o que pueden regenerarse fácilmente.

Otra desventaja es que las funciones estándar para manejar memoria: malloc y free no funcionan de forma segura en el modelo de memoria del API de 16 bits, algo que sí sucede en el modelo virtual. En el API de 16 bits, la función malloc no puede obtener objetos de memoria móviles o descartables. Lo mismo sucede con los operadores new y delete.

Sólo en el caso en que queramos crear objetos descartables tendremos que hacer uso de las funciones del API para manejar memoria.