Conceptos de direcciones y memoria en C++
Aprende los conceptos básicos de direcciones y memoria en C++, incluyendo almacenamiento de datos y direcciones de variables.
En C++, los datos se almacenan en la memoria (RAM) del ordenador. Cada variable tiene su propia dirección en la memoria. Comprender las direcciones de memoria es fundamental para dominar características avanzadas de C++ como los punteros. En este artículo aprenderás la estructura de la memoria, el concepto de dirección y cómo se almacenan las variables en la memoria.
1. ¿Qué es la memoria?
La memoria (RAM) es el espacio donde se almacenan temporalmente los datos y las instrucciones mientras se ejecuta un programa. Cuando el programa finaliza, los datos almacenados en la memoria se eliminan. El compilador de C++ reserva espacio en la memoria para las variables y les asigna direcciones.
Por ejemplo, en el siguiente código, cada variable tiene su propia dirección de memoria:
#include <iostream>
using namespace std;
int main() {
int edad = 25;
double pi = 3.14;
cout << "Dirección de edad: " << &edad << endl;
cout << "Dirección de pi: " << &pi << endl;
return 0;
}
Ejemplo de salida:
Dirección de edad: 0x61ff0c
Dirección de pi: 0x61ff08
Cada dirección representa una ubicación física en la memoria y generalmente se muestra en formato **hexadecimal**.
2. Operador & (Dirección)
El operador & devuelve la dirección de una variable —
es decir, la ubicación de memoria donde se almacena su valor.
int numero = 100;
cout << "Dirección de numero: " << &numero << endl;
cout << "Valor de numero: " << numero << endl;
La dirección es una ubicación, mientras que el valor es el dato almacenado en esa ubicación. Esta distinción es clave para entender los punteros.
3. Estructura de la Memoria (Stack y Heap)
Cuando se ejecuta un programa en C++, la memoria generalmente se divide en dos áreas principales:
| Región | Descripción | Uso |
|---|---|---|
| Stack | Almacena las variables automáticas (locales). La memoria se reserva cuando se llama a una función y se libera automáticamente cuando esta finaliza. | Variables con duración automática (int, double, etc.) |
| Heap | Se utiliza para la memoria asignada dinámicamente. Se administra manualmente mediante new y delete. |
Estructuras de datos dinámicas (objetos creados con new) |
Nota de rendimiento: El Stack es extremadamente rápido porque reservar memoria consiste simplemente en mover un puntero interno. El Heap, en cambio, es más lento, ya que el sistema operativo debe buscar y reservar un bloque de memoria libre que se ajuste al tamaño solicitado.
Ejemplo:
int x = 10; // Se almacena en el Stack (rápido y automático)
int *p = new int(5); // Se almacena en el Heap (más lento, requiere liberación manual)
Es importante observar que el puntero p se almacena en el Stack,
mientras que el valor al que apunta se encuentra en el Heap.
Comprender esta diferencia es fundamental para evitar las fugas de memoria (memory leaks).
4. Operador sizeof
El operador sizeof devuelve el tamaño en bytes que ocupa una variable o tipo de dato en la memoria.
#include <iostream>
using namespace std;
int main() {
cout << "Tamaño de int: " << sizeof(int) << " bytes" << endl;
cout << "Tamaño de double: " << sizeof(double) << " bytes" << endl;
cout << "Tamaño de char: " << sizeof(char) << " bytes" << endl;
return 0;
}
Valores típicos en la mayoría de los sistemas:
int→ 4 bytesdouble→ 8 byteschar→ 1 byte
5. Visualización de variables en memoria
Ejemplo:
int a = 5;
int b = 10;
Representación en memoria:
Dirección Valor
0x61ff0c → 5 (a)
0x61ff08 → 10 (b)
Cada variable tiene una dirección diferente. Estas direcciones pueden aumentar o disminuir según la disposición de la memoria.
6. Introducción a los punteros
Un puntero es una variable especial que almacena la dirección de otra variable.
Se utiliza el operador * (asterisco) para declarar punteros.
#include <iostream>
using namespace std;
int main() {
int numero = 42;
int *ptr = № // La dirección de numero se asigna al puntero
cout << "Valor de numero: " << numero << endl;
cout << "Valor apuntado por ptr: " << *ptr << endl;
cout << "Dirección almacenada en ptr: " << ptr << endl;
return 0;
}
&→ operador de dirección (obtiene la dirección de una variable)*→ operador de desreferencia (accede al valor almacenado en la dirección)
En este ejemplo, ptr almacena la dirección de numero.
Usando *ptr, se accede al valor almacenado en esa dirección.
7. Puntero nulo y uso seguro
Los punteros no deben apuntar a direcciones inválidas.
Si aún no se ha asignado una dirección, deben inicializarse con nullptr.
int *p = nullptr;
if (p == nullptr) {
cout << "El puntero no apunta a ninguna dirección." << endl;
}
Esta verificación ayuda a evitar errores en tiempo de ejecución y fallos de segmentación.
8. Diagrama de punteros y memoria
Ejemplo:
int x = 10;
int *p = &x;
Vista de la memoria:
Variable | Dirección | Valor
x | 0x61ff0c | 10
p | 0x61ff08 | 0x61ff0c
El puntero p almacena la dirección de x.
Usando *p, se puede acceder al valor de x.
9. Gestión de Memoria y Seguridad
En C++, la gestión directa de la memoria ofrece una gran flexibilidad, pero también implica una gran responsabilidad. Toda memoria asignada dinámicamente debe liberarse mediante delete.
int *p = new int(100); // Reservar memoria en el heap
cout << *p << endl;
delete p; // Liberar la memoria
p = nullptr; // Evitar un puntero colgante (dangling pointer)
Una fuga de memoria (memory leak) ocurre cuando la memoria asignada no se libera correctamente.
Esto puede hacer que el programa consuma cada vez más memoria y reduzca su rendimiento con el tiempo.
Para evitar este problema, cada new debe ir acompañado de un delete.
Comprender los punteros sin procesar (raw pointers) y la gestión manual de la memoria es fundamental para aprender C++.
Sin embargo, el C++ moderno (C++11 y versiones posteriores) desaconseja su uso en código de producción.
En su lugar, la biblioteca estándar ofrece Smart Pointers
(como std::unique_ptr y std::shared_ptr mediante el encabezado
<memory>). Estos administran automáticamente la liberación de memoria,
eliminando prácticamente las fugas de memoria y los problemas de punteros colgantes.
10. TL;DR
- Cada variable tiene una dirección en memoria (
¶ obtenerla). - Stack → memoria automática; Heap → memoria dinámica.
sizeofmuestra cuánto espacio ocupa un tipo.int *pdeclara un puntero;*paccede al valor.nullptrdebe usarse como valor inicial seguro.- La memoria asignada con
newdebe liberarse condelete. - Todos los ejemplos se pueden ejecutar en Visual Studio 2022 o GCC 11+.