各位nb的步调员们,各人好,当我们要学一门语言并且要将它学到熟练的话,我们就不得不要去学习它的内存管理这部分的内容了,基于此处,我们这里就来学习一下C++中的内存管理方面的知识。
目次
1.C/C++内存发布
1.1.栈
1.2.内存映射段
1.3.堆
1.4.数据段
1.5.代码段
2.C语言中的动态内存管理方式
2.1.malloc函数
2.2.realloc函数
2.3.calloc函数
2.4.free函数
3.C++中的内存管理方式
3.1.new/delete操作内置范例
3.2new和delete操作自界说范例
4.operator new和operator delete函数(了解知识)
5.new和delete的实现原理
5.1内置范例
5.2自界说范例
6.malloc / free和new / delete的区别
7.定位new表达式(placement—new)(了解)
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,这一节的内容我们就讲到这里,谢谢各人的观看,你们的支持就是我最大的动力。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |