【C++】5:内存管理

C++内存管理和C语言内存管理差不多,可以看一下C语言内存管理

目录

一、C/C++内存分布

1.1 C/C++程序内存分配的几个区域

1.2 C/C++中程序内存区域划分:

二、C语言中动态内存管理方式:malloc/calloc/realloc/free

三、C++内存管理方式

3.1 new/delete操作内置类型

3.2 new/delete操作自定义类型

四、operator new与operator delete函数

五、new和delete实现原理

5.1 内置类型

5.2 自定义类型

六、malloc/free和new/delete的区别


一、C/C++内存分布

1.1 C/C++程序内存分配的几个区域

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数⽽分配的局部变量、函数参数、返回数据、返回地址等。
  2. 堆区(heap):⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
  3. 数据段(静态区):存放全局变量、静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的⼆进制代码。

1.2 C/C++中程序内存区域划分:

二、C语言中动态内存管理方式:malloc/calloc/realloc/free

详细内容请看链接:

https://blog.youkuaiyun.com/qq_62473190/article/details/148482605?spm=1001.2014.3001.5501

三、C++内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理

3.1 new/delete操作内置类型

1.动态申请一个int类型的空间

        int* ptr1 = new int;

2.动态申请一个int类型的空间并且初始化为10
        int* ptr2 = new int(1000);

3.动态申请10个int类型的空间
        int* ptr3 = new int[10];

4.动态申请10个int类型的空间并初始化为0

        int* ptr4 = new int[10] {0};

5.动态申请10个int类型的空间并初始化为前五个,其余的初始化为0

        int* ptr4 = new int[10] {1,2,3,4,5};

使用new申请空间,可以使用delete释放空间,如下所示:

        delete ptr1;

        delete ptr2;

        delete[] ptr3;

        delete[] ptr4;

        delete[] ptr5;

注意1:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。

注意2:使用new来申请内置类型,虽然也能通过free来释放,但是不建议使用。尽量配对使用。

3.2 new/delete操作自定义类型

new/deletemalloc/free最大区别是new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数。

我们写一段代码来申请和释放自定义类型,代码如下所示:

#include<iostream>
using namespace std;
class A {
public:
	A(int a1=1):_a1(a1) {
		cout << "A(int a1=1)" << endl;
	}
	~A() {
		cout << "~A()" << endl;
	}
private:
	int _a1;
};
int main() {
	A* p1 = new A();
	delete p1;
	return 0;
}

我们使用new来申请自定义类型时,会自动调用A的构造函数,使用delete来释放空间会自动调用A的析构函数,运行结果如下所示:

注意,如果我们将A的构造函数的默认参数给去掉,再去运行这个代码,会出现错误,显示没有合适的构造函数可以用,如下所示:

#include<iostream>
using namespace std;
class A {
public:
	A(int a1) :_a1(a1) {
		cout << "A(int a1=1)" << endl;
	}
	~A() {
		cout << "~A()" << endl;
	}
private:
	int _a1;
};
int main() {
	A* p1 = new A();
	delete p1;
	return 0;
}

错误截图如下:

所以我们在使用new来申请自定义类型的时候需要传入一个参数,来调用A的构造函数。代码如下所示:

#include<iostream>
using namespace std;
class A {
public:
	A(int a1) :_a1(a1) {
		cout << "A(int a1=1)" << endl;
	}
	~A() {
		cout << "~A()" << endl;
	}
private:
	int _a1;
};
int main() {
	A* p1 = new A(1);
	delete p1;
	return 0;
}

如果我们想要申请自定义类型的数组呢?以上面的A类类型为例:

        A* p1=new A[3];

如果A类有默认构造函数,这样写是没有问题的,但是上面的类是没有默认构造函数的,那怎么办呢?

我们可以在申请内存的时候,创建三个A类型的对象,然后使用这三个对象初始化数组,如下所示:

int main() {
	A aa1(1);
	A aa2(1);
	A aa3(1);
	A* p1 = new A[3]{ aa1,aa2,aa3 };
	delete[] p1;
	return 0;
}

但是这样写太麻烦了,我们申请自定义类型的数组,还要创建自定义类型的对象,然后再给数组初始化,有没有更好的方法呢?

方法一:使用匿名对象来初始化数组,代码如下:

int main() {
	A* p1 = new A[3]{ A(1),A(2),A(3) };
	delete[] p1;
	return 0;
}

方法二:使用类型转化来初始化数组,代码如下:

int main() {
	A* p1 = new A[3]{ 1,2,3 };
	delete[]  p1;
	return 0;
}

大括号里面的1,2,3会通过隐示类型转化为A类型的对象。具体可以看类和对象(下)的类型转换:

https://blog.youkuaiyun.com/qq_62473190/article/details/151726758?spm=1001.2014.3001.5501

但是,如果类类型需要两个参数才能调用构造函数呢?那怎么使用隐示类型转化来初始化数组呢?代码如下:

#include<iostream>
using namespace std;
class B {
public:
	B(int b1, int b2) :_b1(b1), _b2(b2) {
		cout << "B(int b1, int b2)" << endl;
	}
	~B() {
		cout << "~B()" << endl;
	}
private:
	int _b1;
	int _b2;
};
int main() {
	B* p1 = new B[3]{ {1,2},{3,4},{5,6} };
	delete[] p1;
	return 0;
}

希望以上内容能够帮助读者在使用new的时候分清楚什么时候使用小括号,什么时候使用大括号,什么时候使用中括号。

delete的用法和内置类型的delete用法一样,使用delete在释放一个对象的时候就是delete p1;释放数组就是delete[] p1;尽量不要弄混。如果我们在释放数组的时候少了[],会只调用一次析构函数。有时候可能会出现错误。

下面举一个例子来说明一下少了方括号会怎么样。

我们来执行两个代码,

代码一:

#include<iostream>
using namespace std;
class A {
public:
	A(int a1 = 1, int a2 = 2) :_a1(a1), _a2(a2) {
	}
private:
	int _a1;
	int _a2;
};
int main() {
	A* p1 = new A[10];
	delete p1;
}

代码二:

#include<iostream>
using namespace std;
class B {
public:
	B(int b1=1,int b2=2):_b1(b1),_b2(b2) {
	}
	~B(){}
private:
	int _b1;
	int _b2;
};
int main() {
	B* p2 = new B[10];
	delete p2;
	return 0;
}

以上两段代码都没有使用delete[]来释放数组空间,我们执行这两段代码,会发现代码一能够执行通过,代码二不能执行通过,这是为什么呢?按理来说两段代码应该都不能执行通过才对。会有很多人猜想是析构一次出错了,其实不是这个原因,代码一和代码二都只析构了一次。

代码二比代码一多了一个析构函数,如果有析构函数,在new的时候会在p2前面多开一个4字节的空间,用来存储对象个数,方便析构。如果在delete没有带[],就找不到前面多申请的4个字节,就只会释放动态内存开辟内存的一部分而导致出错。代码一没有析构函数,所以不会多申请4个字节,delete p1 就是从申请的位置开始释放。所以代码一能运行通过,代码二不能运行通过。如果我们都加上[]就没有这个问题了。

所以我们在释放数组的时候必须用delete[]。

四、operator newoperator delete函数

new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

1.operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。

2.operator delete:该函数最终是通过free来释放空间的

五、new和delete实现原理

5.1 内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:

new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申

请空间失败时会抛异常,malloc会返回NULL。

5.2 自定义类型

  • new的原理
  1. 调用operator new函数申请空间
  2. 在申请的空间上执行构造函数,完成对象的构造
  • delete原理
  1. 在空间上执行析构函数,完成对象中资源的清理工作
  2. 调用operator delete函数释放对象的空间
  • new A[N]原理
  1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象的空间的申请
  2. 在申请的空间上执行N次构造函数
  • delete[] 原理
  1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
  2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

六、malloc/freenew/delete的区别

malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。
不同的地方是:
  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在释放空间前会调用析构函数完成空间中资源的清理释放

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值