Cargando...

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ónDescripciónUso
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:


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 = &numero; // 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;
}

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 (& para obtenerla).
  • Stack → memoria automática; Heap → memoria dinámica.
  • sizeof muestra cuánto espacio ocupa un tipo.
  • int *p declara un puntero; *p accede al valor.
  • nullptr debe usarse como valor inicial seguro.
  • La memoria asignada con new debe liberarse con delete.
  • Todos los ejemplos se pueden ejecutar en Visual Studio 2022 o GCC 11+.

Artículos relacionados