🌎首先我们来看一看以下代码中变量在内存中的存储位置。
![]()
c/c++内存分配图:
![]()
1.栈又叫做堆栈,存储非静态局部变量/函数参数/返回值等等,栈是向下增长的。
2.内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。
3.堆用于程序运行时动态内存分配,堆上可以向上增长的。
4.数据段 - 用于存储全局数据和静态数据。
5.代码段 - 可执行的代码/只读常量。
C语言中的动态内存管理
malloc/calloc/realloc/free
![]()
1 2 3 4 5 6 7 8 9 10 | intmain()
{
int* p1 = (int*)malloc(sizeof(int));
int* p2 = (int*)calloc(4,sizeof(int));
int* p3 = (int*)realloc(p2,sizeof(int) * 10);
free(p1);
free(p2);
free(p3);
return0;
}
|
malloc/calloc/realloc的区别?
malloc - 堆上动态开辟空间
realloc - 堆上动态开辟空间 + 初始化为0 (相当于malloc + memset)
calloc - 针对已经有的空间进行扩容 (原地扩容或异地扩容)
C++的内存管理
🚀C语言的内存管理方式在c++中可以继续使用,但是有些地方使用起来就比较麻烦了。因此c++提供了自己的内存管理方式:通过new和delete操作符进行动态内存管理。 new和delete是运算符,不是函数,因此执行效率高。
new和delete操作内置类型::
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | intmain()
{
// new/delete和malloc/free 针对内置类型没有任何差别,只是用法不同
//动态申请一个int类型的空间
int* p1 =newint;
deletep1;
//动态申请一个int类型的空间并初始化为10
int* p2 =newint(10);
deletep2;
//动态申请10个int类型的空间
int* p3 =newint[10];
delete[] p3;
return0;
}
|
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]。
new和delete操作自定义类型::
![]()
注意: 在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc和free不会。
🌏对于以上总结一下:
1.c++中如果是申请内置类型对象或者数组,malloc和new没有什么区别。
2.如果是自定义类型,那么区别很大,new和delete是开空间 + 初始化,析构清理 + 释放空间,malloc和free仅仅是开空间 + 释放空间。
3.建议在c++中,无论是自定义类型还是内置类型的申请和释放,尽量使用new和delete。
operator new与operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,operator new和 operator delete是系统提供的全局函数,new在底层调用operator new 全局函数来申请空间,delete在底层提供operator delete全局函数来释放空间。
如下是c++官方对于这两个函数的描述:
![]()
operator new和operator delete的实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | /*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,
尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void* __CRTDECL operatornew(size_tsize) _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 类型异常
staticconststd::bad_alloc nomem;
_RAISE(nomem);
}
return(p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
voidoperatordelete(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来释放空间的。
operator new的使用案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | structListNode
{
ListNode(intdata = 0)
:_next(nullptr)
,_prev(nullptr)
,_data(data)
{}
ListNode* _next;
ListNode* _prev;
int_data;
};
//operator new的用法跟malloc和free是一样的,都是在堆上申请空间
//只是申请空间失败后的处理方式不一样,malloc失败返回NULL,operator new失败以后抛异常
intmain()
{
//C语言
ListNode* p1 = (ListNode*)malloc(sizeof(ListNode));
free(p1);
//c++
ListNode* p2 = (ListNode*)operatornew(sizeof(ListNode));
operatordelete(p2);
int* p3 = (int*)malloc(100000000000000000);
if(p3 == NULL)
{
cout <<"malloc fail"<< endl;
}
try
{
int* p4 = (int*)operatornew(100000000000000000);
}
//开辟空间失败,捕获异常信息
catch(exception& e)
{
cout << e.what() << endl;
}
return0;
}
|
operator delete的使用案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | classA
{
public:
A(inta = 0)
{
cout <<"A()"<<this<< endl;
}
~A()
{
cout <<"~A()"<<this<< endl;
}
private:
int_a;
};
intmain()
{
//c语言 -> A* p = (A*)malloc(sizeof(A));
//等价于直接用 A* p = new A;
A* p = (A*)operatornew(sizeof(A));
new(p)A;// new(p)A(2); 定位new,placement-new,显示调用构造函数初始化这块空间对象
//等价于 delete p
p->~A();//析构函数可以显示调用
operatordelete(p);
return0;
}
|
operator new与operator delete的类专属重载
内存池:内存池的主要作用是提高效率。通过一次性申请比较大的空间,来避免小空间内存的频繁申请和释放,每次需要为对象分配内存空间时,在已经申请好的大的空间内分配。空闲区被按照对象大小划分为若干块,每个块之间通过链表连接起来。
☑️以下代码演示了,针对链表的节点ListNode通过重载类专属 operator new / operator delete ,实现链表节点使用内存池申请和释放内存,提高效率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | structListNode
{
void* operatornew(size_tn)
{
void* p = nullptr;
p = allocator<ListNode>().allocate(1);
cout <<"memory pool allocate"<< endl;
returnp;
}
voidoperatordelete(void* p)
{
allocator<ListNode>().deallocate((ListNode*)p, 1);
cout <<"memory pool deallocate"<< endl;
}
ListNode* _next;
ListNode* _prev;
int_data;
};
classList
{
public:
List()
{
_head =newListNode();
_head->_next = _head;
_head->_prev = _head;
}
~List()
{
ListNode* cur = _head->_next;
while(cur != _head)
{
ListNode* next = cur->_next;
deletecur;
cur = next;
}
delete_head;
_head = nullptr;
}
private:
ListNode* _head;
};
intmain()
{
List h;
return0;
}
|
new和delete的实现原理
内置类型:
若申请的是内置类型的空间,new和malloc,delete和free基本相似。new/delete 申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间。new在空间申请失败时会抛异常,malloc申请空间失败则返回NULL。