动态内存管理

C语言中的动态内存管理:

       我们一般在堆上开辟动态空间,在C语言中我们一般用到4个函数来管理动态开辟的空间------malloc、calloc、realloc和free。我们先来认识一下这4个函数:

malloc------原型为void *malloc(size_t size); 在堆上开辟size个字节未初始化的空间,这片空间类型为void,并返回一个void*类型的指针。

calloc-------原型为void *calloc(size_t num, size_t size); 在堆上开辟num*size个字节并初始化为0的空间,空间类型为void,返回void*类型的指针,其中num表示元素个数, size表示每个元素的大小。

realloc------原型为void *realloc(void *memblock, size_t size); 对已在堆上存在的内存进行扩容,其中memblock为堆上已存在的空间的起始地址,size为需要将已存在的空间扩展(或缩小)到多大。

free-------原型为void free(void *memblock); 释放动态开辟的内存。

上面的函数中malloc、calloc和realloc函数都可开辟内存,free释放内存,每段堆上申请的内存用完后都要进行释放,否则会发生内存泄漏,在写程序时有各种各样的内存泄漏问题需要我们注意。当然我们也可以用_alloc函数在栈上开辟空间,因为栈上空间具有函数作用域,用完后可以自动回收内存,因此不用用户显示释放。

        上面我们总结了C语言中动态内存管理的诸多知识,但是这些都不是这篇文字的重点,我们现在中点来看C++中的动态内存管理。

C++中的动态内存管理

       在C++中我们用两个关键字new和delete来管理动态内存,两者使用规则如下:


上面已经注明了new/delete、new[]/delete[]要配对使用,然而我们在写代码时会经常忘了进行配对使用,那么如果没有配对会有什么后果呢?在C++中依然可以用C的方式申请空间,那么为什么还要引入new/delete关键字呢?我们来深入研究new/delete管理动态内存。

1、new/delete

       我们先使用new来申请一个int类型元素(4个字节)的空间:

int main()
{
	int *p = new int;

	return 0;
}

通过调试调出汇编代码看关键字new底下的实现:


可以看见在用new开辟空间时调用了operator new函数,我们进入operator new函数内部看是怎么开辟空间的:


我们可以观察到,在调用operator new函数时传递了大小为4的参数,而operator new函数里面则调用了malloc函数。

       通过上面的观察可知,用new开辟空间本质上还是用malloc开辟空间,不过new还做了其他事。下面给出一个简单的类:

class Data
{
public:
	Data()
	{
		cout << "Data()" << endl;
	}
	~Data()
	{
		cout << "~Data()" << endl;
	}
private:
	int a;
	int b;
};

在构造函数和析构函数中都输出对应的函数名,我们用new开辟一个Data类型大小的空间创建一个Data对象:

int main()
{
	Data *pd = new Data;

	return 0;
}

执行结果如下:


可以看出在开辟空间时执行了Data类的构造函数,我们再来看汇编代码:


可以观察到,在调用operator new函数开辟空间后还调用了类Data的构造函数。

       通过上面的观察,我们可以知道new在开辟空间后还会调用对象的构造函数对其进行初始化。

      现在我们用同一个类来探讨delete的作用机制,给出如下代码:

int main()
{
	Data *pd = new Data;
	delete pd;

	return 0;
}

执行程序,看输出结果:


执行了析构函数!经过前面的讨论,我们知道用new开辟空间会执行构造函数但不会执行析构函数,那么只可能是在用delete释放内存时才会执行析构函数,我们来查看汇编代码:


可以看见在执行delete pd; 语句时调用了一个不知名的函数,我们现在进入这个函数:


不知名函数调用了两个函数完成释放内存的操作,即析构函数和operator delete函数,对象生命周期结束时会调用其析构函数,在之前的探讨中我们知道了new会调用operator new函数开辟空间,这里的operator delete 函数是否用于释放内存的呢?我们可以进入这个函数内部:


可以看出,在operator delete函数内部调用了free函数进行释放内存。

       通过上面的分析,我们可以知道,用delete释放内存作用机制是先调用析构函数,然后再进行内存的释放。

2、new[]/delete[]

       new[]和delete[]作用机制和new/delete类似,new[]和new都是先开辟空间然后调用构造函数,delete[]和delete都是先调用析构函数再释放空间,不同的是new[]/delete[]组合是针对多个对象元素,而new/delete针对单个对象,new[]/delete[]针对多个对象,因此会调用多次构造函数和析构函数,由此也引出一个问题---系统怎么知道到底要调用多少次构造函数和析构函数呢?我们继续用上面的类来探讨。用new[]/delete[]管理内存:

int main()
{
	Data *pd = new Data[10];
	delete[] pd;

	return 0;
}

进入汇编代码观察:


可以看到在汇编代码中调用了operator new[]函数和一段不知名代码,根据前面对new的分析,我们可以做出合理的推测,operator new[]函数用于开辟空间,而不知名代码用于调用构造函数,我们先进入operator new[]函数观察:


可以看到,在开辟空间时多开辟了4个字节的空间,这4个字节的空间用来干嘛呢?结合前面的问题,我们很容易就能想到用来存放对象的个数。再来观察delete[]的汇编代码:


进入不知名代码中:


目前我们所观察到的都复合前面的推测。现在将析构函数去掉,也就是类中只有构造函数没有析构函数,我们再次观察new[]汇编代码:

进入operator new[]中:


没错!在没有给出析构函数的情况下开辟空间时没有多开辟4个字节的空间。

         那么我们可以确定--------多开辟4个字节的空间用来存放析构函数的个数,在没有显示给出析构函数的情况下开辟空间时就没有多开辟4个字节。

         现在我们来看一下在显示给出析构函数的情况下用delete和free来释放空间会发生什么:

用delete释放:


用free释放:


用delete和free都释放失败,可见在显示给出析构函数的情况下,因为要多次调用析构函数,因此必须有delete[]来释放内存。

        那么我们再来看一下没有显示给出析构函数的情况:

我们先用delete进行释放:


释放成功。free:


free释放成功。

         我们可以看到在没有显示 给出析构函数的情况下delete和free都可以进行释放。因此我们可以得出结论:

在类中有显示给出析构函数的情况下必须按new/delete、new[]/delete[]进行配对使用,而在没有显示给出析构函数时则可以用delete和free对内存进行释放。

       在进行动态内存管理时,我们要特别注意防止内存泄漏、野指针等问题。动态内存知识广泛,在今后的使用中希望我们能得到更大的提升!


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值