内存管理
引入(C部分回顾)
内核空间
:
属于操作系统范畴栈
:(向下)
函数调用建立栈帧,参数,函数中的局部变量都存在栈帧中堆
:(向上)
理论上而言,后malloc的内存地址比先malloc的大,但不一定,因为下一次申请的空间可能是之前释放的数据段
:
通常用来存放程序中已初始化的(非 0)全局变量和静态局部变量。数据段的起始位置由链接定位文件确认,大小在编译链接时自动分配。数据段属于静态内存分配代码段
:
代码段在内存中被映射为只读。它是由编译器在编译链接时自动计算的。通常是用来存放可执行代码(二进制指令)和只读常量,代码段输入静态内存分配
对于图中的这个区域划分
:
以32位4G为例
- 假如内核占1G,剩下的3G,大部分都是堆
- 栈很小,linux下一般只有8M,所以当递归调用太深会出现栈溢出
- 数据段和代码段不是很大
注意
:
上图中的char_v字符数组是存储在栈上的,char_v是首元素地址解引用,为‘a’同样是在栈上(注意区分ptr_char_v和*char_v)
malloc/calloc/realloc区别
:
calloc等价于:malloc+memset(0)+初始化
realloc是对malloc或calloc的空间进行扩容
C++内存管理
C语言内存管理方式在C++中可以继续使用
,但有些地方就无能为力而且使用起来比较麻烦,因此C++又提出自己的内存管理方式:通过new
和delete
操作符
进行动态内存管理
1)new delete
new delete 对于内置类型
实现原理
:
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL用法
: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 对于自定义类型
实现原理
:
new:
调用operator new函数申请空间,在申请的空间上执行构造函数,完成对象的构造delete:
在空间上执行析构函数,完成对象中资源的清理工作,调用operator delete函数释放对象的空间new[]:
调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请,在申请的空间上执行N次构造函数delete[]:
在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理,调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
用法
:class Test { public: Test() : _data(0) { cout << "Test():" << this << endl; } ~Test() { cout << "~Test():" << this << endl; } private: int _data; }; //new/delete对于自定义 void Test2() { // 申请单个Test类型的对象 Test* p1 = new Test; delete p1; // 申请10个Test类型的对象 Test* p2 = new Test[10]; delete[] p2; }
与malloc/free对比
new/delete会调用自定义类型的构造和析构函数
而malloc/free不会调用
可以看到一共只有new/delete调用的11对构造,析构函数
注意
:
- 一定要匹配(
malloc对应free
,new对应delete
,new[]对应delete[]
)使用,否则可能
会崩溃 (测试表明内置类型不会崩溃,而自定义类型
会崩溃)
- 在C++,建议尽量使用new/delete
2)operator new与operator delete函数
malloc/free和new/delete对于空间申请失败的处理方式
引入
:malloc/free和new/delete对于空间申请失败的处理方式不同:
对于malloc/free:
// malloc失败,返回NULL char* p1 = (char*)malloc((size_t)2*1024*1024*1024); if (p1 == NULL) { printf("malloc fail\n"); } else { printf("malloc success:%p\n", p1); }
对于new/delete:
// new与malloc不一样,申请空间失败,抛异常 try { char* p2 = new char[0x7fffffff]; // 出错,抛异常,跳到捕获异常的位置 printf("xxxx");//不会执行之后的语句 } catch (const exception& e) { cout << e.what() << endl; }
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 new函数是对malloc的封装,并且加上了抛异常
注意
:
- 这不是运算符重载
- 不是new,不会调用构造函数(
new=operator new+构造函数
)
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一样调用_free_dbg _free_dbg( pUserData, pHead->nBlockUse ); __FINALLY _munlock(_HEAP_LOCK); /* release other threads */ __END_TRY_FINALLY return; }
Debug版本的 CRT定义了一套调试版本的内存分配函数(如_malloc_dbg)。当你包含了CRTDBG.h后,如果当前是Debug工程,十有八九会有_DEBUG宏,我们找到CRTDBG.h
发现free同样是调用_free_dbg注意
:
- 该函数最终是通过free来释放空间的
- 不是delete,不会调用析构函数(
delete=析构函数+operator delete
)
operator new与operator delete的类专属重载
引入
:编写一个检测有没有ListNode的节点没有释放的程序并计数
部分代码(重载部分)如下:A(int a = 0) { cout << "A()" << this << endl; } void* operator new(size_t n) { _count++; return ::operator new(n); } void operator delete(void* p) { _count--; return ::operator delete(p); } private: static int _count; }; int A:: _count = 0;//static初始化
前面说过,new/delete是关键字
也是运算符
这样重载new和delete后,就是相当于在用new/delete的时候调用运算符重载,相当于:调用operator new/operator delete函数,同时加上计数操作
,最后打印_count即可
3)new/delete结合实际情况分析
class Stack { public: Stack(int capacity = 4) :_a(new int[capacity]) , _size(0) , _capacity(capacity) { cout << "Stack(int capacity = 4)" << endl; } ~Stack() { delete[] _a; _size = _capacity = 0; cout << "~Stack()" << endl; } private: int* _a; int _size; int _capacity; }; int main() { Stack st;//语句1 Stack* ps = new Stack;//语句2 delete ps; return 0; }
分析语句1,2
分析
:
语句1
:Stack st
,在main函数栈帧里实例化一个对象st,调用他的构造函数初始化列表时new int[capacity]
(new调用operator new,operator new调用malloc) 会在堆上开辟一块capicity个int
的空间,当出了作用域,自动调用析构函数在函数里delete[] _a
会销毁堆上的空间(所谓的清理资源),出了main函数,st也被销毁语句2
:Stack* ps = new Stack;
是在main函数栈帧上定义一个4字节的指针ps,同时new一个12字节的Stack类(由于是new,会在堆上开辟这12字节的空间),同时再去调用他的构造函数,new int[capacity]
在堆上再开辟capicity个int的空间,将空间给_a,返回给ps指针。当我们要释放空间的时候,如果用free会只释放栈上的空间,所以用delete才能正确释放,先调用析构函数,在调用operator delete进行释放空间
4)定位new表达式placement-new
概念
: 定位new表达式是在已分配的原始内存空间
中调用构造函数初始化一个对象
使用
:new (place_address) 类 new (place_address) 类(initializer-list) //place_address:一个指针 //initializer-list:类型的初始化列表
场景示例
:
先看下列代码:A a[5]; A* pb1 = new A[5];// 复制一份a数组到另外一块空间b for (int i = 0; i < 5; ++i) { pb1[i] = a[i]; }
// 直接拷贝构造 A* pb2 = (A*)malloc(sizeof(A) * 5); for (int i = 0; i < 5; ++i) { new(pb2 + i)A(a[i]); }
5)总结malloc/free new/delete作用
- malloc和free是函数,new和delete是操作符
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
- malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间
后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理