Gestión de memoria (I)
Lecturas de la serie
Sobre este artículo
Tiempo estimado de lectura: 5 minutos
Nivel: Básico
Última actualización: 10 de mayo, 2026
Nivel: Básico
Última actualización: 10 de mayo, 2026
1. Memoria virtual
|
| Imagen generada con inteligencia artificial |
La arquitectura x86-64 [1] define un espacio de memoria virtual con direcciones de 64 bits, de los cuales las implementaciones habilitan generalmente sólo los 48 bits menos significativos para direccionamiento. Esto proporciona un tamaño máximo de 256 TiB (248 bytes) de espacio virtual —a comparar con los 4 GiB (232 bytes) de la arquitectura x86—, de un máximo teórico de 16 EiB (264 bytes).
NOTA: MÁS ALLÁ DE LOS 48 BITS
Si bien el límite de 48 bits (256 TiB) ha sido el estándar desde la aparición de la arquitectura x86-64, las necesidades de la computación moderna han impulsado su expansión.
Arquitecturas recientes, como Intel Ice Lake o AMD Zen 4 (y posteriores), permiten habilitar hasta 57 bits de direccionamiento virtual. Esto aumenta el espacio de memoria virtual hasta 128 PiB, permitiendo al software gestionar conjuntos de datos masivos y nuevas tecnologías de memoria persistente que superan ampliamente los límites del esquema tradicional [2].
La figura inferior muestra la estructura (simplificada) del espacio de memoria virtual asignado a un proceso de un único hilo en una plataforma Linux de 64 bits, dividido en áreas de propósito bien definido [3]. A saber, y en orden creciente de direcciones en memoria:
- El segmento de código .text, que comienza típicamente en la dirección 0x00400000. Contiene las instrucciones ejecutables del programa. Es de sólo lectura, a fin de evitar que el programa modifique accidentalmente su propia lógica.
- Segmentos de datos (.data y .bss), donde se alojan las variables con duración de almacenamiento estático (tanto las variables globales como las locales declaradas static):
- El segmento .data contiene las variables estáticas inicializadas explícitamente por el programador.
- El segmento .bss contiene las variables estáticas sin inicializar, las cuales son puestas a cero automáticamente por el sistema operativo al cargar el programa en memoria.
- El área de almacenamiento libre (free store), una región de memoria dinámica destinada a objetos cuyo tiempo de vida es independiente del ámbito (scope) en el que fueron creados. Su tamaño fluctúa en tiempo de ejecución como resultado del uso de expresiones new y delete. Por convención, crece típicamente hacia direcciones de memoria superiores.
- Una región de memoria intermedia que contiene el código y los datos de las bibliotecas compartidas con otros procesos (e.g., la biblioteca estándar del lenguaje).
- La pila de usuario (user stack) se emplea para la ejecución de funciones y el alojamiento de variables locales. Su tamaño fluctúa dinámicamente durante la ejecución, aunque de forma mucho más ordenada y predecible que el free store: se trata de un área de gestión automática donde el compilador reserva y libera espacio conforme las funciones entran y salen de su ámbito de ejecución. Este proceso sigue estrictamente un esquema LIFO (Last In, First Out). Típicamente, la pila crece hacia direcciones de memoria inferiores.
- El núcleo del sistema operativo (kernel), es decir, la parte del sistema operativo que siempre reside en memoria, responsable de facilitar el acceso seguro al hardware de la computadora.
Con el fin de mitigar ataques de seguridad, las distribuciones actuales de Linux y otros sistemas operativos emplean la técnica ASLR (Address Space Layout Randomization [4]) para introducir aleatoriedad en la ubicación de las áreas de datos claves del proceso. Así, por ejemplo, pueden introducirse bloques de memoria sin utilizar de tamaño aleatorio entre el segmento .bss y el inicio del free store, o al fondo de la pila de usuario.
2. Paginación
El espacio de memoria virtual se divide en distintas páginas virtuales, a la vez que la memoria física se divide en marcos de página de igual tamaño. El sistema operativo transfiere entonces las páginas del proceso a los marcos de página libres en memoria principal según sea necesario, siguiéndose un estricto control en la asignación de las direcciones de memoria física. Múltiples procesos (muchos de los cuales pueden ser más extensos que la propia memoria física) pueden ejecutarse de esta forma simultáneamente.
Este procedimiento de paginación simplifica enormemente la gestión de la memoria. Así, por ejemplo, el sistema puede alojar bloques contiguos de memoria virtual en bloques desperdigados aleatoriamente en la memoria principal. El sistema operativo puede, asimismo, direccionar múltiples procesos a las mismas páginas de memoria física con el fin de permitir la compartición de bibliotecas (e.g., la biblioteca estándar del lenguaje C++) y el núcleo del sistema.
La figura inferior proporciona un esquema visual de este proceso:
3. Hacia un código robusto
En esta serie de posts analizaremos múltiples aspectos relativos a la gestión eficiente de la memoria, dando respuesta a cuestiones prácticas de interés:
- ¿Cómo proteger nuestro código frente a posibles corrupciones del user stack?
- ¿Cuándo preferir el free store frente a la pila para el almacenamiento de variables?
- ¿Qué impacto tiene sobre la eficiencia de nuestro programa la fragmentación del free store?
- ¿Cómo conseguir un código robusto que impida la fuga de recursos?
Referencias bibliográficas
- AMD64 Architecture Programmer’s Manual, Volume 2: System Programming – https://docs.amd.com/v/u/en-US/24593_3.44_APM_Vol2
- Wikipedia – Ice Lake (microprocessor) – https://en.wikipedia.org/wiki/Ice_Lake_(microprocessor)
- Bryant R. E. and O'Hallaron D. R., "Computer Systems: A Programmer's Perspective", 2nd Edition. Addison-Wesley (2010).
- Wikipedia – ASLR technique – https://en.wikipedia.org/wiki/Address_space_layout_randomization

