1. Qué vamos a simular
El simulador ejecuta un programa de ejemplo en pasos discretos. En cada paso ocurre una de estas operaciones:
- Entrada a función: se crea un marco de activación en la pila.
- Salida de función: se destruye el marco actual.
- malloc: se reserva un bloque en el heap.
- free: se libera un bloque del heap.
- Asignación a variable: se escribe en una local o en un bloque del heap.
En paralelo se dibujan la pila (marcos apilados) y el heap (bloques asignados/libres). El usuario controla el avance con botones.
2. El programa de ejemplo
Este es el programa que simula. Es deliberadamente simple para que cada operación sea visible:
#include <stdio.h>
#include <stdlib.h>
void procesar(int x)
{
int local = x * 2; /* marco de procesar */
char *buf = malloc(16); /* bloque en el heap */
buf[0] = (char)local;
printf("%d\\n", local);
free(buf); /* liberar bloque */
} /* destruir marco */
int main(void)
{
int i;
for (i = 0; i < 3; i++) {
procesar(i); /* llamada recursiva no recursiva */
}
char *g = malloc(32); /* bloque que no se libera */
g[0] = 1;
return 0; /* fuga de memoria */
}
El programa llama a procesar tres veces (cada una crea un marco y un bloque del heap que se libera) y al final pide un bloque de 32 bytes que nunca se libera (fuga).
3. Cómo se dibuja la pila
La pila se dibuja como una columna de marcos, de arriba (direcciones altas) hacia abajo (direcciones bajas). El marco actual está en la parte inferior. Cada marco muestra:
- El nombre de la función.
- Las variables locales con sus valores.
- El parámetro recibido.
Al entrar a una función se añade un marco; al salir se quita el último.
4. Cómo se dibuja el heap
El heap se dibuja como una fila de bloques. Cada bloque muestra:
- Su índice y tamaño.
- Si está asignado (color) o libre (gris).
- El valor almacenado (si se escribió).
Al hacer malloc se añade un bloque asignado; al hacer free se marca como libre (reutilizable).
5. El simulador interactivo
Usa los botones para avanzar paso a paso. El panel izquierdo muestra el código con la línea actual resaltada; el derecho muestra la pila y el heap.
Simulador · pila y heap paso a paso
CÓDIGO
PILA (crece hacia abajo)
HEAP
6. Qué observar
- La pila crece y decrece: cada llamada a
procesarañade un marco; al retornar se elimina. - Las variables locales se reutilizan: el marco de la segunda llamada ocupa el mismo espacio que el primero.
- El heap acumula bloques: cada
malloc(16)añade un bloque;freelo marca como libre. - La fuga final: el bloque de 32 bytes del final nunca se libera, por lo que queda asignado al terminar.
- La pila del hilo principal:
maintiene su propio marco con la variableiy el punterog.
7. Limitaciones del simulador
Este simulador es una simplificación didáctica. No modela:
- Los metadatos del asignador (cabeceras de bloque).
- La reutilización real de bloques libres (el simulador marca como libre pero no reasigna).
- Los registros, el puntero de marco ni el código máquina.
- La concurrencia ni los hilos.
Para ver la pila y el heap reales, usa GDB y Valgrind (siguiente sección).
8. Mirar en Linux: gdb y valgrind
$ gcc -g -O0 -o ejemplo ejemplo.c
$ gdb ./ejemplo
(gdb) break procesar
(gdb) run
(gdb) bt # backtrace: marcos de activación
(gdb) info locals # variables locales del marco actual
(gdb) print buf # valor del puntero
(gdb) x/16bx buf # contenido del bloque del heap
$ valgrind --leak-check=full ./ejemplo
==...== HEAP SUMMARY:
==...== in use at exit: 32 bytes in 1 blocks
==...== total heap usage: 4 allocs, 3 frees, 80 bytes allocated
==...== LEAK SUMMARY:
==...== definitely lost: 32 bytes in 1 blocks==...== definitely lost: 32 bytes in 1 blocks
gdb btmuestra los marcos de activación (la pila).info localsmuestra las variables locales del marco actual.x/16bx bufmuestra el contenido del bloque del heap.- Valgrind confirma la fuga de 32 bytes al final.
9. Resumen
- La pila crece con las llamadas a funciones y decrece al retornar; el heap acumula bloques hasta que se liberan.
- Cada llamada a función crea un marco de activación; cada malloc crea un bloque en el heap.
- Las variables locales viven en la pila; los bloques dinámicos en el heap.
- El programa de ejemplo tiene una fuga de 32 bytes al final (bloque no liberado).
- GDB y Valgrind permiten inspeccionar la pila y el heap reales.
10. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Qué se crea en la pila al llamar a una función?
Un marco de activación con la dirección de retorno, los parámetros, las variables locales y los registros salvados.
2. ¿Qué se crea en el heap al llamar a malloc?
Un bloque de memoria dinámica del tamaño pedido, precedido por metadatos del asignador.
3. ¿Qué ocurre con la pila al retornar de una función?
El marco se destruye y el puntero de pila se restaura, reutilizando el espacio.
4. ¿Qué ocurre con el heap al llamar a free?
El bloque se marca como libre y pasa a la lista de reutilización del asignador.
5. ¿Por qué el programa de ejemplo tiene una fuga?
Porque el bloque de 32 bytes pedido al final (g = malloc(32)) nunca se libera con free.
6. ¿Qué muestra gdb con bt?
El backtrace: la lista de marcos de activación desde la función actual hasta main.
7. ¿Qué muestra Valgrind al final de la ejecución?
El resumen del heap: bytes en uso, número de asignaciones y liberaciones, y fugas detectadas.
8. ¿Qué limitación tiene el simulador de este tema?
No modela los metadatos del asignador, la reutilización real de bloques ni la concurrencia.
Para practicar
- Ejecuta el programa de ejemplo con GDB y observa cómo crece la pila en cada llamada a procesar.
- Modifica el programa para liberar el bloque final y verifica con Valgrind que no hay fugas.
- Añade una variable local grande en procesar y observa cómo cambia el tamaño del marco.
- Usa
x/16bx bufen GDB para ver el contenido del bloque antes y después de escribir. - Compila con
-fno-omit-frame-pointery observa cómo cambia el backtrace en GDB. - Modifica el simulador para añadir una operación de realloc y observa el efecto.
- Compara el número de allocs y frees reportados por Valgrind con los del programa.