Conceptos de direcciones y memoria
Cómo funcionan las direcciones de memoria: bytes, disposición y referencia con punteros.
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 C++, la memoria se divide generalmente en dos áreas principales:
| Región | Descripción | Uso |
|---|---|---|
| Stack (pila) | Almacena variables automáticas (locales). El espacio se asigna al llamar a una función y se libera cuando termina. | Variables con vida automática (int, double, etc.) |
| Heap (montón) | Área de memoria asignada dinámicamente. Se gestiona manualmente con new y delete. |
Estructuras de datos dinámicas (objetos creados con new) |
Ejemplo:
int x = 10; // Se almacena en la pila
int *p = new int(5); // Se almacena en el montón
El puntero p se encuentra en la pila, pero el valor al que apunta está en el montón.
Esta diferencia es importante para evitar 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 y seguridad de la memoria
En C++, la gestión directa de la memoria ofrece un gran poder, pero también una gran responsabilidad. Toda memoria asignada dinámicamente debe liberarse con delete.
int *p = new int(100); // Asignar memoria en el montón
cout << *p << endl;
delete p; // Liberar la memoria
p = nullptr; // Evitar puntero colgante
Una fuga de memoria ocurre cuando no se libera correctamente la memoria,
lo que puede ralentizar el programa con el tiempo.
Por eso, cada new debe ir acompañado de un delete.
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+.