动态内存管理
一,C/C++内存分布
1.内核空间:放的是与操作系统相关的代码,用户没有权限直接去操作;如果要操作,只能通过指定的api函数进行。
2.栈:存放与函数调用相关的一些数据;
栈帧:函数的参数,函数体中的局部变量,一些寄存器信息。特性:函数调用完成之后,对应的栈帧就被回收了。
3.内存映射段:放置静态库动态库的数据等。
4.堆:用户进行动态内存申请。C语言中:malloc/calloc/realloc。这些空间用完之后,必须要通过free来进行释放,堆空间相对而言比较大。
5.数据段:存放全局数据(变量和对象),被static修饰数据。
特性:当程序启动时,该部分数据的空间就被开辟好了,当程序结束时被销毁—>即该位置存储的数据生命周期伴随程序。
6.代码段:放置用户代码,以及只读常量。该位置的数据是不能修改的。
二,C语言中动态内存管理方式
1.malloc/calloc/realloc这三个方法之间的区别??
相同点:
1.都是C语言中用来进行动态内存申请的库函数
2.申请的空间都在堆上,用完之后必须要使用free来进行释放
3.如果空间申请成功,返回空间的首地址,如果申请失败返回的是NULL,因此在使用之前必须要进行判空。
4.返回值类型都是void*,在接收返回值时,必须要进行强转。
不同点:
void* malloc(size_t size);
malloc的参数size是用户所申请空间的字节数;
申请空间成功返回空间的首地址,如果申请空间失败,返回的是NULL;
用户在进行接收的时候必须要强转,在使用时必须要进行判空,使用完之后必须要借助free来进行释放。
void* calloc(size_t num, size_t size);
参数个数不同:num:表示元素的个数。 size:表示单个元素的所占的字节数
功能上唯一的不同就是:calloc会将其申请的内容空间初始化为0
void* realloc(void* p, size_t size);
将p指向堆空间的大小调整到size字节
p==NULL-->该函数的功能与malloc类似,直接申请size字节返回即可。
p!=NULL-->将p指向的空间大小调整到size字节
假设:p指向空间的大小为oldsize字节。
size<=oldsize:将p指向的空间缩小,然后返回原空间的首地址即可
size>oldsize:将p指向的空间扩大
大一点点:realloc(p,16);直接在p的基础上进行延申
大许多:realloc(p,400) ;申请size个字节的新空间,将旧空间中的内容拷贝到新空间,释放旧空间,然后返回新空间的地址
三,C++中动态内存管理
前言:C++既然可以兼容C语言,即C语言中动态内存管理的方式在C++中仍旧可以使用。
为什么C++要单独整一套自己的动态内存管理方式呢?
在C++中,使用malloc/free在堆上申请或者释放内置类型的空间,没有任何问题。
但是不能采用malloc从堆上申请对象的空间。因为:malloc不会主动调用构造函数
因此该块空间并不能成为对象,而只是与对象大小相同的一块堆空间。
也不能使用free释放堆对象的空间,因为free在释放对象空间时,不会调用析构函数将对象中的资源清理干净。
C++中动态内存管理的方式:
申请单个类型的空间:new
int* p1 = new int;
int* p2 = new int(10); // 对申请的空间进行初始化
释放单个类型空间使用:delete
delete p1;
delete p2;
申请一段连续的空间:new []
int* p3 = new int[10];
int* p4 = new int[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
释放一段连续的空间: delete[]
delete[] p3;
delete[] p4;
注意:
new/delete new[]/delete[] malloc/free一定要匹配使用。
否则:程序可能会崩溃或者程序可能会发生内存泄漏
四,new和delete的实现原理
1.内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间;
而且new在申请空间失败时会抛异常,malloc会返回NULL。
2.自定义类型
1. new T;
1.申请内存空间
调用void* operator new(size_t size)申请空间
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
operator new(size)函数中直接使用malloc循环来申请空间,
成功:直接返回
失败:检测用户是否提供内存不足的应对措施:
是:执行用户所给的操作,然后继续使用malloc来进行申请
否:该方法会抛出bad_alloc异常
2.调用构造函数对申请的空间进行初始化
2. delete P;
1.调用对应类型的析构函数,清理对象中的资源
2.调用void operator delete(void* p)
operator delete最终是通过free来释放空间的.
3. new T[n]
1.申请空间
先调用void* operator new[](size_t size)来申请空间
注意:如果T类中定义了析构函数,在使用new[]申请空间时会多申请4个字节
2.调用n次构造函数,将申请的内存空间的中的n个对象构造好
4. delete[] p;
1.调用n次析构函数对p指向的空间中的资源进行清理
2.调用void operator delete[](void* p)对p指向的空间进行释放
注意:先创建的后释放
五,定位new表达式
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象.
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义
类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
class Test
{
public:
Test(int t = 0)
:_t(t)
, _p(new int)
{
cout << "Test(int):" << this << endl;
}
~Test()
{
delete _p;
cout << "~Test():" << this << endl;
}
private:
int _t;
int* _p;
};
int main()
{
// 注意:malloc申请出来的空间不能将其称作为对象
// pt现在指向的是与Test对象相同大小的一段空间
// 因为:malloc在申请空间期间不会调用构造函数
Test* pt = (Test*)malloc(sizeof(Test));
// 现在需要将pt指向的堆空间变成一个对象
// 如果能够在pt指向的空间上执行构造函数即可
new(pt)Test(100);
pt->~Test(); //调用析构函数
free(pt);
return 0;
}