目录
operator new 和 operator delete
C语言的动态内存管理:
在C语言中,动态内存的管理主要靠四个函数来实现,这四个函数分别是malloc、calloc、realloc、free。他们分别有不同的功能,但总体上都是在内存的堆上进行内存操作。
1.malloc
结构:
参数是开辟空间的大小,单位是字节(bite),如果开辟成功,则返回开辟空间的首地址,类型是void*,开辟失败就返回空指针NULL。
2.calloc
结构:
calloc函数和malloc函数的功能相近,但是calloc函数会把开辟的空间给赋值成0,其参数分别是开辟空间中元素的个数(num)和每个元素所占空间的大小(size),单位是字节(bite)。 如果开辟成功,则返回开辟空间的首地址,类型是void*,开辟失败就返回空指针NULL。
3.realloc
结构:
realloc函数是给已经分配好的空间重新分配空间,其参数是空间的地址(memblock)和空间的大小(size),单位是字节(bite)。如果开辟成功,则返回开辟空间的首地址,类型是void*,开辟失败就返回空指针NULL。
4.free
结构:
free函数是对动态开辟的空间进行销毁,即数据销毁,把空间权限开放给操作系统,使操作系统能够再次拥有这片空间的分配权和使用权。参数是空间地址(memblock),无返回值。
使用事项
1.calloc函数初始化只能是0,对于元素是指针的情况会将元素初始化为NULL,元素是浮点数的情况下会初始化为0.0。
2.realloc函数用来增容时是在已存在的空间后追加空间,如果后面没有足够的空间的话会在内存其他的地方寻找足够的空间,并把原来的数据拷贝过去。而当realloc函数用来缩小空间时,因缩小而失去后面的空间,而这些空间里的数据则变为随机值,并且无法合法访问。
3.为避免内存泄漏,当程序最后结束的时候要对动态开辟的空间进行释放操作,也就是调用free函数。
C++动态内存管理
由于C++是面向对象的语言,因此会经常使用到类。而类里面的成员变量多数被访问限定为私有(private),无法在动态开辟的时候在类的外部直接初始化,因此C++引入了new和delete来解决这样和类似的问题。
开辟内置类型数据空间
#include <iostream>
using namespace std;
int main()
{
int* pa = new int; //申请一个元素的空间
int* pb = new int(1); //申请一个元素的空间并初始化为1
int* pArrey = new int[10]; //申请10个元素的空间
int* p = new int[10]{ 1,2,3,4,5,6,7,8,9,10 };//申请多个元素的空间并初始化
cout << *pb << endl;
delete pa;
delete pb;
delete[] pArrey; //销毁多个元素的时候要加[]
delete[] p;
return 0;
}
格式:
开辟单个元素空间:new 数据类型
开辟单个元素空间并初始化:new 数据类型(初始值)
开辟多个元素空间:new 数据类型[n],n为元素个数
开辟多个元素空间并初始化:new 数据类型[n] {num1, num2, ... ,num},n为元素个数,num1, num2, ... ,num为初始值,未初始化的元素默认为零。
注:
申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和 delete[]。
开辟自定义类型数据空间
首先先来看一段代码:
#include <iostream>
using namespace std;
class A
{
public:
A(int val = 1)
:_val(val)
{
cout << "A()" << endl; //表明只要调用A的构造函数就输出A()
_a = new int[10];
}
~A()
{
cout << "~A()" << endl; //表明只要调用A的析构函数就输出~A()
delete[] _a;
_val = 0;
}
private:
int* _a;
int _val;
};
int main()
{
A* pa = new A(10);
delete pa;
return 0;
}
运行结果:
可以看出在new和delete自定义类型数据的时候,调用的是类的构造函数和析构函数。
这也就意味着当我们自己写好构造函数时,我们在主程序里用new动态开辟空间时,会自动调用构造函数给他初始化。
再看另一种特殊情况:类里含有自定义类型的数据
#include <iostream>
using namespace std;
class A
{
public:
A(int val = 1)
:_val(val)
{
cout << "A()" << endl;
_a = new int[10];
}
~A()
{
cout << "~A()" << endl;
delete[] _a;
_val = 0;
}
private:
int* _a;
int _val;
};
class B
{
private:
A _a1;
A _a2;
};
int main()
{
B* pb = new B;
delete pb;
return 0;
}
运行结果:
可以看出调用了两次 “自定义类型A” 的构造函数和析构函数 ,只因为B里面的成员变量有两个A类数据。也就是说,new动态开辟时会自动调用自定类型数据的构造函数和析构函数。
小结:
在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。
new和delete实现原理
事实上,new和delete都是在malloc和free函数的基础上实现的,但又不是简单的直接使用malloc和free,这里就要谈到operator new 和 operator delete这两个全局函数了!
operator new 和 operator delete
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的 全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局 函数来释放空间。
operator new 和 operator delete 的代码:
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 delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void *pUserData)
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg( pUserData, pHead->nBlockUse );
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
事实上,operator new 实际也是通过malloc来申请空间,如果malloc申请空间 成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异 常。operator delete 最终是通过free来释放空间的。
内置类型的数据的实现原理
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常, malloc会返回NULL。
自定义类型的数据的实现原理
1.new
调用operator new函数申请空间,之后在申请的空间上执行构造函数,完成对象的构造。
2.delete
在空间上执行析构函数,完成对象中资源的清理工作,之后调用operator delete函数释放对象的空间。
3.new T[N]
调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请,之后在申请的空间上执行N次构造函数。
4.delete[]
在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理,之后调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间。
定位new表达式
如果我们用malloc为自定义类型的数据开辟一段空间,开辟之后并没有构造对象,只是有了相应类型的空间了。这个时候就需要使用定位new了。
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
格式
new (place_address) type或者new (place_address) type(initializer-list) place_address必须是一个指针,initializer-list是类型的初始化列表。
实例
#include <iostream>
#include<stdlib.h>
using namespace std;
class A
{
public:
A(int val = 1)
:_val(val)
{
cout << "A()" << endl;
_a = new int[10];
}
~A()
{
cout << "~A()" << endl;
delete[] _a;
_val = 0;
}
private:
int* _a;
int _val;
};
class B
{
private:
A _a1;
A _a2;
};
int main()
{
B* pb = (B*)malloc(sizeof(B));
new(pb) B;
free(pb);
return 0;
}
运行到 new(pb) B; 时的结果:
相当于实例化开辟的空间了。
malloc/free和new/delete的区别
相同点
都是从堆上申请空间,并且需要用户手动释放。
不同点
1. malloc和free是函数,new和delete是操作符。
2. malloc申请的空间不会初始化,new可以初始化。
3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可。
4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型。
5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常。
6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。