C++ 中的动态内存分配

C++C++Beginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

介绍

在本实验中,你将学习如何在 C++ 中处理动态内存分配。你将从使用 new 操作符分配内存开始,然后使用 delete 操作符释放内存。你还将探索如何创建动态数组,使用智能指针如 shared_ptrunique_ptr,并检查内存泄漏。这些技能对于 C++ 编程中的有效内存管理至关重要。

本实验涵盖以下关键步骤:使用 new 操作符分配内存,使用 delete 操作符释放内存,使用 new[] 创建动态数组,使用 delete[] 删除数组,实现 shared_ptr 智能指针,使用 unique_ptr 实现独占所有权,检查内存泄漏,以及处理内存分配失败。


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{{"`C++ 中的动态内存分配`"}} cpp/classes_objects -.-> lab-446081{{"`C++ 中的动态内存分配`"}} cpp/constructors -.-> lab-446081{{"`C++ 中的动态内存分配`"}} cpp/pointers -.-> lab-446081{{"`C++ 中的动态内存分配`"}} cpp/exceptions -.-> lab-446081{{"`C++ 中的动态内存分配`"}} end

使用 new 操作符分配内存

在这一步骤中,你将学习如何在 C++ 中使用 new 操作符在运行时动态分配内存。动态内存分配允许你创建变量和数组,其大小在程序执行期间确定,相比静态内存分配提供了更大的灵活性。

打开 WebIDE,在 ~/project 目录下创建一个名为 dynamic_memory.cpp 的新文件:

touch ~/project/dynamic_memory.cpp

将以下代码添加到 dynamic_memory.cpp 中:

#include <iostream>

int main() {
    // 使用 new 动态分配一个整数
    int* dynamicInteger = new int;

    // 为动态分配的整数赋值
    *dynamicInteger = 42;

    // 打印动态分配的整数值
    std::cout << "Dynamically allocated integer value: " << *dynamicInteger << std::endl;

    // 打印动态分配整数的内存地址
    std::cout << "Memory address: " << dynamicInteger << std::endl;

    return 0;
}

让我们分解关键概念:

  1. int* dynamicInteger = new int;

    • new 操作符为整数分配内存
    • 返回指向分配内存的指针
    • 在堆(heap)上创建变量,而不是栈(stack)
  2. *dynamicInteger = 42;

    • 使用解引用操作符 * 赋值
    • 将值 42 存储在动态分配的内存中

编译并运行程序:

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

示例输出:

Dynamically allocated integer value: 42
Memory address: 0x55f4e8a042a0

关于 new 操作符的关键点:

  • 在运行时动态分配内存
  • 返回指向分配内存的指针
  • 内存分配在堆上
  • 大小可以在运行时确定
  • 允许更灵活的内存管理

注意:在接下来的步骤中,你将学习如何正确释放这些动态分配的内存,以防止内存泄漏。

使用 delete 操作符释放内存

在这一步骤中,你将学习如何使用 delete 操作符正确释放 C++ 中动态分配的内存。未能释放内存可能会导致内存泄漏,从而影响程序的性能。

打开 WebIDE,在 ~/project 目录下创建一个名为 memory_release.cpp 的新文件:

touch ~/project/memory_release.cpp

将以下代码添加到 memory_release.cpp 中:

#include <iostream>

int main() {
    // 动态分配一个整数
    int* dynamicInteger = new int;

    // 为动态分配的整数赋值
    *dynamicInteger = 42;

    // 打印释放内存前的值
    std::cout << "Value before release: " << *dynamicInteger << std::endl;

    // 使用 delete 释放动态分配的内存
    delete dynamicInteger;

    // 删除后将指针设置为 nullptr
    dynamicInteger = nullptr;

    // 演示安全的指针使用
    if (dynamicInteger == nullptr) {
        std::cout << "Memory has been successfully released" << std::endl;
    }

    return 0;
}

关于 delete 操作符的关键点:

  1. delete dynamicInteger;

    • 释放之前通过 new 分配的内存
    • 防止内存泄漏
    • 将内存释放回系统
  2. dynamicInteger = nullptr;

    • 删除后将指针设置为 nullptr
    • 防止意外使用已释放的内存
    • 提供额外的安全检查

编译并运行程序:

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

示例输出:

Value before release: 42
Memory has been successfully released

重要的内存管理规则:

  • 始终为通过 new 分配的内存使用 delete
  • 删除后将指针设置为 nullptr
  • 删除后切勿使用指针
  • 每个 new 都应有对应的 delete

使用 new[] 创建动态数组

在这一步骤中,你将学习如何在 C++ 中使用 new[] 操作符创建动态数组。动态数组允许你在运行时为多个元素分配内存,相比静态数组提供了更大的灵活性。

打开 WebIDE,在 ~/project 目录下创建一个名为 dynamic_array.cpp 的新文件:

touch ~/project/dynamic_array.cpp

将以下代码添加到 dynamic_array.cpp 中:

#include <iostream>

int main() {
    // 声明动态数组的大小
    int arraySize = 5;

    // 使用 new[] 创建一个动态整数数组
    int* dynamicArray = new int[arraySize];

    // 初始化数组元素
    for (int i = 0; i < arraySize; ++i) {
        dynamicArray[i] = i * 10;
    }

    // 打印数组元素
    std::cout << "Dynamic Array Contents:" << std::endl;
    for (int i = 0; i < arraySize; ++i) {
        std::cout << "Element " << i << ": " << dynamicArray[i] << std::endl;
    }

    return 0;
}

关于动态数组的关键点:

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

    • 为整数数组分配内存
    • arraySize 决定了元素的数量
    • 内存分配在堆上
    • 大小可以在运行时确定
  2. 数组初始化和访问:

    • 使用标准数组索引 dynamicArray[i]
    • 可以像普通数组一样操作
    • 允许根据运行时条件动态调整大小

编译并运行程序:

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

示例输出:

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

重要注意事项:

  • 动态数组提供了内存分配的灵活性
  • 大小可以在运行时确定
  • 记得使用 new[] 进行分配,使用 delete[] 进行释放
  • 始终匹配分配和释放方法

使用 delete[] 删除数组

在这一步骤中,你将学习如何使用 delete[] 操作符正确释放为动态数组分配的内存。正确的内存管理对于防止内存泄漏和确保资源的高效利用至关重要。

打开 WebIDE,在 ~/project 目录下创建一个名为 delete_array.cpp 的新文件:

touch ~/project/delete_array.cpp

将以下代码添加到 delete_array.cpp 中:

#include <iostream>

int main() {
    // 声明动态数组的大小
    int arraySize = 5;

    // 使用 new[] 创建一个动态整数数组
    int* dynamicArray = new int[arraySize];

    // 初始化数组元素
    for (int i = 0; i < arraySize; ++i) {
        dynamicArray[i] = i * 10;
    }

    // 打印删除前的数组元素
    std::cout << "Array Contents Before Deletion:" << std::endl;
    for (int i = 0; i < arraySize; ++i) {
        std::cout << "Element " << i << ": " << dynamicArray[i] << std::endl;
    }

    // 使用 delete[] 释放动态分配的数组
    delete[] dynamicArray;

    // 删除后将指针设置为 nullptr
    dynamicArray = nullptr;

    // 演示安全的指针使用
    if (dynamicArray == nullptr) {
        std::cout << "Dynamic array has been successfully deleted" << std::endl;
    }

    return 0;
}

关于删除动态数组的关键点:

  1. delete[] dynamicArray;

    • 正确释放整个数组的内存
    • 必须为通过 new[] 分配的数组使用 delete[]
    • 防止内存泄漏
    • 为每个数组元素调用析构函数
  2. dynamicArray = nullptr;

    • 删除后将指针设置为 nullptr
    • 防止意外访问已释放的内存
    • 提供额外的安全检查

编译并运行程序:

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

示例输出:

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

重要的内存管理规则:

  • 始终为通过 new[] 分配的数组使用 delete[]
  • 切勿混用 deletedelete[]
  • 删除后将指针设置为 nullptr
  • 每个 new[] 都应有对应的 delete[]

实现智能指针 shared_ptr

在这一步骤中,你将学习 shared_ptr,这是 C++ 标准库中的一种智能指针,提供自动内存管理和引用计数功能。智能指针有助于防止内存泄漏并简化内存管理。

打开 WebIDE,在 ~/project 目录下创建一个名为 shared_pointer.cpp 的新文件:

touch ~/project/shared_pointer.cpp

将以下代码添加到 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() {
    // 创建一个指向 MyClass 的 shared_ptr
    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(42);

    // 创建另一个指向同一对象的 shared_ptr
    std::shared_ptr<MyClass> ptr2 = ptr1;

    // 打印引用计数
    std::cout << "Reference Count: " << ptr1.use_count() << std::endl;

    // 通过 shared_ptr 访问对象
    std::cout << "Data from ptr1: " << ptr1->getData() << std::endl;
    std::cout << "Data from ptr2: " << ptr2->getData() << std::endl;

    // 当指针超出作用域时,内存会自动释放
    return 0;
}

关于 shared_ptr 的关键点:

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

    • 创建一个带有动态内存分配的共享指针
    • 使用构造函数参数初始化对象
    • 比单独的 newshared_ptr 创建更高效
  2. ptr1.use_count()

    • 返回指向同一对象的 shared_ptr 实例数量
    • 帮助跟踪对象的引用计数
  3. 自动内存管理:

    • 当没有 shared_ptr 引用对象时,内存会自动释放
    • 防止手动内存管理错误

编译并运行程序:

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

示例输出:

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

shared_ptr 的重要特性:

  • 自动内存管理
  • 引用计数
  • 防止内存泄漏
  • 线程安全的引用计数

使用 unique_ptr 实现独占所有权

在这一步骤中,你将学习 unique_ptr,这是一种提供动态分配资源独占所有权的智能指针。与 shared_ptr 不同,unique_ptr 确保同一时间只有一个指针可以拥有资源。

打开 WebIDE,在 ~/project 目录下创建一个名为 unique_pointer.cpp 的新文件:

touch ~/project/unique_pointer.cpp

将以下代码添加到 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;
    // 当函数结束时,资源会自动删除
}

int main() {
    // 创建一个 unique_ptr
    std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>(42);

    // 打印资源数据
    std::cout << "Resource value: " << ptr1->getData() << std::endl;

    // 使用 std::move 转移所有权
    std::unique_ptr<Resource> ptr2 = std::move(ptr1);

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

    // 将 unique_ptr 传递给函数(所有权被转移)
    processResource(std::move(ptr2));

    return 0;
}

关于 unique_ptr 的关键点:

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

    • 创建一个带有动态内存分配的独占指针
    • 确保资源的独占所有权
  2. std::move(ptr1)

    • 转移资源的所有权
    • 原始指针变为 nullptr
    • 防止多个指针拥有同一资源
  3. 自动内存管理:

    • unique_ptr 超出作用域时,资源会自动删除
    • 无需手动管理内存

编译并运行程序:

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

示例输出:

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

unique_ptr 的重要特性:

  • 独占所有权
  • 自动内存管理
  • 不能被复制,只能被移动
  • 防止资源泄漏

检查内存泄漏

在这一步骤中,你将学习如何使用 Valgrind 这一强大的内存调试工具来检测和防止 C++ 中的内存泄漏。当动态分配的内存未被正确释放时,就会发生内存泄漏,导致程序消耗越来越多的内存。

首先,在终端中安装 Valgrind:

sudo apt update
sudo apt install -y valgrind

在 WebIDE 中创建一个内存泄漏示例,在 ~/project 目录下创建 memory_leak.cpp 文件:

touch ~/project/memory_leak.cpp

将以下代码添加到 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() {
    // 这里会导致内存泄漏,因为资源未被删除
    Resource* leak = new Resource(42);
    // 缺失:delete leak;
}

int main() {
    // 模拟多次内存泄漏
    for (int i = 0; i < 3; ++i) {
        createMemoryLeak();
    }

    return 0;
}

使用调试符号编译程序:

g++ -g memory_leak.cpp -o memory_leak

运行 Valgrind 以检测内存泄漏:

valgrind --leak-check=full ./memory_leak

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)

如你所见,Valgrind 检测到 12 字节的内存泄漏,分布在 3 个块中。createMemoryLeak() 中创建的 Resource 对象未被删除,导致了内存泄漏。

要修复内存泄漏,修改代码以正确删除资源:

void createMemoryLeak() {
    // 正确删除动态分配的资源
    Resource* leak = new Resource(42);
    delete leak;  // 添加此行以防止内存泄漏
}

重新编译并使用 Valgrind 运行程序,以验证内存泄漏已修复:

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)

关于内存泄漏的关键点:

  • 始终 delete 动态分配的内存
  • 使用智能指针如 unique_ptrshared_ptr
  • 使用 Valgrind 检测内存泄漏
  • 使用 -g 标志编译以获得更好的调试信息

处理内存分配失败

在这一步骤中,你将学习如何在 C++ 中处理内存分配失败。当动态内存分配失败时,new 操作符会抛出 std::bad_alloc 异常,你可以捕获并优雅地处理该异常。

打开 WebIDE,在 ~/project 目录下创建一个名为 memory_allocation.cpp 的新文件:

touch ~/project/memory_allocation.cpp

将以下代码添加到 memory_allocation.cpp 中:

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

void demonstrateMemoryAllocation() {
    try {
        // 尝试分配大量内存
        const size_t largeSize = 1000000000000; // 1 万亿个整数
        int* largeArray = new int[largeSize];

        // 如果分配失败,此行将不会执行
        std::cout << "Memory allocation successful" << std::endl;

        // 清理分配的内存
        delete[] largeArray;
    }
    catch (const std::bad_alloc& e) {
        // 处理内存分配失败
        std::cerr << "Memory allocation failed: " << e.what() << std::endl;
    }
}

void safeMemoryAllocation() {
    // 使用 std::nothrow 防止抛出异常
    int* safeArray = new(std::nothrow) int[1000000];

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

    // 使用分配的内存
    std::cout << "Safe memory allocation successful" << std::endl;

    // 清理分配的内存
    delete[] safeArray;
}

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

    // 方法 1:使用异常处理
    demonstrateMemoryAllocation();

    // 方法 2:使用 std::nothrow
    safeMemoryAllocation();

    return 0;
}

关于内存分配失败处理的关键点:

  1. 使用 std::bad_alloc 进行异常处理:

    • try-catch 块捕获内存分配异常
    • 提供详细的错误信息
    • 防止程序崩溃
  2. std::nothrow 分配:

    • 防止抛出异常
    • 在分配失败时返回 nullptr
    • 允许静默处理失败

编译并运行程序:

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

示例输出:

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

重要的内存分配策略:

  • 始终检查分配失败
  • 使用异常处理或 std::nothrow
  • 实现备用机制
  • 避免分配极大的内存块

总结

在本实验中,你将学习如何在 C++ 中处理动态内存分配。首先,你将学习使用 new 操作符在运行时动态分配内存,相比静态分配,这提供了更灵活的内存管理。接着,你将学习如何使用 delete 操作符正确释放这些动态分配的内存,以防止内存泄漏。此外,你还将探索如何使用 new[] 创建动态数组,并使用 delete[] 删除它们。实验还涵盖了使用智能指针(如 shared_ptrunique_ptr)来简化内存管理并避免常见问题。最后,你将学习如何检查和处理内存分配失败的技术。

您可能感兴趣的其他 C++ 教程