目录
C++内存分布
分布图
说明
- 栈(堆栈):非静态局部变量/函数参数/返回值等等,栈是向下增长的;
- 内存映射段:是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信;
- 堆:用于程序运行时动态内存分配,堆是可以向上增长的;
- 数据段:存储全局数据和静态数据;
- 代码段:可执行的代码/只读常量;
内存管理
内置类型
C语言
void* malloc(总字节数)
:申请指定字节大小的空间,且无初始化,返回一个void*
指针指向空间首地址,因此在使用时需要强制转换;void* realloc(动态开辟空间的地址, 调整后的字节大小)
:在动态申请的空间的基础上,进行空间的扩容,如果第一个参数为NULL
,那么作用和malloc
一样;被扩容的指针不需要我们进行释放,我们只需释放扩容后返回的指针;void* calloc(申请元素的个数, 元素的大小)
:申请元素个数 * 元素大小的空间,并且将空间内容初始化为全 0;
void Test (){
int* p1 = (int*) malloc(sizeof(int));
free(p1);
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
free(p3 );
}
C++
type* p = new type
:申请一个指定类型的元素空间;type* p = new type(初始化内容)
:申请指定类型的单个空间,并初始化为指定内容;delete p
:释放申请的单个空间;typr* pp = new type[元素个数]
:申请指定元素个数的连续空间;delete[] pp
:释放动态申请的连续空间;
void Test(){
// 动态申请一个int类型的空间
int* ptr4 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);
// 动态申请10个int类型的空间
int* ptr6 = new int[3];
delete ptr4;
delete ptr5;
//释放连续空间
delete[] ptr6;
}
自定义类型
通过上面的内容,大家可以发现malloc / free
和new / delete
其实差别并不是太大,主要是malloc / free
使用起来更麻烦一些,所以大家可能感觉new / delete
的存在可有可无,但是其实并非这么简单;
当我们申请自定义类型空间时,就可以明显地发现,当使用malloc
时,创建出来的对象并没有初始化,也就是创建对象时没有自动调用构造函数,而在使用new
创建对象时,当对象创建好后,会自动调用构造函数,为对象初始化;并且当申请连续的自定义空间时,空间开辟好后,会自动连续的的调用构造函数,为每一个对象初始化;
同理,当时用free
释放对象空间时,并不会调用其析构函数来释放资源空间,当时用delete
时,则会自动调用对象的析构函数来释放资源,然后释放申请的空间;且当释放连续空间时,会自动连续的调用析构函数,为每一个对象释放资源,然后再将申请的空间进行释放;
class Test{
public:
Test()
: _data(0)
{
cout<<"Test():"<<this<<endl;
}
~Test(){
cout<<"~Test():"<<this<<endl;
}
private:
int _data;
};
void Test2(){
// 申请单个Test类型的空间
Test* p1 = (Test*)malloc(sizeof(Test));
free(p1);
// 申请10个Test类型的空间
Test* p2 = (Test*)malloc(sizoef(Test) * 10);
free(p2);
}
void Test2(){
// 申请单个Test类型的对象
Test* p1 = new Test;
delete p1;
// 申请10个Test类型的对象
Test* p2 = new Test[10];
delete[] p2;
}
operator new
与operator delete
函数讲解
operator new
与operator delete
是系统提供的全局函数,new
在底层调用operator new
全局函数来申请空间,delete
在底层通过operator delete
全局函数来释放空间,下面是这两个函数的底层代码:
//operator new:
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:
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
来释放空间的。
定制函数
-
内存池技术是在真正使用内存之前,预先申请分配一定数量、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的几个显著优点:(内存池技术只是简单的介绍一下,后面还会深入的学习的)
-
1.减少了与 OS 的交互,使得内存分配效率得到提升;
2.申请一大块空间,然后再根据需求从这块空间中拿出需要的,这样可以大大减少内存碎片的存在;
3.因为这是提前就申请好的,所以在使用时可以不需要去释放空间,等到最后再一次性释放空间,避免了内存泄露的问题;
定制函数就是对于那些需要频繁申请内存的类,通过重载类专属operator new
与operator delete
函数,实现使用内存池技术申请和释放内存,提高效率;在类中重载类专属operator new
与operator delete
函数可以屏蔽掉与之对应的全局函数,这样在申请空间时,会自动调用类中的重载函数,然后按照我们自己定制的函数体来完成我们的需求;下面是一个链表类的例子:
struct ListNode{
ListNode* _next;
ListNode* _prev;
int _data;
//专属类重载
void* operator new(size_t n){
void* p = nullptr;
p = allocator<ListNode>().allocate(1);
cout << "memory pool allocate" << endl;
return p;
}
void operator delete(void* p){
allocator<ListNode>().deallocate((ListNode*)p, 1);
cout << "memory pool deallocate" << endl;
}
};
class List{
public:
List(){
_head = new ListNode;
_head->_next = _head;
_head->_prev = _head;
}
~List()
{
ListNode* cur = _head->_next;
while (cur != _head)
{
ListNode* next = cur->_next;
delete cur;
cur = next;
}
delete _head;
_head = nullptr;
}
private:
ListNode* _head;
};
int main(){
List l;
return 0;
}
new
与delete
实现原理
内置类型
如果申请的是内置类型的空间,new
和malloc
,delete
和free
基本类似,不同的地方是:new / delete
申请和释放的是单个元素的空间,new[] / delete[]
申请的是连续空间,而且new
在申请空间失败时会抛异常,malloc
会返回 NULL。
自定义类型
new
的原理- 调用
operator new
函数申请空间; - 申请空间完毕后,为其调用构造函数进行初始化;
- 调用
delete
的原理- 在空间上执行析构函数,完成对象中资源的清理工作;
- 调用
operator delete
函数释放对象的空间;
new T[N]
的原理- 调用
operator new[]
函数,在operator new[]
函数中实际调用operator new
函数完成 N 个对象空间的申请; - 在申请的空间上执行 N 次构造函数;
- 调用
delete[]
的原理- 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理;
- 调用
operator delete[]
释放资源空间,实际在operator delete[]
函数中调用operator delete
来释放空间;
定位new
表达式
- 概念:定位
new
表达式是在已分配的原始内存空间中调用构造函数初始化一个对象; - 语法格式:
new (place_address) type
或者new (place_address) type(initializer-list)
,这其中place_address
必须是一个指针,initializer-list
是类型的初始化列表; - 使用场景:定位
new
表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new
的定义表达式进行显示调构造函数进行初始化;
小结
malloc / free
和new / delete
的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
malloc
和free
是函数,new
和delete
是操作符,因此在使用前者时需要包含对应的头文件,而使用后者则不用;malloc
申请的空间不会初始化,new
可以初始化;malloc
申请空间时,需要手动计算空间大小并传递,new
只需在其后跟上空间的类型即可;malloc
的返回值为void*
, 在使用时必须强转,new
不需要,因为new
后跟的是空间的类型;malloc
申请空间失败时,返回的是 NULL,因此使用时必须判空,new
不需要,但是new
需要捕获异常;- 申请自定义类型对象时,
malloc / free
只会开辟空间,不会调用构造函数与析构函数,而new
在申请空间后会调用构造函数完成对象的初始化,delete
在释放空间前会调用析构函数完成空间中资源的清理;