C++中内存的动态管理

文章讲述了C++中动态内存管理,包括使用new,delete操作符以及malloc/calloc/realloc/free的区别,特别强调了在类中使用new和delete时构造函数和析构函数的自动调用。还讨论了operatornew和operatordelete的底层实现原理以及new[]和delete[]的正确用法。
摘要由CSDN通过智能技术生成

我们在C语言中了解到可以在栈区动态开辟空间,并且用完要进行释放,防止内存泄漏。

引入

C++中也有可以进行动态开辟空间和释放空间的操作符new 、delete,虽然C++中也可以用malloc、calloc、realloc、free函数,但是C++中引入了类,而类中又有构造函数和析构函数,在实例化对象时会默认调用。如果你想在堆上开辟类类型的空间继续malloc的话,那么该空间的内容就不会自动调用构造函数,而free也不会自动调用析构函数,因为你创建的类指针变量存的是这块空间的地址,属于内置类型,所以程序结束时也不会自动调用析构函数。

对内置类型的内存管理

int main()
{
	// 动态申请一个int类型的空间,不初始化
	int* ptr1 = new int;

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

	// 动态申请3个int类型的空间,并初始化为1 2 3
	int* ptr3 = new int[3] {1, 2, 3};

	delete ptr1;//释放单个空间
	delete ptr2;

	delete[] ptr3;//释放连续空间
	return 0;
}

申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用
new[]和delete[]。其实此时对于内置类型而言new与delete相较于malloc与free实现的功能其实是一样的。

对自定义类型的内存管理 

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	A* p1 = (A*)malloc(sizeof(A));
	//A* p2 = new A(1);
	free(p1);
	//delete p2;

	return 0;
}

运行结果:

int main()
{
	//A* p1 = (A*)malloc(sizeof(A));
	A* p2 = new A(1);
	//free(p1);
	delete p2;

	return 0;
}

运行结果:new空间时自动调用构造函数,delete空间时会先调用析构函数

int main()
{

	A* p2 = new A[5];

	delete[] p2;

	return 0;
}

运行结果:会在new的时候调用5次构造函数,在delete之前调用5次析构函数

operator new与operator delete函数

其实操作符new、delete也不是单纯的直接实现的,其实,在它们内部还调用着对应的函数operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

operator new

其实operator new也是通过调用malloc函数实现的,在堆区开辟空间,但是在malloc的基础之上稍稍加了修饰,malloc调用失败会返回NULL,而operator new在此基础上加了判断条件:

while ((p = malloc(size)) == 0)//C++中NULL就是0

此时如果为空的话,就会抛异常报错。

operator delete

其实像operator new一样,operator delete是通过free来释放空间的。

new和delete的实现原理

其实new和delete操作符也不是仅仅就调用了operator new和operator delete那么简单,实际上还调用了类的构造函数,而delete还调用了类的析构函数(delete是先调用析构函数,再调用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来释
放空间

 未匹配报错

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		//cout << "A():" << this << endl;
	}
	~A()
	{
		//cout << "~A():" << this << endl;
		//_a = 0;
	}
private:
	int _a;
};
int main()
{
	A* p = new A[5];
	delete p;

	return 0;
}

此时delete未与new[]匹配,其实应该delete[]p来释放空间的,此时其实就是未匹配的原因报错的。

实际上当你执行:

	A* p = new A[5];

并不是开辟5个A类型大小的空间,实际上会多开辟四个字节来存放你要开辟的空间的数字,也就是5,存着的目的也就是通过指针偏移得到这四个字节的数据,从而调用多少次析构函数,所以必须要用delete[]p来释放才行(实际上[]里接收的就是前面四字节空间存的5)。但是调用operator delete时,指针会偏移指向最前面,释放所有的空间,也就包括前面的四个字节。

所以 用delete p来释放空间就肯定会报错,不仅仅不会调用5次析构函数,而且最主要还是不会将前面四字节空间释放,所以就:


 

其实我演示的是在x86环境下的,所以就多开辟四字节空间存放数据,试试看在x64环境下,貌似是开辟8字节空间存放数据。

 这里A占4个字节,-1就是偏移四字节,-2就是偏移8字节。



其实你不写析构函数的话,编译器也不会报错

class A
{
public:
	A(int a=0)
		: _a(a)

	{}
private:
	int _a;
};
int main()
{
	A* p = new A[5];
	delete p;

	return 0;
}

因为VS编译器进行优化了,没写析构函数,编译器默认生成,但是默认生成也不会执行什么命令,此时编译器就会进行优化,所以就不会再多开辟字节去存放开辟空间的数字了,所以调用operator delete时就是正确的,不会报错,但是可能会内存泄漏(由于没有调足够的析构函数)



欢迎各位莅临指导!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CR0712

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值