Punteros, Heap y Stack
Entiende cómo C++ maneja la memoria con punteros, el heap y el stack
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?
- Arreglos grandes: Un arreglo de 10 millones de
inten el stack causa stack overflow. Solución: decláralo global (que va al segmento de datos, no al stack) o usanew.
// ❌ 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;
}
-
Recursión profunda: Cada llamada recursiva usa espacio en el stack. Con 100,000 llamadas, puede fallar.
-
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
| Concepto | Stack | Heap |
|---|---|---|
| Tamaño | Limitado (~1-8 MB) | Grande (GBs) |
| Velocidad | Rápido | Más lento |
| Gestión | Automática | Manual (new/delete) |
| Uso | Variables locales | Estructuras dinámicas |
| En competencias | Arreglos globales | Rara 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
*p = 20modificaa(porque p apunta a a). Ahora a = 20.p = &bcambia p para que apunte a b.*p = 30modificab(porque ahora p apunta a b). Ahora b = 30.
