Stack & Heap — Guia

Anatomia de la memoria: donde viven tus datos y por que importa
Abrir simulacion
Stack = automatico, Heap = manual
El modelo de memoria

La memoria de un programa es un espacio finito dividido en regiones. El Stack maneja la ejecucion: llamadas, retornos, variables locales. El Heap almacena datos dinamicos que sobreviven a las funciones.

Stack: LIFO Automatico Heap: Dinamico Manual/GC

Stack (Pila)

Crece hacia abajo. Cada llamada a funcion crea un frame con variables locales y direccion de retorno. Al terminar la funcion, el frame desaparece automaticamente. Rapido, predecible, limitado.

Heap (Monton)

Crece hacia arriba. Tu pides memoria explicitamente (malloc, new) y la liberas manualmente (free, delete) o via garbage collector. Flexible, lento, propenso a errores.

Stack Frames: la anatomia de una llamada

Cada llamada a funcion crea un stack frame:

  • Return address: donde continuar al terminar
  • Base pointer: enlace al frame anterior
  • Argumentos: parametros pasados a la funcion
  • Variables locales: los datos de esta funcion

LIFO (Last In, First Out): el ultimo frame creado es el primero en destruirse. Esta propiedad hace que la recursion funcione y que la memoria se gestione automaticamente.

Heap Allocation: libertad con responsabilidad

El heap es necesario cuando:

  • El tamano no se conoce en compilacion
  • Los datos deben sobrevivir a la funcion
  • Los datos son demasiado grandes para el stack
  • Necesitas compartir datos entre funciones

Peligros: Memory leaks (olvidar free), dangling pointers (usar memoria liberada), double free (liberar dos veces), fragmentacion.

Punteros: el puente entre mundos

Un puntero es una variable que guarda una direccion de memoria:

int* p = malloc(sizeof(int));

p vive en el Stack (8 bytes, la direccion)
*p vive en el Heap (4 bytes, el entero)

Los punteros son poderosos pero peligrosos. Un puntero incorrecto puede leer/escribir cualquier parte de la memoria, causando crashes o vulnerabilidades de seguridad.

El zoo de bugs de memoria

  • Stack Overflow: recursion demasiado profunda
  • Memory Leak: heap nunca liberado
  • Dangling Pointer: usar despues de free
  • Double Free: liberar dos veces
  • Buffer Overflow: escribir mas alla del limite
  • Use After Free: acceder a memoria liberada
Causa de la mayoria de vulnerabilidades de seguridad
Laboratorio

Experimentos guiados

Cada experimento revela como funciona la memoria en tiempo de ejecucion.

1

Visualizar la recursion

Hipotesis: Cada llamada recursiva crea un nuevo frame en el stack, y los frames se destruyen en orden inverso al retornar.

  1. Ejecuta una funcion factorial(5)
  2. Observa como el stack crece con cada llamada
  3. Cuenta los frames: deberia haber 5 (o 6 con main)
  4. Nota que cada frame tiene su propia copia de "n"
  5. Observa los frames desaparecer al retornar

La recursion funciona porque el stack mantiene el contexto de cada llamada separado. factorial(3) no interfiere con factorial(5) porque viven en frames distintos.

Cada frame = una instancia de la funcion
2

Provocar Stack Overflow

Hipotesis: Una recursion sin caso base agotara el stack, causando un crash cuando el stack colisione con el heap.

  1. Crea una funcion que se llame a si misma sin condicion de parada
  2. Ejecuta y observa el stack crecer rapidamente
  3. Nota cuando el stack se acerca al heap
  4. Observa el crash: "Stack Overflow"
  5. Calcula: cuantos frames cupieron?

El stack tiene un tamano maximo (tipicamente 1-8 MB). Cada frame consume espacio para variables locales, return address, etc. Recursion infinita = frames infinitos = crash garantizado.

ulimit -s muestra el limite del stack
3

Memory Leak: el heap que nunca se libera

Hipotesis: Si allocamos memoria en el heap sin liberarla, el heap crecera indefinidamente hasta agotar la memoria disponible.

  1. En un loop, haz malloc(1000) sin free
  2. Observa el heap crecer con cada iteracion
  3. El stack no crece (el puntero se sobrescribe)
  4. Los bloques previos quedan "huerfanos"
  5. Ejecuta suficientes iteraciones — que pasa?

Un memory leak clasico: el puntero se sobrescribe, perdiendo la referencia al bloque anterior. La memoria queda allocada pero inaccesible. En produccion, esto agota la RAM lentamente.

Herramientas: Valgrind, AddressSanitizer, LeakSanitizer detectan memory leaks automaticamente.

Probar en la simulacion
Conexiones con EigenLab

Organizacion de memoria en otros sistemas

El patron stack/heap aparece en contextos sorprendentes.

La celula: compartimentalizacion

Una celula organiza sus componentes en compartimentos. El citoplasma es como el heap: espacio general para organelos. El reticulo endoplasmatico procesa proteinas en capas, como el stack procesa funciones. La membrana gestiona que entra y sale, como el sistema operativo gestiona la memoria.

Simulacion relacionada: Mitosis — la celula "duplica su memoria" antes de dividirse.

Automatas finitos: estados como frames

Un DFA tiene un estado actual pero no stack. Un PDA (automata de pila) agrega un stack para recordar contexto, permitiendo reconocer lenguajes mas complejos. La jerarquia de Chomsky refleja el poder que da la memoria.

Simulacion relacionada: Finite Automata — comparar DFA (sin stack) vs lo que podria un PDA.

Estratigrafia: capas de tiempo

Las capas geologicas se depositan como un stack: la mas reciente arriba. Los fosiles en capas profundas son mas antiguos. Leer la estratigrafia es como leer un stack trace: cada capa representa un momento en el tiempo.

Simulacion relacionada: Estratigrafia — capas geologicas como "stack frames" del tiempo.

Reacciones quimicas: intermediarios

Una reaccion multi-paso tiene intermediarios que existen temporalmente, como variables locales en el stack. Los catalizadores "prestan" recursos y los recuperan, similar a como el stack presta espacio y lo recupera. El estado de transicion es el "return address" quimico.

Simulacion relacionada: Cinetica Quimica — intermediarios como "variables temporales".

Modelos alternativos

Mas alla del modelo clasico

Tradeoff eterno: Control manual (C/C++) da rendimiento maximo pero riesgo de bugs. Gestion automatica (GC, Rust) da seguridad pero con costo en rendimiento o complejidad.

Seguridad

Por que la memoria importa para la seguridad

La mayoria de exploits de bajo nivel abusan del modelo de memoria. Por eso lenguajes como Rust y tecnicas como ASLR, stack canaries, DEP existen: mitigar las consecuencias de bugs de memoria.

Preguntas para reflexionar

  1. Por que el stack crece hacia abajo y el heap hacia arriba? Que pasaria si crecieran en la misma direccion?
  2. Rust no tiene garbage collector pero previene memory leaks. Como logra esto sin overhead en runtime?
  3. Un servidor web procesa millones de requests. Como deberia manejar la memoria para evitar leaks acumulativos?
  4. Los lenguajes funcionales puros (Haskell) no tienen "variables". Tienen stack y heap? Como manejan la memoria?