目录
前言
C语言中的内存管理方式
C++内存管理方式
new/delete操作内置类型:
new/delete操作自定义类型:
new申请空间同时初始化
new创建链表
new多参数传参
new失败的抛异常
位数与指针关系
operator new与operator delete函数
new和delete的实现原理
1. 内置类型
2. 自定义类型
定位new表达式(palcement-new)
后记
前言
欢迎大家再次来到小鸥的C++频道~
今天我们将讲解C++中有关内存管理的内容~
C语言中的内存管理方式
malloc/calloc/realloc/free
在我们学习C语言的时候我们知道,C语言中就是靠上述的四个函数进行动态内存管理的
下面有一段简短的代码,可以简单的回忆一次其相关内容
void Test ()
{
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );
}
由C语言知识我们知道,realloc是对p2进行扩展,而扩展分为两种情况:若为本地扩展,那么扩展成功后p3就是p2;若为异地扩展,那么realloc会自动释放p2指针。
C++内存管理方式
虽然C++也兼容C语言的内存管理方式,但具有一定的局限性,且使用较为麻烦,所以C++提供了自己的内存管理方式:使用两个新的操作符new和delete进行动态内存管理。
new/delete操作内置类型:
注:申请和释放单个元素的空间,使用new和delete操作符;申请和释放连续的空间,使用new[]和delete[],要匹配起来使用。
new/delete操作自定义类型:
class A
{
public:
A(int a = 1)
:_a(a)
{
cout << "A()" << endl;
}
A(const A& a)
{
cout << "A(const A& a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
void Print()
{
cout << "Print->" << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = new A;
A* p1 = new A[3];
delete p;
delete[] p1;
return 0;
}
结果如下:
从打印的结果可以推出:new和delete操作符不仅会申请和释放空间,并且会自动调用A类的默认构造函数和析构函数,直接就创建出了对象。
由此可见:
new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数。
new申请空间同时初始化
//申请空间时允许传参进行初始化
A* p = new A(1);
A* p1 = new A[3]{ 1, 2, 3 };
p->Print();
for (int i = 0;i < 3; i++)
{
(p1 + i)->Print();
}
delete p;
delete[] p1;
new创建链表
typedef int LNDataType;
struct ListNode
{
LNDataType _val;
ListNode* _next;
//构造函数,new操作符直接自动调用,直接替代了C语言实现链表中用于获取节点的BuyNode函数
ListNode(LNDataType x)
:_val(x)
,_next(nullptr)
{}
};
int main()
{
ListNode* n1 = new ListNode(1);
ListNode* n2 = new ListNode(2);
ListNode* n3 = new ListNode(3);
ListNode* n4 = new ListNode(4);
n1->_next = n2;
n2->_next = n3;
n3->_next = n4;
return 0;
}
new多参数传参
//多参数类
class A
{
public:
A(int a = 1,int b = 1)
:_a(a)
,_b(b)
{
cout << "A(int a = 1,int b = 1)" << endl;
}
A(const A& a)
:_a(a._a)
,_b(a._b)
{
cout << "A(const A& a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
void Print()
{
cout << "Print->" << _a << "/" << _b << endl;
}
private:
int _a;
int _b;
};
int main()
{
A* p = new A(1, 2);
//本质是隐式类型转换
//A aa = { 1, 1 };
A* p1 = new A[3]{ {1,2}, {2,3}, {3,4} };
//使用匿名对象进行初始化
A* p2 = new A[3]{ A(1,2), A(2,3), A(3,4) };
A aa1 = (1, 1);
A aa2 = (2, 2);
A aa3 = (3, 3);
//有名对象进行初始化,调用拷贝构造
A* p3 = new A[3]{ aa1, aa2, aa3 };
p->Print();
for (int i = 0; i < 3; i++)
{
(p1 + i)->Print();
}
delete p;
delete[] p1;
delete[] p2;
delete[] p3;
return 0;
}
new失败的抛异常
(实际上一般都是不会失败)
new操作符在申请空间失败后,不会像malloc一样返回空指针,而是有一个叫做”抛异常“的概念;由throw try/catch组成
//1M 约等于 100W Byte
//1G 约等于 10亿 Byte
void* p1 = new char[1024 * 1024 * 1024];
cout << p1 << endl;
void* p2 = new char[1024 * 1024 * 1024];
cout << p2 << endl;
return 0;
如果不进行捕获异常,程序就会直接报错。
使用try catch捕获异常:
在32位环境下,按照1G为单位申请空间
int main()
{
//1M 约等于 100W Byte
//1G 约等于 10亿 Byte
try
{
void* p1 = new char[1024 * 1024 * 1024];
cout << p1 << endl;
void* p2 = new char[1024 * 1024 * 1024];
cout << p2 << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
在64位环境下按照同样的1G为单位申请空间
int main()
{
try
{
int n = 1;
while(1)
{
void* p1 = new char[1024 * 1024 * 1024];
cout << n++ << endl;
}
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
try catch捕获异常,e.what() 可以返回异常原因。
位数与指针关系
由上文64位环境下的申请的空间可以知道,空间申请是先申请的虚拟内存(不然我的电脑就爆炸啦!),详细的可以自行搜索学习(我也不知道~)
operator new与operator delete函数
上文中我们讲解了new和delete操作符,他们是用户进行动态内存申请和释放的操作符;而operator new和operator delete是系统默认提供的两个函数,new和delete操作符的底层就是调用这两个函数来进行空间的申请和释放的。
/*operator new:该函数实际通过ma110c来申请空间,
当ma11oc申请空间成功时直接返回:申请空间
失败,尝试执行空
间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。*/
void *__CRTDEcL operator new(size_t size)_THROWl(_STD bad_alloc)
{
// try to allocate size bytes
void *p;
while((p = ma11oc(size)) == 0)
if(_ca1lnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_a11oc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
/*
operator delete:该函数最终是通过free来释放空间的
*/
void operator delete(void *pUserData)
{
_CrtMemBlockHeader *pHead;
RTCCALLBACK(_RTC_Free_hook,(puserpata,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和operator delete实际上也是通过使用malloc和free来实现其功能的,即new和delete的本质就是包装起来的,跟高级好用的malloc和free
new和delete的实现原理
1. 内置类型
对于内置类型来说,new与malloc,delete与free基本类似;而new与delete用于申请释放单个元素空间,new[]和delete[]用于申请释放连续的空间,且new在申请失败时会抛异常,malloc则会返回NULL指针。
2. 自定义类型
new的原理:1.先调用operator new函数申请空间;2.在申请的空间上自动调用自定义类型的构造函数,完成对对象的构造。
因此new出来的对象就已经是构造完成的。
new[]的原理:1.调用operator new[]函数,在operator new[]中调用operator new函数完成N个对象空间的申请;2.在申请的空间上执行N次构造函数完成对象的构造。
delete的原理:1.先调用对象的析构函数,完成对象中资源的清理工作;2.调用operator delete函数,释放对象空间;
delete[]的原理:1.先执行N次析构函数,完成对N个对象的资源清理;2.调用operator delete[],实际也是调用operator delete函数来释放N个对象的空间。
定位new表达式(palcement-new)
定位new表达式是用来给已经申请了空间而没有进行构造的对象空间使用的
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
实际效果和直接使用new是一样的,大多是情况使用不到
使用场景:
配合内存池来使用,内存池分配出的内存是没有进行初始化的,所以如果是自定义类型的对象,就需要使用定位new的定义式来显示调用构造函数(构造函数一般不能显示调用)
后记
再次感谢各位读者的阅读~
本篇还有不足的地方欢迎大家在评论区指出,本篇没有详细解释的部分名词大家可以自行搜索学习~
本期专栏:C++_海盗猫鸥的博客-****博客
个人主页:海盗猫鸥-****博客
感谢各位的关注~