【C++】内存管理与分配
目录
💕1.内存分配
💕2.动态开辟与释放内存(关键字new,delete)
💕3.动态开辟时,类的问题
💕4.动态开辟内存的写法
💕5.operator new[]与operator delete[]函数
💕6.new和delete的实现原理
💕7.delete与free混合使用的问题
💕8.关于new/delete时,显示调用构造/析构函数的问题
💕9.开辟失败需要单独写吗?
💕10.完结撒花
(最新更新时间——2025.1.18)
💕1.内存分配
我们先来看一下内存的分配->:
在C++中,内存分配就如上图所示,但光看没有用,我们做几道题
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; int globalVar = 1; static int staticGlobalVar = 1; void Test() { static int staticVar = 1; int localVar = 1; int num1[10] = { 1, 2, 3, 4 }; char char2[] = "abcd"; const char* pChar3 = "abcd"; int* ptr1 = (int*)malloc(sizeof(int) * 4); int* ptr2 = (int*)calloc(4, sizeof(int)); int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4); free(ptr1); free(ptr3); } 1. 选择题: 选项 : A.栈 B.堆 C.数据段(静态区) D.代码段(常量区) globalVar在哪里?____ char2在哪里?____ staticGlobalVar在哪里?____ * char2在哪里?___ staticVar在哪里?____ pChar3在哪里?____ localVar在哪里?____ * pChar3在哪里?____ num1 在哪里?____ ptr1在哪里?____ * ptr1在哪里?___
答案如下->:
左边五个没什么可以讲的,主要讲右边六个,char2是数组的名字,数组名表示首元素地址,所以解引用数组名其实指的是"abcd"中的'a',虽然字符串"abcd"存在常量区,但因为是数组,所以字符串被拷贝到栈里了,所以*char是栈区。
pChar3是一个指针,所以存放在栈区,*pChar3所指向的内容在常量区,所以*pChar3在常量区。
ptr1是一个指针,在栈区,*ptr所指向的内容在堆区,所以*ptr在堆区
💕2.动态开辟与释放内存(关键字new,delete)
在C++中,我们有了新的动态内存开辟方式,那就是new,那我们之前学的malloc等还可以用吗,答案是可以的,但是会有一些问题,什么问题?我们后面讲
不懂动态内存分配的请看这篇文章->:
C语言——动态内存分配
new关键字的使用
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; int main() { int* a = new int;//开辟一个int的空间 int* b = new int[4];//开辟4个int的空间 }
如何?是不是比malloc很方便
那我们该如何释放呢?
这时候就要用到关键字delete,代码如下->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; int main() { int* a = new int;//开辟一个int的空间 int* b = new int[4];//开辟4个int的空间 delete a;//释放单个空间 delete[] b;//释放多个空间 }
我们之前学过,malloc开辟空间的同时不可以进行初始化,但是calloc可以
那new可以开辟空间的同时进行初始化吗?
答案是可以的,代码如下->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; int main() { int* a = new int(10);//单个初始化 int* b = new int[4] {1, 2, 3, 4};//多个初始化 delete a;//释放单个空间 delete[] b;//释放多个空间 }
💕3.动态开辟时,类的问题
我们想给类开辟空间时该怎么做,我们先用malloc为例
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; class Date { private: int _year; int _month; int _day; }; int main() { Date* d1 = (Date*)malloc(sizeof(Date));//很正常的开辟空间 d1->_year;//不能访问,因为是私有 }
使用malloc开辟类的空间会导致无法访问类内部成员,因为类内部成员基本都是私有的
但如果用new呢?
看这些代码,我们可以得知,new开辟类的空间时,是会调用类的构造函数的,这就导致我们可以对类中的成员进行初始化
💕4.动态开辟内存的写法
接下来请看整合起来的代码,更加易懂些->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; class Date { public: Date(int a = 10, int b = 20) :_year(a) ,_month(b) ,_day(a) { cout << "第"<<count<<"次调用构造函数" << endl; count++; } ~Date() { cout << "第" << _count << "次调用析构函数" << endl; _count++; } private: int _year; int _month; int _day; static int count; static int _count; }; int Date::count = 0; int Date::_count = 0; int main() { int* a = new int;//开辟1个int int* b = new int[10];//开辟10个int int* c = new int(2);//开辟1个int并初始化为2 int* d = new int[5] {1, 2, 3, 4, 5};//开辟5个int并依次初始化为1,2,3,4,5 Date* d1 = new Date;//开辟1个Date,并调用它的构造函数 Date* d2 = new Date[5];//开辟5个Date,并分别调用它们的构造函数 Date* d3 = new Date(2);//开辟1个Date,并调用它的构造函数,并将2传给构造函数 Date* d4 = new Date[5]{ 9,8,{1,2},{3,4},{5,6} };//开辟5个Date,并调用它们的构造函数 //第一个Date将9传给构造函数 //第二个Date将8传给构造函数 //第三个Date将1,2传给构造函数 //第四个Date将3,4传给构造函数 //第五个Date将5,6传给构造函数 delete a; delete[] b; delete c; delete[] d; delete d1; delete[] d2; delete d3; delete[] d4; }
我们总结一下->:
new 【自定义类型】除了开空间,还会调用构造函数delete 对于【自定义类型】会调用析构函数
💕5.operator new[]与operator delete[]函数
我们在开辟多个类型数据时用new T[N],其内部调用的是operator new[ ]函数,而operator new[ ]函数其实就是由operator new函数实现完成的,见下面汇编代码:
同理delete[ ]函数内部实现是由operator delete[ ]函数实现的,而operator delete[ ]函数内部实现也是由operator delete函数所实现的:
💕6.new和delete的实现原理
1.这里以下图来看,我们在new开辟空间时,是先调用operator new(malloc)函数来申请空间的,然后在这块申请空间上调用该对象的构造函数,开辟新的空间并来完成对象的构造
2.在delete时,先执行对象的析构函数,将构造函数开出的空间里的资源先清理掉,然后调用operator delete(free)函数来释放operator new(malloc)申请的空间
3.new T[ ]开辟多个空间时,只会调用一次operator new(malloc)函数,每次申请新的空间时,每个对象都会调用一次它的构造函数
4.在delete[ ]时也只会调用一次operator delete(free)函数,调用N次析构函数
💕7.delete与free混合使用的问题
如下图,我们可以看到类的大小为4个字节,开辟一个类的空间大小不应该也是4吗?为什么是8呢?
这其实是因为在开辟类的空间时,会单独开辟出一个整型的空间,用来计数开辟了几个类的空间,我们再开辟一个来试试
这次,我们开辟了两个类的空间大小,但是开辟了12个字节,这也证实了我们的说法
其实单独开辟一个整形的空间用来存储个数,是为了delete[ ]时知道有多少个对象,要调用多少次析构函数
那如果我们分别用delete和free释放时,它会从哪里释放呢?
我们来试一试
用delete时,所有的开辟空间都被释放掉了,那用free呢(如下)?
可见,free只会释放掉new时所调用的operator new所开辟出来的空间,对于单独开辟出的那一个用于计数的int类型的空间并不会释放
💕8.关于new/delete时,显示调用构造/析构函数的问题
在new/delete时,是不可以直接显示调用构造函数的,我把写法放在下面的代码里了
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; class Date { public: Date(int a = 10, int b = 20) :_year(a) , _month(b) , _day(a) { } ~Date() { } private: int _year; int _month; int _day; }; int main() { Date* d1 = new Date;//开辟1个Date,并调用它的构造函数 d1->Date();//不支持这样显示调用构造函数 new (d1)Date;// 对已有空间,显示调用构造 new (d1)Date(10);// 对已有空间,显示调用构造 Date* d2 = new Date[10]; new (d2)Date[10]{ 1,2,3,4,5,6,7 };// 对已有空间,显示调用构造 //也可以这么写 for (int i = 0; i < 10; i++) { new (d2+i)Date(i); } // 释放时的写法 for (int i = 0; i < 10; i++) { (d2 + i)->~Date(); } }
💕9.开辟失败需要单独写吗?
在C++中,使用new开辟空间可以每次不写判断是否开辟成功,在我们学习 "异常" 后就会懂,这里先不做讲解