各位nb的程序员们,大家好,当我们要学一门语言并且要将它学到熟练的话,我们就不得不要去学习它的内存管理这部分的内容了,基于此处,我们这里就来学习一下C++中的内存管理方面的知识。
目录
4.operator new和operator delete函数(了解知识)
6.malloc / free和new / delete的区别
1.C/C++内存发布
我们先来看一下C/C++中空间的发布情况(用图表示更为清晰):
结合上面的那幅图,我们这里来一一解释一下各个空间:
1.1.栈
又叫堆栈——非静态局部变量/函数参数/返回值等等,栈是向下增长的(执行程序也是在栈中进行的,如下图所示)。
1.2.内存映射段
是高效的I/O映射方式,用于装载一个 共享的动态内存库。用户可使系统接口创建共享内存,做进程间通信。(后面会讲到,这里了解一下即可)
1.3.堆
用于程序运行时动态内存分布,堆是可以向上增长的。
1.4.数据段
存储全局数据和静态数据。
1.5.代码段
可执行的代码/只读常量。
2.C语言中的动态内存管理方式
2.1.malloc函数
这个函数的功能就是创建一块空间,它创建的这块空间是在堆上开创的,它开创的空间与我们在定义变量时系统为我们开创的空间不同的地方在于,它开创的空间属于是动态空间,也就是可以在这块空间的基础上还可以对这块空间再次进行扩容的操作。
大家可以通过这个链接学习一下这个malloc函数:malloc - C++ Reference
2.2.realloc函数
进行扩容操作,这个函数的主要作用就是对malloc和calloc函数所开创的空间进行扩容操作。
大家可以通过这个链接学习一下这个realloc函数:realloc - C++ Reference
2.3.calloc函数
这个函数的作用是和malloc函数的作用差不多,都是在堆上开创一块动态空间,只不过这个函数和malloc函数不同的是,malloc函数它只是开创一块空间,而calloc函数不仅仅是开创一块空间,在将这块空间开创好后,会自动将这块空间进行初始化操作,默认初始化为0。
大家可以通过这个链接学习一下这个calloc函数:calloc - C++ Reference
2.4.free函数
我们前面所讲的malloc和calloc函数,这两个函数开创好空间之后,我们在使用完这个空间之后,我们就需要将这个开创的空间释放掉,如果我们不释放掉的话,程序执行结束后,这块空间在堆中就会以一种悬空的状态仍然留在堆中,如果长期以往这样下去的话,就会造成内存泄漏的问题,系统就会崩溃,为了防止这种问题的诞生,所以我们在用完这个开创的空间之后,我们就可以使用free这个函数将这块开创的空间释放掉。
大家可以通过这个链接学习一下这个free函数:free - C++ Reference
3.C++中的内存管理方式
C语言中的内存管理方式也可以在C++中继续使用,但是在有些地方上就显得有点无能为力了,而且使用起来反而还比较麻烦,因此在C++中又提出了自己的内存管理方式:通过new和delete操作符来进行动态内存管理。
3.1.new/delete操作内置类型
new的功能类似于C语言中malloc函数的功能,从堆中申请一块空间,而delete的功能就类似于C语言中的free函数的功能,也就是归还这块空间的使用权。
#include<iostream>
using namespace std;
int main()
{
int* a1 = new int;//从堆上动态申请一个int类型的空间,用int类型的指针去a1指向这块空间。
int* a2 = new int(10);//从堆上动态申请一块int类型的空间,创建好后,将这块空间自动初始化为10,我们使用int类型的指针a2去接收它。
int* a3 = new int(20);//new是一个操作符,int是开创的空间的类型,20是初始化的值。
int* a4 = new int[10];//从堆上动态开辟10个类型为int的连续空间,开创好后并不会对其进行初始化操作。
int* a5 = new int[10] {0};//从堆上动态开辟10个类型为int的连续空间,开创好后对这10个空间均进行初始化操作,并且均初始化为0。
int* a6 = new int[10] {1, 2, 3, 4, 5, 6};//从堆上动态开辟10个类型为10个类型为int的连续空间,这块空间的前6个空间分别初始化为1,2,3,4,5,6其余的空间默认初始化为0。
delete a1;//释放我们这里所创建的空间。
delete a2;//dalete是操作符。
delete a3;
delete[] a4;//由于a4指向的是一块连续的空间,因此我们这里的delete后面要加上[],若要问为什么,就是C++语法规定的。
delete[] a5;
delete[] a6;
return 0;
}
注意:(1).我们在对a6指针指向的那块空间进行初始化操作时,我们只初始化了这块连续的空间的前6个空间,其余的空间我们C++默认初始化为0,这里的默认初始化我们需要注意一下,就是初始化这里的默认初始化为0,是在给了编译器的初始值的情况下,并且所给的初始值的个数要小于所开的空间个数的前提下,初始化后剩余的空间编译器才会默认初始化为0,如果我们这里不给初始值的话,编译器是不会对所开的空间进行初始化操作的。
(2).申请和释放单个元素的空间,我们这里使用new和delete,申请和释放连续的空间,我们这里使用new[ ]和delete[ ],一定要配套起来使用。
3.2new和delete操作自定义类型
我们这里在进行讲解操作前先来补充一个知识:new/delete和malloc/free的最大区别就是new/delete对于自定义类型它除了会进行开空间的操作以外还会自动调用这个自定义类型的构造函数和析构函数,而malloc/free只会开空间和释放相应的空间。
#include<iostream>
using namespace std;
class date
{
public:
date(int year = 1, int month = 1, int day = 1)
{
cout << "date()" << endl;
}
//编译器会在这里自动生成一个拷贝构造,我们这里就不写了。
~date()
{
cout << "~date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date* d1 = new date;//date();我们从打印结果来看的话,当我们使用new函数开创了一个date自定义类型的空间时,它确实是自动调用了date类的构造函数。
delete d1;//~date();从打印结果来看的话,我们在调用delete函数的时候,确实是会自动调用d1对象的析构函数。
date* d2 = new date[10];//这里会连续输出10行date(),因为我们在这里开创的是一块数组,这个数组中有10个空间,也就相当于是连续开创了10个date类型的空间,因此编译器在这里会调用10次构造函数。
//接下来我们来看一下这里的初始化的问题。
date* d3 = new date{ 2024,9,13 };//我们在堆上动态申请了一块的date连续的内存空间,我们对其进行进行初始化操作(换句话说,其实也就是这里的传参操作),这种初始化的方式是叫做隐式类型转换的方式,也就是说,这里编译器会根据这3个实参实现构造一个date类型的临时对象,构造好后,会调用拷贝构造函数将这个临时对象中的数据拷贝到这个我们刚刚开创的新空间中。
//构造函数的方式进行初始化(有名)
date b1(1, 2, 3);
date b2(4, 5, 6);
date b3(7, 8, 9);
date* d4 = new date[3]{ b1,b2,b3 };//这里会连续输出3行date(date& d),这里编译器其实会调用拷贝构造函数分别将b1,b2,b3这3个对象中的数据一一拷贝到刚刚开创的3个date类型的空间中。
//匿名对象进行初始化
date* d5 = new date[3]{ date(11,22,33), date(44,55,66),date(77,88,99) };
delete[10] d2;
delete d3;
delete[3] d4;
delete[3] d5;
return 0;
}
4.operator new和operator delete函数(了解知识)
new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层调用operator delete来释放空间。
operator new实际上也是通过malloc函数来申请空间的,如果malloc函数申请空间成功的话就直接返回,否则就执行用户提供的空间不足应对措施,如果用户提供该措施的话就继续申请,否则就会抛异常。operator dalete最终是通过free函数来释放空间的。
5.new和delete的实现原理
5.1内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[ ]和delete[ ]申请和释放的是连续的空间,而new在申请空间失败时会被抛异常,malloc会返回NULL。
5.2自定义类型
(1).new的原理
1>.调用operator new函数申请空间。
2>.在申请的空间上执行析构函数,完成对象的构造。
(2).delete的原理
1>.在空间上执行析构函数,完成对象中资源的清理工作。
2>.调用operator dalete函数,释放对象的空间。
(3).new [T]的原理
1>.调用operator new[ ]函数,在operator new[ ]中实际调用operator new函数完成N个对象空间的申请。
2>.在申请的空间上执行N次析构函数。
(4).delete的原理
1>.在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理。
2>.调用operator dalete[ ]释放空间,实际在operator dalete[ ]中调用operator dalete函数来释放空间。
总结:malloc函数和free函数配套使用,new和delete函数配套使用,申请和释放空间一定要配套,否则的话,会有出错的风险。
6.malloc / free和new / delete的区别
(1).共同点:都是从堆上申请空间,并且需要用户手动进行释放。
(2).不同点:1>.malloc和free是函数,new/delete是操作符。
2>.malloc申请的空间不会继续初始化操作,而new则可以自动进行初始化操作。
3>.malloc申请空间时,需要手动计算空间的大小并进行传递,new只需要在其后面跟上空间的类型即可,如果是多个对象的话,在[ ]中指定对象的个数就可以了。
4>.malloc函数的返回值未void*类型,在使用时我们必须将它强转,而new是不需要的,因为new后面跟的就是这个空间的类型。
5>.malloc申请空间失败时,返回的是NULL,因此使用时必须要判空,而new不需要,大那是new需要捕获异常。
6>.申请自定义类型的对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间的同时就会自动调用构造函数完成对象的初始化操作,delete在释放空间前会调用析构函数完成空间中资源的清理释放。
7.定位new表达式(placement—new)(了解)
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:new (place_address) type } place_address必须是一个指针
new (place_address) type(initializer_list) } initializer_list是类型的初始化列表
使用场景:在实际中一般是配合内存池一起使用的,因为内存池它所分配出来的空间不会进行初始化,所以如果自定义类型的对象,需要使用new的定义表达式进行调用构造函数以使得对对象进行初始化操作。
#include<iostream>
using namespace std;
class date
{
public:
date(int a = 1)
{
cout << "date(int a = 1)" << endl;
}
~date()
{
cout << "~date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//我们在这里先试着使用operator new函数来开创一块空间。
date* d1 = (date*)operator new(sizeof(date));//通过这句代码我们可以得知,这个operator new这个函数可以直接调用,我们这里是直接调用operator new这个函数,因此,这里不会调用date的构造函数,若想调用构造函数的话,可以使用new的定位表达式。
new(d1)date;//new是关键字(操作符),d1是指针,date是对象的类型。这个就是定位new的写法。这样的话,编译器就会调用date类的构造函数给d1指针指向的那块空间进行初始化操作。
//如果date类需要传值的话,我们这里还可以传值:new(d1)date(10);
d1->~date();//析构函数的话,我们则可以使用"->"这个操作符直接调用即可。
operator delete(d1);//释放空间。
return 0;
}
OK,这一节的内容我们就讲到这里,谢谢大家的观看,你们的支持就是我最大的动力。