Punteros y Memoriac++punterosmemoriaheapstacknewdelete

Punteros, Heap y Stack

Entiende cómo C++ maneja la memoria con punteros, el heap y el stack

OOI Oaxaca9 de febrero de 20264 min read

La analogía: direcciones postales

Imagina que la memoria de tu computadora es una calle con casas numeradas. Cada casa (byte) tiene una dirección. Un puntero es como un papel donde anotas la dirección de una casa. No contiene el objeto en sí, sino dónde encontrarlo.

¿Qué es un puntero?

Un puntero es una variable que almacena la dirección de memoria de otra variable.

int x = 42;
int* p = &x;   // p guarda la dirección de x

cout << x << endl;    // 42 (el valor)
cout << &x << endl;   // 0x7fff... (la dirección)
cout << p << endl;     // 0x7fff... (la misma dirección)
cout << *p << endl;    // 42 (el valor al que apunta p)
  • &x = "dame la dirección de x"
  • *p = "dame el valor en la dirección que guarda p" (desreferenciar)

Stack vs Heap

Tu programa tiene dos zonas de memoria principales:

Stack (pila de ejecución)

  • Variables locales y parámetros de funciones.
  • Se asigna y libera automáticamente.
  • Muy rápido, pero limitado (~1-8 MB).
  • Las variables se destruyen al salir del bloque.
void funcion() {
    int x = 10;       // x vive en el stack
    int arr[100];      // arreglo en el stack
}  // x y arr se destruyen aquí automáticamente

Heap (memoria dinámica)

  • Memoria que tú solicitas con new.
  • Tú la controlas: la pides y debes liberarla con delete.
  • Mucho más grande (GBs disponibles).
  • Más lento que el stack.
void funcion() {
    int* p = new int(42);      // 42 se almacena en el heap
    int* arr = new int[1000000]; // arreglo grande en el heap

    // Usar...

    delete p;        // Liberar un solo elemento
    delete[] arr;    // Liberar un arreglo
}
⚠️

Si haces new sin delete, tienes una fuga de memoria (memory leak). El programa consume más y más memoria hasta que se queda sin ella.

¿Cuándo importa esto en competencias?

  1. Arreglos grandes: Un arreglo de 10 millones de int en el stack causa stack overflow. Solución: decláralo global (que va al segmento de datos, no al stack) o usa new.
// ❌ Stack overflow si está dentro de main
int main() {
    int arr[10000000];  // 40 MB en el stack → crash
}

// ✅ Variable global (no usa el stack)
int arr[10000000];
int main() {
    // Funciona bien
}

// ✅ Heap (memoria dinámica)
int main() {
    int* arr = new int[10000000];
    delete[] arr;
}
  1. Recursión profunda: Cada llamada recursiva usa espacio en el stack. Con 100,000 llamadas, puede fallar.

  2. Listas enlazadas y árboles: Cada nodo se crea con new.

Punteros y arreglos

En C++, el nombre de un arreglo es esencialmente un puntero:

int arr[5] = {10, 20, 30, 40, 50};

cout << arr[2] << endl;      // 30
cout << *(arr + 2) << endl;  // 30 (aritmética de punteros)

int* p = arr;         // p apunta al primer elemento
cout << p[3] << endl; // 40 (se puede indexar como arreglo)

Nullptr

Un puntero que no apunta a nada:

int* p = nullptr;

if (p == nullptr) {
    cout << "El puntero no apunta a nada" << endl;
}

// NUNCA desreferencies un nullptr:
// cout << *p;  // ¡CRASH! Segmentation fault

Resumen práctico

ConceptoStackHeap
TamañoLimitado (~1-8 MB)Grande (GBs)
VelocidadRápidoMás lento
GestiónAutomáticaManual (new/delete)
UsoVariables localesEstructuras dinámicas
En competenciasArreglos globalesRara vez explícito
💡

Consejo práctico: En competencias, la forma más fácil de evitar problemas de memoria es declarar arreglos grandes como variables globales. No necesitas new/delete en la mayoría de los casos.

Ejercicio mental

¿Qué imprime este código?

int a = 5;
int b = 10;
int* p = &a;

*p = 20;
p = &b;
*p = 30;

cout << a << " " << b << endl;
Ver respuesta
20 30
  1. *p = 20 modifica a (porque p apunta a a). Ahora a = 20.
  2. p = &b cambia p para que apunte a b.
  3. *p = 30 modifica b (porque ahora p apunta a b). Ahora b = 30.