Dónde están los bytes
Esta es una pregunta muy interesante, también planteada en la documentación oficial de Zig: Where are the bytes. Por cierto, te recomiendo que eches un vistazo a la documentación oficial cuando tengas dudas, o simplemente por aprender más sobre Zig.
A continuación puedes ver un diagrama de memoria de un programa Zig:
Diagrama de memoria en un programa Zig |
|
|
La memoria usada por un programa Zig se divide en tres partes bien diferenciadas:
- Stack (pila),
- Heap (en español “montículo”, aunque se usa más el término memoria dinámica),
- Memoria de tiempo de compilación.
Memoria de tiempo de compilación
En la base del programa está la memoria de tiempo de compilación. Esta parte de memoria contiene todo lo que Zig conoce antes de ejecutar el programa: código, datos literales, valores constantes y cualquier dato evaluado en comptime. Se incluyen en el binario final, listos para ser cargados en memoria cuando el programa arranca. Es una zona predecible - no cambia durante la ejecución porque su contenido fue definido antes de empezar.
Stack
La stack funciona como una pila: los datos se apilan como si fueran cajas, unos encima de otros. Para acceder a un stack, solo se accede por la cima. La política de acceso es LIFO (Last In, First Out) - el último dato en entrar es el primero en salir. La pila como estructura de datos es rápida y se usa para almacenar valores de tamaño reducido. Se sitúa en la parte alta de la memoria del programa y crece hacia abajo. Cuando se liberan datos, la pila se reduce y ocupa menos memoria.
Cuando declaramos una variable o constante cuyo valor no es conocido en tiempo de compilación - pero sí se conoce su tamaño - Zig puede usar el stack para almacenarla.
Eso sí- aunque se conozca el tamaño, podemos - pero no siempre debemos - usar la stack ya que tiene un espacio limitado. Si almacenamos estructuras grandes o hacemos muchas llamadas recursivas, podríamos provocar el desbordamiento de pila (stack overflow).
Ya vimos el ejemplo de un posible crecimiento desmedido de la stack en el apartado: Bucles/ Recursividad con una función que se llamaba a sí misma sin una condición de parada:
recursive_infinite.zig |
|
fn infinite_loop() void { infinite_loop(); } pub fn main() void { infinite_loop(); } |
|
$ zig run recursive_infinite.zig |
|
Segmentation fault (core dumped) |
A cada llamada recursiva se le asigna un frame (bloque dedicado de memoria) en la stack. El tamaño de ese frame depende del sistema operativo y su gestión de recursos. Incluso aunque el espacio de cada frame sea pequeño, al repetirse miles de veces, se acumulan uno trás otro hasta que el programa intenta acceder a una zona de memoria fuera del espacio asignado a su pila. De esa manera provoca un Segmentation fault y el sistema operativo mata el proceso.
Heap
Heap (memoria dinámica) es la zona de memoria que usamos para almacenar datos cuyo tamaño no se conoce hasta que se ejecuta el programa. Esto sucede, por ejemplo, cuando se carga el contenido de un fichero, al leer la entrada por teclado o se generan estructuras dinámicas de datos etc. El heap es más lento pero mucho más grande que la stack.
Si necesitamos espacio, lo solicitamos en tiempo de ejecución. Si hay memoria disponible, el sistema nos la concede. Si no - obtendremos un error y lo tendremos que gestionar de manera adecuada. El heap, al contrario que la stack, crece hacia arriba, desde las direcciones más bajas hacia las más altas.
