C++ 动态内存管理
在 C++ 中,动态内存管理是一个核心概念,它允许在运行时分配和释放内存。以下是 C++ 动态内存管理需要掌握的关键知识点:
1. new 和 delete 操作符
在 C++ 中,new 和 delete 是用于动态内存分配和释放的基本操作符。
new 操作符
new 用于在堆(也称为自由存储区)上动态分配内存,并返回指向新分配内存的指针。new 会调用对象的构造函数来初始化对象。
基本用法
int* ptr = new int; // 分配一个 int 的内存 *ptr = 5; // 初始化该 int
初始化
new 还支持直接初始化:
int* ptr = new int(5); // 分配内存并初始化为 5
异常
如果内存分配失败(例如由于内存不足),new 将抛出 std::bad_alloc 异常。
数组分配
对于数组的分配,new 提供了特殊的语法:
int* array = new int[10]; // 分配一个包含 10 个整数的数组
delete 操作符
delete 用于释放由 new 分配的内存,并调用对象的析构函数。
基本用法
delete ptr; // 释放内存并调用析构函数
数组释放
释放由 new 分配的数组需要使用 delete[]:
delete[] array; // 释放数组内存
注意事项
避免内存泄漏:每个 new 分配的内存都应该使用对应的 delete 或 delete[] 来释放。
避免悬挂指针:释放内存后,指针变成悬挂指针。应该将指针设为 nullptr。
不要重复释放内存:对同一内存块调用两次 delete 会导致未定义行为。
数组与非数组形式要匹配:用 new[] 分配的内存必须用 delete[] 释放,用 new 分配的内存必须用 delete 释放。
替代方案
在现代 C++ 开发中,建议使用智能指针(如 std::unique_ptr 和 std::shared_ptr)来自动管理动态分配的内存,以避免手动管理内存的复杂性和风险。智能指针在其析构函数中自动释放它们所拥有的内存,从而帮助防止内存泄漏和其他常见的内存管理错误。
2. 动态分配数组
在 C++ 中,动态分配数组涉及使用 new 和 delete 操作符来在堆上分配和释放连续的内存块,用于存储数组。这种机制允许在运行时确定数组的大小,提供了比静态数组更大的灵活性。
使用 new[] 分配数组
new[] 用于动态分配一个元素数量已知的数组。
基本用法
int* myArray = new int[10]; // 分配一个包含 10 个整数的数组
在这个例子中,new[] 分配了一个包含 10 个整数的数组,并返回一个指向数组第一个元素的指针。分配的数组在堆上,其生命周期直到使用 delete[] 显式释放为止。
初始化数组
使用 new[] 分配的数组默认进行值初始化。对于内置类型,如 int,这意味着数组元素被初始化为零。对于类类型的元素,将调用默认构造函数。
使用 delete[] 释放数组
使用 delete[] 来释放由 new[] 分配的数组内存。
基本用法
delete[] myArray; // 释放数组内存
在这个例子中,delete[] 释放了之前分配的数组内存。如果数组元素是类对象,delete[] 还将调用每个对象的析构函数。
注意事项
匹配使用 new[] 和 delete[]:用 new[] 分配的内存必须用 delete[] 释放。使用不匹配的 delete 形式会导致未定义行为。
避免内存泄漏:每个通过 new[] 分配的数组都应该使用 delete[] 释放。
处理数组指针:释放内存后,指针变成悬挂指针。为了安全,应将指针设为 nullptr。
数组大小不可更改:一旦数组被分配,其大小将固定,不能更改。如果需要可调整大小的数组,可以考虑使用 std::vector。
替代方案
在现代 C++ 中,推荐使用 std::vector 或其他标准容器来替代手动管理的动态数组。标准容器提供了更安全、更方便的方式来处理动态分配的数组,自动管理内存,并提供了许多额外的功能,如自动调整大小和元素访问的边界检查。
3. 处理内存分配失败
在 C++ 中,动态内存分配可能会失败,尤其是在内存资源紧张的情况下。处理内存分配失败是动态内存管理的一个重要方面,确保程序在内存不足时能够可靠地运行。
new 的内存分配失败
当使用 new 进行内存分配时,如果系统无法满足分配请求,将抛出 std::bad_alloc 异常。这是 C++ 标准库中定义的一个异常类型,用来表示内存分配失败。
示例
try { int* myArray = new int[1000000000]; // 尝试分配大量内存 } catch (const std::bad_alloc& e) { std::cerr << "Memory allocation failed: " << e.what() << std::endl; // 处理内存分配失败的情况 }
在这个例子中,如果内存分配失败,程序会捕获 std::bad_alloc 异常,并通过 catch 块来处理这种情况。
new (nothrow) 和内存分配失败
C++ 还提供了一种不抛出异常的内存分配方式,即 new (std::nothrow)。如果使用 new (std::nothrow) 进行内存分配,当内存分配失败时,它不会抛出异常,而是返回一个空指针。
示例
#include <new> // 引入 std::nothrow int* myArray = new(std::nothrow) int[1000000000]; if (myArray == nullptr) { std::cerr << "Memory allocation failed." << std::endl; // 处理内存分配失败的情况 }
在这个例子中,如果内存分配失败,则 myArray 会被设置为 nullptr,程序可以检查这一点并相应地处理。
注意事项
- 及时处理分配失败:应检查每次 new 或 new (nothrow) 调用的返回值,并在内存分配失败时进行适当处理。
- 避免资源泄露:在处理内存分配失败时,应确保已分配资源的正确释放,避免资源泄露。
- 考虑备选方案:在内存紧张的环境中,考虑使用更小的内存请求,或使用备选的数据结构和算法。
4. 智能指针
在 C++ 中,智能指针是用于自动管理动态分配的内存的对象。智能指针通过在其析构函数中释放关联的内存,帮助避免内存泄漏和指针错误。标准库提供了几种类型的智能指针,最常用的包括 std::unique_ptr、std::shared_ptr 和 std::weak_ptr。
std::unique_ptr
std::unique_ptr 是一个独占式智能指针,它拥有其指向的对象,并在 unique_ptr 对象销毁时自动删除关联的对象。
特点
- 独占所指向的对象。
- 不允许复制,但可以移动。
- 自动释放所拥有的对象。
示例
#include <memory> std::unique_ptr<int> ptr(new int(10)); // 创建 unique_ptr
std::shared_ptr
std::shared_ptr 是一个共享式智能指针,它允许多个 shared_ptr 实例共享同一个对象。当最后一个拥有对象的 shared_ptr 被销毁时,对象被删除。
特点
- 共享对象的所有权。
- 使用引用计数来跟踪拥有对象的 shared_ptr 数量。
- 允许复制和移动。
示例
std::shared_ptr<int> shared1(new int(20)); std::shared_ptr<int> shared2 = shared1; // 共享所有权
std::weak_ptr
std::weak_ptr 是一种不拥有对象的智能指针,它被设计用来观察 std::shared_ptr,但不影响其引用计数。
特点
- 不拥有对象,不影响 shared_ptr 的引用计数。
- 用来解决 shared_ptr 相互引用导致的循环引用问题。
- 可以从 shared_ptr 或另一个 weak_ptr 创建。
示例
std::shared_ptr<int> shared(new int(30)); std::weak_ptr<int> weak = shared; // 观察 shared_ptr
管理策略
- unique_ptr 适用于资源的独占管理。
- shared_ptr 适用于资源的共享管理,需要注意循环引用问题。
- weak_ptr 用于“安全观察” shared_ptr 所管理的资源,避免循环引用。
总结
智能指针在自动管理资源方面提供了极大的方便,是现代 C++ 中管理动态资源的首选方法。使用智能指针可以减少直接使用 new 和 delete 的需要,降低内存泄漏和其他内存错误的风险。
c++动态内存管理(二)https://developer.aliyun.com/article/1437213?spm=a2c6h.13262185.profile.33.5bba685cuSQkDD