c++中的动态内存管理问题
c++中使用new和delete实现动态内存管理。new和delete实现动态管理对象,new[]和delete[]实现动态管理对象数组。c++中的new和delete运算符均使用我们c中学过的malloc和delete函数实现动态内存的开辟。
首先,先简单介绍下c中的几个动态内存函数malloc,realloc,calloc,free;
void *malloc(size_t size); //动态内存开辟函数
void free(void *pointer); //内存释放函数,向指针传一个NULL不会有任何效果
void *relloc(void *ptr,size_t,new_size);//扩容函数,依然保存扩容之前的内容(扩大内存),若减小内存,则内存块尾的空间被拿掉
void *calloc(size_t num_elements,size_t size);//动态内存开辟并初始化,返回的指针指向的内存的内容被初始化为0
一般在程序出现内存错误很有可能就是动态内存的释放出问题了,所以在使用动态内存时一定要注意释放的问题,在c中还需要注意在释放完动态内存后将指向动态内存的指针置为NULL,避免形成野指针,好了c中的动态内存管理就回忆到这,毕竟我想说的是c++的动态内存管理问题。
c++中使用new/delete或new[]/delete[]实现动态管理,特别需要提醒的是在动态内存的管理中一定要注意new/delete,new[]/delete[],malloc/free一定要注意匹配使用。
我知道大家肯定想既然c++兼容c,为什么不直接使用c中的malloc/free,而要使用new/delete,首先我们需要了解一下二者的区别和联系。
【malloc/free和new/delete的区别和联系?】
1. 它们都是动态管理内存的入口。
2. malloc/free是C/C++标准库的函数,new/delete是C++操作符。
3. malloc/free只是动态分配内存空间/释放空间。new/delete除了分配空间还会调用构造函数和析构函数进行对象的初始化与清理(清理成员)。
4. malloc/free需要手动计算类型并且返回值会void*,new/delete可以自动计算类型的内存的字节数,返回对应类型的指针。
写个简单的程序显示下malloc和new的使用;
首先定义一个Array类:
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; //记录动态空间开辟了几个int型的空间
};
下面我们观察下new和malloc的使用
void Test()
{
Array* p1 = (Array*)malloc(sizeof(Array)); //malloc()只负责空间的开辟
Array* p2 = new Array; //new开辟空间并且构造了一个对象
Array* p3 = new Array(20);
Array* p4 = new Array[10];
free(p1);
delete p2;
delete p3;
delete[] p4; //在释放动态开辟的数组空间时一定要使用delete[]
}
通过测试程序,很明显的看到new和malloc的不同。
其实通过动态开辟的数组空间使用delete并不一定会出错,例如:
int *pi = new int[10];
delete pi;
此时程序并不会出错,程序没有崩溃并不代表程序正确,只不过在这次运行没有崩溃而已,相信大家都看出来了此时delete并没有将动态开辟的空间全部释放,只释放了数组一个元素的空间而已,此时发生了很严重的内存泄漏问题,可能很多人对内存泄漏这个问题不是很重视,但这个问题又是却是致命的,内存泄漏并不仅仅只是将系统内存耗尽,最重要的问题你当前所运行的程序也可能崩溃,这对一个程序来说是致命的,但这个问题却是每一个程序员所苦恼的问题,内存泄漏在程序开发中总是如影随形,解决这个问题就要使用到智能指针的知识了,这不是我这篇博客的重点,咱们现在还是静下心来研究new/delete吧!!!!
之前说过c++中的new和delete的内部还是依靠malloc/free实现的,在使用我们new时我们调试进入内部发现,new调用了operator new(),delete调用了operator delete(),c++ 中还有其他几个内存管理接口,new/delete表达式的实现依靠库中的这几个函数。
void * operator new (size_t size);
void operator delete (size_t size);
void * operator new [](size_t size);
void operator delete[] (size_t size);
这几个函数在内部实现空间的开辟和释放,他们只负责空间的开辟和释放,不会调用构造函数构造对象也不会调用析构函数清理对象。实际上他们只是malloc/free的封装。
下面我们总结下new/delete,new[]/delete[]在程序运行分别做了些什么
new做了两件事
1. 调operator new分配空间。
2. 调构造函数初始化对象。
delete也做了两件事
1. 调析构函数清理对象
2. 调operator delete释放空间
new[N]
1. 调operator new分配空间。
2. 调N次构造函数分别初始化每个对象。
delete[]
1. 调N次析构函数清理对象。
2. 调operator delete释放空间。
特别需要注意的是delete[]调用了N此析构函数,N在哪保存呢?
实际上new[]在调用operator[]时多开辟了四个字节用来存放创建对象的个数。
对象的个数 9 | 指向对象的指针 | 指向对象的指针 | 指向对象的指针 | 指向对象的指针 | 指向对象的指针 | 指向对象的指针 | 指向对象的指针 | 指向对象的指针 | 指向对象的指针 |
四个字节
我们可以使用定位new表达式来模拟实现new的功能,我们先来了解下new表达式:
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
new (place_address) type
new (place_address) type(initializer-list)
place_address必须是个指针,initializer-list是类型的初始化列表。
模拟实现new[]/delete[]
class A //类中的成员变量存在内存对齐
{
public:
A(int size = 10):
_sz(size)
, _p(new int[size])
{}
A(const A& a):
_sz(a._sz)
, _p(new int[a._sz])
{
}
~A()
{
delete[] _p;
}
private:
int _sz;
int *_p;
};
void Test()
{
A *a = (A*)operator new[](sizeof(A)*10 + 4);
//给类开辟空间,由于析构函数显示定义,多开辟四个字节去保存对象个数
*(int *)a = 10;
A *pStart = (A*)((int*)a + 1);
int i = 0;
for (i = 0; i < 10;i++)
{
new (pStart+i)A(i);
}
int count = *((int*)pStart - 1);
for (i = 0; i < count; i++)
{
pStart[i].~A();
}
operator delete[]((int*)pStart - 1);//释放空间
}
new/delete、new[]/delete[]在程序中的实现:
文中的的红色字体均是重点。
本人见识尚欠,若有理解错误之处,还望各位大神提出来,鄙人一定虚心学习,认真改正。
本文出自 “qin-wang” 博客,请务必保留此出处http://10810196.blog.51cto.com/10800196/1759454
原文地址:http://10810196.blog.51cto.com/10800196/1759454