C++中的内存管理(一)
文章目录
- 动态分配和初始化对象
- 内存耗尽
- 释放动态内存
- delete之后重置指针
C++定义了两个运算符来分配和释放内存。
new
分配内存,
delete
释放
new
分配的内存。值得注意的是,使用这两个指针的类通常不能依赖于默认的拷贝、赋值、析构函数。
#include <cstring>
#include <iostream>class MyString {
private:char* data;
public:MyString(const char* str = "") {data = new char[strlen(str) + 1];strcpy(data, str);}// 拷贝构造(深拷贝)MyString(const MyString& other) {data = new char[strlen(other.data) + 1];strcpy(data, other.data);}// 赋值运算符(深拷贝)MyString& operator=(const MyString& other) {if (this != &other) {delete[] data;data = new char[strlen(other.data) + 1];strcpy(data, other.data);}return *this;}// 析构函数~MyString() {delete[] data;}void print() const {std::cout << data << std::endl;}
};int main() {MyString a("Hello");MyString b = a; // 调用拷贝构造b.print();MyString c;c = a; // 调用赋值运算符c.print();
}
-
默认拷贝构造 / 赋值运算符 只是做浅拷贝,会直接复制指针值。
-
两个对象就会指向同一块堆内存,析构时会导致重复释放(double free)。
-
自己写可以确保做深拷贝,让每个对象管理自己独立的内存。
动态分配和初始化对象
在自由空间分配的内存是无名的,因此new
无法为其分配的对象命名,而是返回一个指向该对象的指针。
默认情况下,动态分配的对象是默认初始化的,这意味着内置类型或组合类型对象的值将是未定义的,而类类型对象将用默认构造函数初始化。
int *pi = new int; //指向未定义的int
string *ps = new string; //指向初始化为空的string
除了默认初始化之外,还可以通过传统的构造方式(圆括号)、列表初始化(花括号):
int *pi = new int(1024); //指向对象的值为1024
string *ps = new string(10, '9'); //指向值为“9999999999”vector<int> *pv = new vector<int>{0, 1, 2, 3, 4};
值得注意的是,传统构造初始化,若不加参数,则进行值初始化。对于定义了自己的构造函数的类类型来说,值初始化和默认初始化都是通过默认构造函数来初始化。但是对于内置类型:值初始化有着良好定义的值,而默认初始化的对象的值则是未定义的。类似的,对于类中的内置类型成员,如果没有显示初始化,而是仅使用默认构造函数的话,则它们的值也是未定义的。
- 配合auto
auto p1 = new auto(obj); //指向一个与obj相同类型的对象,该对象使用obj初始化
- 配合const
const int *pci = new const int(1024);
const string *pcs = new const string;
内存耗尽
int *p1 = new int; //如果分配失败,new抛出std::bad_alloc
int *p2 = new (nothrow) int; //如果分配失败,new返回一个空指针
释放动态内存
我们传递给delete的指针必须指向动态分配的内存,或者是一个空指针。释放一块并非new
分配的内存,或者将相同的指针释放多次,其行为是未定义的。
使用new
的动态内存管理非常容易出错:
- 忘记delete内存,造成内存泄漏;
- 使用已经释放掉的对象,悬空指针;
- 同一块内存被释放两次。
delete之后重置指针
当我们在delete一个指针之后,指针值就无效了。但在很多机器上指针依然保存着已经释放掉的动态内存地址。即成了悬空指针。虽然可以在delete之后,将其赋值为nullptr
,但这只提供了有限的保护:
int *p(new int(42)); //p指向动态内存
auto q = p; //q指向相同的动态内存
delete p; //p、q都无效
p = nullptr; //重置p
上列中并没有重置到指针q,还是容易出错。在实际系统中,查找指向相同内存的所有指针是异常困难的。因此需要更加安全的方法来使用动态内存。