引入(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* ptr4 = new int;
int* ptr5 = new int(10);
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;
};
void Test2()
{
Test* p1 = new Test;
delete p1;
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:
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:
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)
{
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
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);
__TRY
pHead = pHdr(pUserData);
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg( pUserData, pHead->nBlockUse );
__FINALLY
_munlock(_HEAP_LOCK);
__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;
前面说过,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;
Stack* ps = new Stack;
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)
场景示例 : 先看下列代码:
A a[5];
A* pb1 = new A[5];
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在释放空间前会调用析构函数完成空间中资源的清理
6)内存泄漏
|