Asignación dinámica de memoria en C++

C++C++Beginner
Practicar Ahora

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

En este laboratorio, aprenderás cómo manejar la asignación dinámica de memoria en C++. Comenzarás asignando memoria utilizando el operador new, luego liberarás la memoria con el operador delete. También explorarás la creación de arreglos dinámicos, el uso de punteros inteligentes como shared_ptr y unique_ptr, y la comprobación de fugas de memoria. Estas habilidades son esenciales para una gestión efectiva de la memoria en la programación en C++.

El laboratorio cubre los siguientes pasos clave: asignar memoria utilizando el operador new, liberar memoria con el operador delete, crear arreglos dinámicos con new[], eliminar arreglos utilizando delete[], implementar el puntero inteligente shared_ptr, usar unique_ptr para la propiedad exclusiva, comprobar las fugas de memoria y manejar los fallos de asignación de memoria.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL cpp(("C++")) -.-> cpp/BasicsGroup(["Basics"]) cpp(("C++")) -.-> cpp/OOPGroup(["OOP"]) cpp(("C++")) -.-> cpp/AdvancedConceptsGroup(["Advanced Concepts"]) cpp/BasicsGroup -.-> cpp/arrays("Arrays") cpp/OOPGroup -.-> cpp/classes_objects("Classes/Objects") cpp/OOPGroup -.-> cpp/constructors("Constructors") cpp/AdvancedConceptsGroup -.-> cpp/pointers("Pointers") cpp/AdvancedConceptsGroup -.-> cpp/exceptions("Exceptions") subgraph Lab Skills cpp/arrays -.-> lab-446081{{"Asignación dinámica de memoria en C++"}} cpp/classes_objects -.-> lab-446081{{"Asignación dinámica de memoria en C++"}} cpp/constructors -.-> lab-446081{{"Asignación dinámica de memoria en C++"}} cpp/pointers -.-> lab-446081{{"Asignación dinámica de memoria en C++"}} cpp/exceptions -.-> lab-446081{{"Asignación dinámica de memoria en C++"}} end

Asignar memoria utilizando el operador new

En este paso, aprenderás cómo usar el operador new en C++ para asignar memoria dinámicamente en tiempo de ejecución. La asignación dinámica de memoria te permite crear variables y arreglos cuyo tamaño se determina durante la ejecución del programa, lo que brinda más flexibilidad en comparación con la asignación estática de memoria.

Abre el WebIDE y crea un nuevo archivo llamado dynamic_memory.cpp en el directorio ~/project:

touch ~/project/dynamic_memory.cpp

Agrega el siguiente código a dynamic_memory.cpp:

#include <iostream>

int main() {
    // Dynamically allocate an integer using new
    int* dynamicInteger = new int;

    // Assign a value to the dynamically allocated integer
    *dynamicInteger = 42;

    // Print the value of the dynamically allocated integer
    std::cout << "Dynamically allocated integer value: " << *dynamicInteger << std::endl;

    // Print the memory address of the dynamically allocated integer
    std::cout << "Memory address: " << dynamicInteger << std::endl;

    return 0;
}

Desglosemos los conceptos clave:

  1. int* dynamicInteger = new int;:

    • El operador new asigna memoria para un entero
    • Devuelve un puntero a la memoria asignada
    • Crea la variable en el heap (montón), no en el stack (pila)
  2. *dynamicInteger = 42;:

    • Utiliza el operador de desreferenciación * para asignar un valor
    • Almacena el valor 42 en la memoria asignada dinámicamente

Compila y ejecuta el programa:

g++ dynamic_memory.cpp -o dynamic_memory
./dynamic_memory

Ejemplo de salida:

Dynamically allocated integer value: 42
Memory address: 0x55f4e8a042a0

Puntos clave sobre el operador new:

  • Asigna memoria dinámicamente en tiempo de ejecución
  • Devuelve un puntero a la memoria asignada
  • La memoria se asigna en el heap
  • El tamaño se puede determinar en tiempo de ejecución
  • Permite una gestión de memoria más flexible

Nota: En los siguientes pasos, aprenderás cómo liberar adecuadamente esta memoria asignada dinámicamente para evitar fugas de memoria.

Liberar memoria con el operador delete

En este paso, aprenderás cómo usar el operador delete para liberar adecuadamente la memoria asignada dinámicamente en C++. No liberar la memoria puede provocar fugas de memoria, lo que puede causar problemas de rendimiento en tu programa.

Abre el WebIDE y crea un nuevo archivo llamado memory_release.cpp en el directorio ~/project:

touch ~/project/memory_release.cpp

Agrega el siguiente código a memory_release.cpp:

#include <iostream>

int main() {
    // Dynamically allocate an integer
    int* dynamicInteger = new int;

    // Assign a value to the dynamically allocated integer
    *dynamicInteger = 42;

    // Print the value before releasing memory
    std::cout << "Value before release: " << *dynamicInteger << std::endl;

    // Release the dynamically allocated memory using delete
    delete dynamicInteger;

    // Set the pointer to nullptr after deletion
    dynamicInteger = nullptr;

    // Demonstrate safe pointer usage
    if (dynamicInteger == nullptr) {
        std::cout << "Memory has been successfully released" << std::endl;
    }

    return 0;
}

Puntos clave sobre el operador delete:

  1. delete dynamicInteger;:

    • Libera la memoria previamente asignada con new
    • Previene fugas de memoria
    • Devuelve la memoria al sistema
  2. dynamicInteger = nullptr;:

    • Establece el puntero a nullptr después de la eliminación
    • Ayuda a prevenir el uso accidental de memoria liberada
    • Proporciona una comprobación de seguridad adicional

Compila y ejecuta el programa:

g++ memory_release.cpp -o memory_release
./memory_release

Ejemplo de salida:

Value before release: 42
Memory has been successfully released

Reglas importantes de gestión de memoria:

  • Siempre usa delete para la memoria asignada con new
  • Establece los punteros a nullptr después de la eliminación
  • Nunca uses un puntero después de que haya sido eliminado
  • Cada new debe tener un delete correspondiente

Crear arreglos dinámicos con new[]

En este paso, aprenderás cómo crear arreglos dinámicos utilizando el operador new[] en C++. Los arreglos dinámicos te permiten asignar memoria para múltiples elementos en tiempo de ejecución, lo que brinda más flexibilidad que los arreglos estáticos.

Abre el WebIDE y crea un nuevo archivo llamado dynamic_array.cpp en el directorio ~/project:

touch ~/project/dynamic_array.cpp

Agrega el siguiente código a dynamic_array.cpp:

#include <iostream>

int main() {
    // Declare the size of the dynamic array
    int arraySize = 5;

    // Create a dynamic integer array using new[]
    int* dynamicArray = new int[arraySize];

    // Initialize array elements
    for (int i = 0; i < arraySize; ++i) {
        dynamicArray[i] = i * 10;
    }

    // Print array elements
    std::cout << "Dynamic Array Contents:" << std::endl;
    for (int i = 0; i < arraySize; ++i) {
        std::cout << "Element " << i << ": " << dynamicArray[i] << std::endl;
    }

    return 0;
}

Puntos clave sobre los arreglos dinámicos:

  1. int* dynamicArray = new int[arraySize];:

    • Asigna memoria para un arreglo de enteros
    • arraySize determina el número de elementos
    • La memoria se asigna en el heap (montón)
    • El tamaño se puede determinar en tiempo de ejecución
  2. Inicialización y acceso al arreglo:

    • Utiliza la indexación estándar de arreglos dynamicArray[i]
    • Puede manipularse como un arreglo normal
    • Permite un tamaño dinámico basado en condiciones de tiempo de ejecución

Compila y ejecuta el programa:

g++ dynamic_array.cpp -o dynamic_array
./dynamic_array

Ejemplo de salida:

Dynamic Array Contents:
Element 0: 0
Element 1: 10
Element 2: 20
Element 3: 30
Element 4: 40

Consideraciones importantes:

  • Los arreglos dinámicos brindan flexibilidad en la asignación de memoria
  • El tamaño se puede determinar en tiempo de ejecución
  • Recuerda usar new[] para la asignación y delete[] para la desasignación
  • Siempre coincide los métodos de asignación y desasignación

Eliminar arreglos utilizando delete[]

En este paso, aprenderás cómo liberar adecuadamente la memoria asignada para arreglos dinámicos utilizando el operador delete[]. Una gestión adecuada de la memoria es crucial para prevenir fugas de memoria y garantizar una utilización eficiente de los recursos.

Abre el WebIDE y crea un nuevo archivo llamado delete_array.cpp en el directorio ~/project:

touch ~/project/delete_array.cpp

Agrega el siguiente código a delete_array.cpp:

#include <iostream>

int main() {
    // Declare the size of the dynamic array
    int arraySize = 5;

    // Create a dynamic integer array using new[]
    int* dynamicArray = new int[arraySize];

    // Initialize array elements
    for (int i = 0; i < arraySize; ++i) {
        dynamicArray[i] = i * 10;
    }

    // Print array elements before deletion
    std::cout << "Array Contents Before Deletion:" << std::endl;
    for (int i = 0; i < arraySize; ++i) {
        std::cout << "Element " << i << ": " << dynamicArray[i] << std::endl;
    }

    // Release the dynamically allocated array using delete[]
    delete[] dynamicArray;

    // Set the pointer to nullptr after deletion
    dynamicArray = nullptr;

    // Demonstrate safe pointer usage
    if (dynamicArray == nullptr) {
        std::cout << "Dynamic array has been successfully deleted" << std::endl;
    }

    return 0;
}

Puntos clave sobre la eliminación de arreglos dinámicos:

  1. delete[] dynamicArray;:

    • Libera adecuadamente la memoria para todo el arreglo
    • Debe utilizarse delete[] para arreglos asignados con new[]
    • Previene fugas de memoria
    • Llama al destructor para cada elemento del arreglo
  2. dynamicArray = nullptr;:

    • Establece el puntero a nullptr después de la eliminación
    • Previene el acceso accidental a memoria liberada
    • Proporciona una comprobación de seguridad adicional

Compila y ejecuta el programa:

g++ delete_array.cpp -o delete_array
./delete_array

Ejemplo de salida:

Array Contents Before Deletion:
Element 0: 0
Element 1: 10
Element 2: 20
Element 3: 30
Element 4: 40
Dynamic array has been successfully deleted

Reglas importantes de gestión de memoria:

  • Siempre utiliza delete[] para arreglos asignados con new[]
  • Nunca mezcles delete y delete[]
  • Establece los punteros a nullptr después de la eliminación
  • Cada new[] debe tener un delete[] correspondiente

Implementar el puntero inteligente shared_ptr

En este paso, aprenderás sobre shared_ptr, un puntero inteligente de la Biblioteca Estándar de C++ que proporciona gestión automática de memoria y conteo de referencias. Los punteros inteligentes ayudan a prevenir fugas de memoria y simplifican la gestión de memoria.

Abre el WebIDE y crea un nuevo archivo llamado shared_pointer.cpp en el directorio ~/project:

touch ~/project/shared_pointer.cpp

Agrega el siguiente código a shared_pointer.cpp:

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass(int value) : data(value) {
        std::cout << "Constructor called. Value: " << data << std::endl;
    }

    ~MyClass() {
        std::cout << "Destructor called. Value: " << data << std::endl;
    }

    int getData() const { return data; }

private:
    int data;
};

int main() {
    // Create a shared_ptr to MyClass
    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(42);

    // Create another shared_ptr pointing to the same object
    std::shared_ptr<MyClass> ptr2 = ptr1;

    // Print reference count
    std::cout << "Reference Count: " << ptr1.use_count() << std::endl;

    // Access object through shared_ptr
    std::cout << "Data from ptr1: " << ptr1->getData() << std::endl;
    std::cout << "Data from ptr2: " << ptr2->getData() << std::endl;

    // Pointers will automatically free memory when they go out of scope
    return 0;
}

Puntos clave sobre shared_ptr:

  1. std::make_shared<MyClass>(42):

    • Crea un puntero compartido con asignación dinámica de memoria
    • Inicializa el objeto con un argumento del constructor
    • Es más eficiente que la creación separada de new y shared_ptr
  2. ptr1.use_count():

    • Devuelve el número de instancias de shared_ptr que apuntan al mismo objeto
    • Ayuda a realizar un seguimiento del conteo de referencias del objeto
  3. Gestión automática de memoria:

    • La memoria se libera automáticamente cuando ningún shared_ptr hace referencia al objeto
    • Previene errores de gestión manual de memoria

Compila y ejecuta el programa:

g++ -std=c++11 shared_pointer.cpp -o shared_pointer
./shared_pointer

Ejemplo de salida:

Constructor called. Value: 42
Reference Count: 2
Data from ptr1: 42
Data from ptr2: 42
Destructor called. Value: 42

Características importantes de shared_ptr:

  • Gestión automática de memoria
  • Conteo de referencias
  • Previene fugas de memoria
  • Conteo de referencias seguro para subprocesos (thread-safe)

Utilizar unique_ptr para propiedad exclusiva

En este paso, aprenderás sobre unique_ptr, un puntero inteligente que proporciona propiedad exclusiva de recursos asignados dinámicamente. A diferencia de shared_ptr, unique_ptr asegura que solo un puntero puede ser propietario del recurso en un momento dado.

Abre el WebIDE y crea un nuevo archivo llamado unique_pointer.cpp en el directorio ~/project:

touch ~/project/unique_pointer.cpp

Agrega el siguiente código a unique_pointer.cpp:

#include <iostream>
#include <memory>

class Resource {
public:
    Resource(int value) : data(value) {
        std::cout << "Resource created. Value: " << data << std::endl;
    }

    ~Resource() {
        std::cout << "Resource destroyed. Value: " << data << std::endl;
    }

    int getData() const { return data; }

private:
    int data;
};

void processResource(std::unique_ptr<Resource> resource) {
    std::cout << "Processing resource with value: " << resource->getData() << std::endl;
    // Resource will be automatically deleted when function ends
}

int main() {
    // Create a unique_ptr
    std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>(42);

    // Print resource data
    std::cout << "Resource value: " << ptr1->getData() << std::endl;

    // Transfer ownership using std::move
    std::unique_ptr<Resource> ptr2 = std::move(ptr1);

    // ptr1 is now nullptr
    if (ptr1 == nullptr) {
        std::cout << "ptr1 is now null after moving ownership" << std::endl;
    }

    // Pass unique_ptr to a function (ownership is transferred)
    processResource(std::move(ptr2));

    return 0;
}

Puntos clave sobre unique_ptr:

  1. std::make_unique<Resource>(42):

    • Crea un puntero único con asignación dinámica de memoria
    • Asegura la propiedad exclusiva del recurso
  2. std::move(ptr1):

    • Transfiere la propiedad del recurso
    • El puntero original se convierte en nullptr
    • Previene que múltiples punteros sean propietarios del mismo recurso
  3. Gestión automática de memoria:

    • El recurso se elimina automáticamente cuando unique_ptr sale de su ámbito
    • No se requiere gestión manual de memoria

Compila y ejecuta el programa:

g++ -std=c++14 unique_pointer.cpp -o unique_pointer
./unique_pointer

Ejemplo de salida:

Resource created. Value: 42
Resource value: 42
ptr1 is now null after moving ownership
Processing resource with value: 42
Resource destroyed. Value: 42

Características importantes de unique_ptr:

  • Propiedad exclusiva
  • Gestión automática de memoria
  • No se puede copiar, solo mover
  • Previene fugas de recursos

Comprobar fugas de memoria

En este paso, aprenderás cómo detectar y prevenir fugas de memoria en C++ utilizando Valgrind, una potente herramienta de depuración de memoria. Las fugas de memoria ocurren cuando la memoria asignada dinámicamente no se libera adecuadamente, lo que hace que tu programa consuma cantidades crecientes de memoria.

Primero, instala Valgrind en la terminal:

sudo apt update
sudo apt install -y valgrind

Crea un ejemplo de fuga de memoria en el WebIDE creando memory_leak.cpp en el directorio ~/project:

touch ~/project/memory_leak.cpp

Agrega el siguiente código a memory_leak.cpp:

#include <iostream>

class Resource {
public:
    Resource(int value) : data(value) {
        std::cout << "Resource created: " << data << std::endl;
    }

    ~Resource() {
        std::cout << "Resource destroyed: " << data << std::endl;
    }

private:
    int data;
};

void createMemoryLeak() {
    // This creates a memory leak because the resource is not deleted
    Resource* leak = new Resource(42);
    // Missing: delete leak;
}

int main() {
    // Simulate multiple memory leaks
    for (int i = 0; i < 3; ++i) {
        createMemoryLeak();
    }

    return 0;
}

Compila el programa con símbolos de depuración:

g++ -g memory_leak.cpp -o memory_leak

Ejecuta Valgrind para detectar fugas de memoria:

valgrind --leak-check=full./memory_leak

Ejemplo de salida de Valgrind:

==1988== Memcheck, a memory error detector
==1988== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1988== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==1988== Command:./memory_leak
==1988==
Resource created: 42
Resource created: 42
Resource created: 42
==1988==
==1988== HEAP SUMMARY:
==1988==     in use at exit: 12 bytes in 3 blocks
==1988==   total heap usage: 5 allocs, 2 frees, 73,740 bytes allocated
==1988==
==1988== 12 bytes in 3 blocks are definitely lost in loss record 1 of 1
==1988==    at 0x4849013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==1988==    by 0x109241: createMemoryLeak() (memory_leak.cpp:19)
==1988==    by 0x109299: main (memory_leak.cpp:26)
==1988==
==1988== LEAK SUMMARY:
==1988==    definitely lost: 12 bytes in 3 blocks
==1988==    indirectly lost: 0 bytes in 0 blocks
==1988==      possibly lost: 0 bytes in 0 blocks
==1988==    still reachable: 0 bytes in 0 blocks
==1988==         suppressed: 0 bytes in 0 blocks
==1988==
==1988== For lists of detected and suppressed errors, rerun with: -s
==1988== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Como puedes ver, Valgrind detectó una fuga de memoria de 12 bytes en 3 bloques. El objeto Resource creado en createMemoryLeak() no se eliminó, lo que causó la fuga de memoria.

Para solucionar la fuga de memoria, modifica el código para eliminar adecuadamente los recursos:

void createMemoryLeak() {
    // Properly delete the dynamically allocated resource
    Resource* leak = new Resource(42);
    delete leak;  // Add this line to prevent memory leak
}

Compila y ejecuta el programa nuevamente con Valgrind para verificar que la fuga de memoria se haya solucionado:

g++ -g memory_leak.cpp -o memory_leak
valgrind --leak-check=full./memory_leak
==2347== Memcheck, a memory error detector
==2347== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2347== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==2347== Command:./memory_leak
==2347==
Resource created: 42
Resource destroyed: 42
Resource created: 42
Resource destroyed: 42
Resource created: 42
Resource destroyed: 42
==2347==
==2347== HEAP SUMMARY:
==2347==     in use at exit: 0 bytes in 0 blocks
==2347==   total heap usage: 5 allocs, 5 frees, 73,740 bytes allocated
==2347==
==2347== All heap blocks were freed -- no leaks are possible
==2347==
==2347== For lists of detected and suppressed errors, rerun with: -s
==2347== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Puntos clave sobre las fugas de memoria:

  • Siempre delete la memoria asignada dinámicamente
  • Utiliza punteros inteligentes como unique_ptr y shared_ptr
  • Utiliza Valgrind para detectar fugas de memoria
  • Compila con la bandera -g para obtener mejor información de depuración

Manejar fallos de asignación de memoria

En este paso, aprenderás cómo manejar los fallos de asignación de memoria en C++. Cuando la asignación dinámica de memoria falla, el operador new lanza una excepción std::bad_alloc, que puedes capturar y manejar de manera adecuada.

Abre el WebIDE y crea un nuevo archivo llamado memory_allocation.cpp en el directorio ~/project:

touch ~/project/memory_allocation.cpp

Agrega el siguiente código a memory_allocation.cpp:

#include <iostream>
#include <new>
#include <limits>

void demonstrateMemoryAllocation() {
    try {
        // Attempt to allocate a large amount of memory
        const size_t largeSize = 1000000000000; // 1 trillion integers
        int* largeArray = new int[largeSize];

        // This line will not be reached if allocation fails
        std::cout << "Memory allocation successful" << std::endl;

        // Clean up allocated memory
        delete[] largeArray;
    }
    catch (const std::bad_alloc& e) {
        // Handle memory allocation failure
        std::cerr << "Memory allocation failed: " << e.what() << std::endl;
    }
}

void safeMemoryAllocation() {
    // Use std::nothrow to prevent exception throwing
    int* safeArray = new(std::nothrow) int[1000000];

    if (safeArray == nullptr) {
        std::cerr << "Memory allocation failed silently" << std::endl;
        return;
    }

    // Use the allocated memory
    std::cout << "Safe memory allocation successful" << std::endl;

    // Clean up allocated memory
    delete[] safeArray;
}

int main() {
    std::cout << "Demonstrating memory allocation failure handling:" << std::endl;

    // Method 1: Using exception handling
    demonstrateMemoryAllocation();

    // Method 2: Using std::nothrow
    safeMemoryAllocation();

    return 0;
}

Puntos clave sobre el manejo de fallos de asignación de memoria:

  1. Manejo de excepciones con std::bad_alloc:

    • El bloque try-catch captura las excepciones de asignación de memoria
    • Proporciona información detallada del error
    • Previene la caída del programa
  2. Asignación con std::nothrow:

    • Previene el lanzamiento de excepciones
    • Devuelve nullptr en caso de fallo de asignación
    • Permite manejar los fallos de forma silenciosa

Compila y ejecuta el programa:

g++ memory_allocation.cpp -o memory_allocation
./memory_allocation

Ejemplo de salida:

Demonstrating memory allocation failure handling:
Memory allocation failed: std::bad_alloc
Safe memory allocation successful

Estrategias importantes de asignación de memoria:

  • Siempre verifica los fallos de asignación
  • Utiliza manejo de excepciones o std::nothrow
  • Implementa mecanismos de respaldo
  • Evita asignar bloques de memoria extremadamente grandes

Resumen

En este laboratorio, aprenderás cómo manejar la asignación dinámica de memoria en C++. Primero, aprenderás a utilizar el operador new para asignar memoria dinámicamente en tiempo de ejecución, lo que permite una gestión de memoria más flexible en comparación con la asignación estática. Luego, aprenderás cómo liberar adecuadamente esta memoria asignada dinámicamente utilizando el operador delete para prevenir fugas de memoria. Además, explorarás cómo crear matrices dinámicas con new[] y eliminarlas con delete[]. El laboratorio también cubre el uso de punteros inteligentes, como shared_ptr y unique_ptr, para simplificar la gestión de memoria y evitar errores comunes. Finalmente, aprenderás técnicas para comprobar y manejar los fallos de asignación de memoria.