动态内存管理

      在C 语言中,我们使用malloc /calloc /realloc /free进行动态内存管理,而在c++中,则更为简单;C + + 通过new和delet e动态管理内存。
  • new / delet e动态管理对象。
  • new [] / delet e[]动态管理对象数组。



这里要强调一点malloc/free、new/delete、new[ ]/delete[ ]一定要匹配使用。为什么呢?我们用下面的几个例子一步步来说明一下

例一:

int*p1 = new int;
int*p2 = new int(3);
int*p3 = new int[10];

delete []p1;
delete p3;


这里我们并没有匹配使用 new/delete、new[ ]/delete[ ],但是由运行结果来看,好像没有什么问题;

接下来再举一个例子-->

例二:

class AA
{
public:
	AA()
	{
		cout << "AA()" << endl;
	}
	AA(const AA&aa)
	{
		cout << "AA(const AA&aa)" << endl;
	}
	~AA()
	{
		cout << "~AA()" << endl;
	}
private:
	int _aa;
};
void test2()
{
	AA*p1 = (AA*)malloc(sizeof(AA));
	AA*p2 = new AA;

	//free(p1);
	//delete p2;
}
int main()
{
	test2();
	return 0;
}

会发现它调用了构造函数,这里经过调试,我们可以发现:是AA*p2=new AA调用了构造函数。

如果把代码稍作改动(构造函数部分)-->

例二(1):

AA(int aa)
:_aa(aa)
{
	cout << "AA()" << endl;
}


再进行编译,会发现编译不通过。因为AA*p2=new AA不仅开空间,还会调用构造函数;而这里我们明显没有给AA参数。所以应该改为:AA*p2=new AA(10);

同理,delete会调用析构函数:

void test2()
{
	AA*p1 = (AA*)malloc(sizeof(AA));
	AA*p2 = new AA(10);

	free(p1);
	delete p2;
}

接下来我们调换一下顺序:malloc开出的空间用delete释放、new开出的空间用free释放


看似好像也没有问题,但是这里要注意上图第2中情况(new/free)很危险,请看下面-->

例二(2):

class AA
{
public:
	AA(int aa)
		:_aa(aa)
	{
		cout << "AA()" << endl;
		p = 开空间;
	}
	AA(const AA&aa)
	{
		cout << "AA(const AA&aa)" << endl;
	}
	~AA()
	{
		cout << "~AA()" << endl;
		释放p所指向的空间
	}
private:
	int _aa;
	int*p;//类的私有成员中定义了一个指针
};
void test2()
{
	AA*p2 = new AA(10);
	free(p2);
}
这里就会出现问题,为什么呢?

原因就在于:在类的私有成员中我们定义了一个指针,在构造函数中我们为其开辟了一块空间,相应地,在析构函数中释放p所指向的这块空间;但是这里test2()函数中,new没有与delete匹配使用,但是用了free.有前面的讲解我们得知new会调用构造函数,而delete会调用析构函数。重点来了,问题就出在这--->new调用了构造函数,在构造函数中已经给p开了空间(p=开空间),但是free并不会调用析构函数,所以p开出的空间没有被释放掉,这就导致了一个很严重的问题:内存泄露。


接着看:还是上面的代码(构造函数改为以下,test2函数改为以下)

例二(3):

AA(int aa=0)
	:_aa(aa)
{
	cout << "AA()" << endl;
}

结果:两种情况程序都崩溃。

再来看下面一段代码-->

例三:

class  Array
{
public:
	Array(size_t  size = 10)
		: _size(size)
		, _a(0)
	{
		cout << "Array(size_t size)" << endl;
		if (_size  > 0)
		{
			_a = new  int[size];
		}
	}
	~Array()
	{
		cout << "~Array()" << endl;
		if (_a)
		{
			delete[]  _a;
			_a = 0;
			_size = 0;
		}
	}
private:
	int*_a;
	size_t    _size;
};
void  Test()
{
	Array*  p1 = (Array*)malloc(sizeof  (Array));
	free(p1);
}
这里我们用了malloc/free,但是会出现一个问题:malloc没有调用构造函数进行初始化,导致类中的_a指针就会是随机值,也就是所谓的“野指针”,free(p1)没有调析构函数,也就没有清理掉里边包含的那段空间,这个时候就会出现“内存泄露”。

综上:尽量使用new/delete

---------------------------------------------------------------------------------------------------------

 1.全局变量、全局静态变量、局部静态变量、局部变量之间的区别是什么?


-----------------------------------------------------------------------------------------------------------------------

下面这幅图帮助我们对变量的存储区域有更深刻的理解:


--------------------------------------------------------------------------------------------------------------

再来看一个问题:

void * operator   new   (s ize_ t   s ize);
void   operator   delete  (s ize_ t   s ize);
void * operator   new  [](s ize_ t   s ize);
void * operator   delet e[]  (s ize_ t   s ize);

乍一眼看,很像运算符的重载,其实并不然(这里我们可以试验一下)

AA*p5 = (AA*) new(sizeof(AA));

再来看一点malloc和new的不同之处:

void test2()
{
	AA*p5 = (AA*) operator new[](sizeof(AA)*1000000000);
	//AA*p6 = (AA*) malloc(sizeof(AA)* 1000000000);
	
}

很明显,我们的目的是:让它们分配内存失败,然后观察malloc与new分配内存失败后二者的处理方式

调出监视窗口进行调试,结果如下--->


可见,malloc分配空间失败会返回一个空指针,而new分配空间失败会“抛异常”。

捕获异常-->

int main()
{
	try
	{
		test2();
	}
	catch (exception&e)
	{
		cout << e.what() << endl;
	}
	system("pause");
	return 0;
}

再来看下面一段代码:(我们详细分析一下函数的调用步骤)-->

例四:

void test3()
{
	AA*p1 = new AA;
	delete p1;
}
详解如图:



回到我们之前所讲的例子:malloc/delete、new/free混合用为什么都没有出问题呢?因为前面我们所举例的类型都是int,属于内置类型;对于内置类型而言,没有构造函数、没有析构函数,那么无论调new还是malloc都没有区别why?调用malloc就相当于直接去开辟空间,new相当于是--->new会调用operator new、operator new里面又去调用malloc也就是说对于内置类型而言,如果是new出来的空间,调用malloc去释放和调用delete去释放是一样的,最终都会调用free

 但是对于自定义类型就不同了;如果用malloc开辟了空间,free去释放,就不会调用析构函数,没调用析构函数可能就会出现“内存泄露”。

再来看下面一个例子-->

例五:

class AA
{
public:
	AA(int aa=0)
		:_aa(aa)
	{
		cout << "AA()" << endl;
	}
	AA(const AA&aa)
	{
		cout << "AA(const AA&aa)" << endl;
	}
	~AA()
	{
		cout << "~AA()" << endl;
	}
private:
	int _aa;
};

void test3()
{
	AA*p2 = new AA[10];
	delete p2;

}
运行会崩溃,为什么呢?这里就涉及到了一个重点按我们之前所想:这里应该是开辟了40个空间,事实上呢?并不是这样,事实上开辟了44个空间



  • 问题(1)为什么会开44个空间呢?多开出来的4个空间其实是存放对象个数


  • 问题(2)为什么要存对象的个数?原因就在于析构要去做一件事:它首先会去调用析构函数,其次会调用operator delete[ ](),调用operator delete[ ]()的时候p的地址是0x003c51e8。申请空间时多开了4个字节在头上,然后释放的时候也会减去4个字节(到0x003c51e8),传给operator delete,operator delete再传给free。

正确的书写方式--->

AA*p2 = new AA[10];
delete []p2;
详解调用析构函数的过程:




错误的写法(1):(程序会崩溃)

AA*p2 = new AA[10];
free(p2);
这里讲一下崩溃的原因(如下图所示)最初开辟空间的时候多开了4个字节,在图中蓝色箭头;而不delete[ ]p2直接free的话就会从下面(图中黑色箭头所指位置)开始释放空间;这样就出现了问题:开辟了一整块空间,最后却释放了部分。

错误的写法(2):(程序也会崩溃)

AA*p2 = new AA[10];
delete p2;
原因:与写法(1)相同。都是只释放了部分空间。

--------------------------------------------------------------------------------------------------------------------------

为什么delete【】p2就不会有问题呢?

原因就在于:它除了要释放空间,还要先调用析构函数,有多少个对象就会调用多少次析构函数;但是只有一个指针,编译器并不知道有多少个对象,所以这里多开辟的4个字节(指针指向)就是方便能找到对象的个数,找到个数再去调用析构函数,再把指针减去4个字节;然后调用operator delete[ ] 去释放这段空间

还有一个问题:为什么delete p2就会崩溃?

答:只有operator delete[ ] 才会去找那4个字节,why?只有operator delete[ ] 出来的时候,它才会多开辟4个字节存放内存个数;而delete p2不需要存个数,因为它永远只有一个。所以奔溃的原因是因为指针释放的位置不对。


最后一个问题(一):

int *p1 = new int[10];
delete p1;

这里为什么就不会崩溃?开辟空间开了多少个?不是44个?


原因:注意-->这不是自定义类型,自定义类型要调用构造和析构函数,而这是int(内置类型),不需要调用构造和析构函数;注意-->只有调用析构函数才会知道要调用多少次(由对象个数确定),才多开4个字节把对象的个数存下来;对于基本类型直接free就可以,压根不用多开4个字节。

问题(二)所有的内置类型都会这样吗?这里开辟了多少个字节?

class BB
{
private:
	int _bb;
};
int main()
{
	BB*p1 = new BB[10];
	return 0;
}

其实本来应该开44个,但是这里编译器优化了,本来再开4个存放个数就会知道究竟调用多少次析构函数,但是这里没有显式地写析构函数,没有显式写的话编辑器就认为它是缺省的,缺省的析构函数可以认为什么都不做。

显式地写上析构函数:

public:
	~BB()
	{

	}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值