这里是目录
- C++内存管理方式
- new/delete操作内置类型
- new和delete操作自定义类型
- 定位new
- 内存泄漏
前言
我们的程序当中主要有以下类型的数据(用途/存储角度):
局部数据、静态数据、全局数据、常量数据、动态申请的数据
内存布局:
C++内存管理方式
new/delete操作内置类型
C语言中动态内存管理方式:malloc/calloc/realloc/free这些在C++中同样适用,但是在C++中新增了一套全新的玩法:
通过new和delete操作符进行动态内存管理
new和delete都是操作符(最好配套使用(new/delete配套或者malloc/free配套),部分场景如果不配套使用可能会出现问题)
用法:new+类型
int main()
{
//C
int* p1 = (int*)malloc(sizeof(int));
free(p1);
//C++
int* p2 = new int;
delete p2;
//C
int* p3 = (int*)malloc(sizeof(int) * 10);
free(p3);
//C++
//申请10个int的数组
int* p4 = new int[10];
delete[] p4;
//注意:P4和P5是不一样的
//申请一个数组,初始化为10
int* p5 = new int(10);
delete p5;
//数组的初始化方式
int* p6 = new int[10] {1, 2, 3, 4, 5};
delete p6;
return 0;
}
new/malloc申请内置类型的数据除了用法,其他方面没有什么区别
new和delete操作自定义类型
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "调用构造函数" << this << endl;
}
~A()
{
cout << "调用析构函数" << this << endl;
}
private:
int _a;
};
int main()
{
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
return 0;
}
运行效果:
可以看到new会调用构造函数,并且delete会调用析构函数再释放空间
10个对象则会调用10次
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "调用构造函数 - " << this << endl;
}
~A()
{
cout << "调用析构函数 - " << this << endl;
}
private:
int _a;
};
int main()
{
A* p1 = new A[10];
cout << "=======================" << endl;
delete[] p1;
return 0;
}
其他方式
class A
{
public:
A(int a)
: _a(a)
{
cout << "调用构造函数 - " << this << endl;
}
~A()
{
cout << "调用析构函数 - " << this << endl;
}
private:
int _a;
};
int main()
{
//无默认构造的初始化方式
A* p1 = new A[4]{ 1,2,3,4 };
//或者
A* p2 = new A[4]{ A(1),A(2),A(3),A(4) };
delete[] p1;
delete[] p2;
return 0;
}
malloc/free和new/delete的区别
new/delete申请自定义类型的数据,除了用法,还有一个重大区别:new和delete会调用构造函数初始化,析构函数清理
malloc和free是函数,new和delete是操作符
malloc申请的空间不会初始化,new可以初始化
malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。
定位new
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象
使用格式:
new (place_address) type或者new (place_address)
type(initializer-list) place_address必须是一个指针,initializer-list是类型的初始化列表
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
// 定位new/replacement new
int main()
{
// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
A* p1 = (A*)malloc(sizeof(A));
//内置类型不会自动调用构造函数和析构函数
//显式调用构造函数
new(p1)A; // 注意:如果A类的构造函数有参数时,此处需要传参
p1->~A();
free(p1);
}
内存泄漏
什么是内存泄漏:
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费
内存泄露的危害:
int main()
{
char* p1 = new char[1024 * 1024 * 1024];
cout << p1 << endl;
return 0;
}
代码运行前内存占用:
代码运行后:
可以看到是申请出了1G的空间,那么我们频繁运行这段代码会造成内存泄漏吗?
实际上并不会,因为编译器会在程序结束的时候帮我们释放掉这些内存,那么什么场景下会有很大的危害呢?
如果这是一个24小时都在运行的程序(比如服务器),那就会有很大的危害,大的内存泄漏很容易查出来,小的内存泄漏则很难,比如一次泄露几个字节,每天泄露一点点久而久之服务器就会变卡,直到服务器宕机
分类:
堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak
系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定
总结
普通程序内存泄漏影响不大,进程正常结束会释放资源
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死
以上就是本篇文章的全部内容了,希望大家看完能有所收获