- C/C++的内存分布
- C语言中动态内存管理
- 申请堆空间
- malloc:在堆上直接分配 n 字节大小的空间
- calloc:在堆上,分配n*size个字节,并初始化为 0
- recalloc:重新分配堆上的 void指针p 所指的空间为 n 个字节
- 释放堆空间——free
- malloc实现原理
- free函数如何知道释放多少个空间?
- C++中动态内存管理
- `new/delete`和`malloc/free`的区别&联系:
- new/delete 动态开辟普通变量
- new[ ]/delete[ ] 动态申请一段连续的空间
- new/delete 动态开辟自定义类型对象
- new和delete的实现原理
- `operator new()`与`operator delete()`函数
- `operator new[]()`与`operator delete[]()`函数
- new / delete 底层流程
- 定位new表达式(placement-new)
C/C++的内存分布
- 每个进程都有这样的虚拟内存分布方式;
- C++中的new 与 new[]都是在堆上开辟(同C语言的malloc、calloc、realloc ...);
- OS之所以将完整的内存划分为这样不同的区域,是为了方便对内存空间的管理;
C语言中动态内存管理
- C标准函数库中,常见的堆上内存管理函数有malloc(), calloc(), recalloc(), free();
- 注意这些函数使用是大多需要类型强转;
- C语言动态内存管理所有接口的头文件
; - 堆上的内存空间不会自动释放,直到调用free()函数,才会释放堆上的存储空间;
申请堆空间 malloc:在堆上直接分配 n 字节大小的空间
- 原型:void * malloc(int n);
- 含义:在堆上,分配n个字节,并返回void指针类型;
- 返回值:
分配成功:返回堆空间首地址;
分配失败:返回NULL;
calloc:在堆上,分配n*size个字节,并初始化为 0
- 原型:void *calloc(int n, int size);
- 含义:在堆上,分配 n*size 个字节,并初始化为0,返回void* 类型;
- 返回值:
分配成功:返回堆空间首地址;
分配失败:返回NULL;
recalloc:重新分配堆上的 void指针p 所指的空间为 n 个字节
- 原型:void * realloc(void * p,int n);
- 含义:重新分配堆上的void指针p所指的空间为n个字节,同时会复制原有内容到新分配的堆上存储空间。
注意,若原来的void指针p在堆上的空间不大于n个字节,则保持不变; - 返回值:
分配成功:返回堆空间首地址;
分配失败:返回NULL;
释放堆空间——free
- 原型:void free (void * p);
- 含义:释放void指针p所指的堆上的空间。
- 返回值:无
- 任何通过malloc、calloc、realloc开辟的堆空间,在使用结束前必须使用free()接口释放内存,否则会发生内存泄漏;
malloc实现原理
// 有时间补充代码展示
free函数如何知道释放多少个空间?
- 因为在堆上开辟的空间会多余一部分用来存储这块空间的信息,即堆空间的管理结构体;
- 释放时会偏移的结构体的首地址释放空间;
C++中动态内存管理
- C++的动态内存管理方式完全兼容C语言的所有方式;
- C++特有方式:new申请堆空间、delete释放堆空间,两个关键字(也可以看作是操作符,但不是函数);
new/delete和malloc/free的区别&联系:
- malloc/free是C/C++标准库的函数,new/delete是C++操作符(操作符的使用不需要头文件);
- new直接跟类型,malloc跟字节数个数;
- new申请出堆空间后不需要判空,malloc必需判空(具体原因下面会说);
- malloc/free需要手动计算类型大小且返回值为void*,new/delete可自己计算类型的大小并返回对应类型的指针;
- new申请空间时可以顺便将对象初始化为任意值,calloc只能将每个字节初始化为0;
- new和delete就像malloc和free一样,都要成对使用;
- C语言与C++特有的动态管理内存的方式,开辟与释放不能混用(若不匹配使用,可能会造成内存泄漏或程序崩溃)(具体原因下面会说);
- 一般情况下,在C++中使用其特有的方式管理动态内存(更简单、更强大);
new/delete 动态开辟普通变量
new[ ]/delete[ ] 动态申请一段连续的空间
- 注意在堆上开辟二维数组,不能直接这样定义int **array = new int[m][n];
- new[] 与 delete[]也要成对使用;
new/delete 动态开辟自定义类型对象
这里有一个类,我们想在堆上开辟该类对象
// new & delete 自定义对象
class Test
{
public:
// 无参构造函数
Test()
{
cout << "无参构造函数,this:" << this << endl;
}
// 带参构造函数
Test(int a) :_a(a){}
// 析构函数
~Test()
{
cout << "析构函数,this:" << this << endl;
}
private:
int _a;
};
【测试 1】采用malloc之类的C语言方式在堆上开辟类对象,由于是C语言标准库的函数,没有类与对象这类自定义类型的概念,因此:
- malloc只是从堆上申请了指定大小的空间,并没有调用构造函数初始化该对象;
- free只是将该内存归还给OS,并没有调用析构函数清理对象资源;
【测试 2】采用new和delete在堆上开辟类对象:
- 这是C++中管理动态内存的关键字,会调用operator new && operator delete 函数开辟堆空间;
- new会开辟对象大小的空间,并会调用构造函数初始化对象;
- delete是释放对象大小空间,释放前先会调用析构函数清理对象空间;
- 注意new与delete的特性:先构造的后释放;
结论:C++中定义一个对象:
- 如果在栈上开辟这个对象,那么编译器在代码编译阶段直接在栈帧上会为这个局部变量开辟空间,再通过构造函数来初始化这个对象;
- 栈上释放这个对象,先通过析构函数清理对象资源,再通过编译器回收栈帧空间;
- 如果在堆上开辟这个对象,new操作符先通过调用operator new()函数在堆上开辟空间,再调用构造函数初始化该空间;
- 堆上释放这个对象,delete操作符先通过析构函数清理对象资源,在调用operator delete函数回收堆上空间;
new和delete的实现原理
- new 和 delete既是一个关键字,也是一个操作符,那么就可以重载;
- operator new()与operator delete()函数是系统提供的全局重载函数;
- new在底层通过调用operator new全局函数来申请空间;
- delete在底层通过调用operator delete全局函数来释放空间;
- 如果堆上申请的是自定义类对象,new在调用operator new之后还会调用构造函数初始化对象;delete会先调用析构函数销毁对象后,再调用operator delete;
注:
- 本博客代码测试环境均在VS2013,若版本更高,operator new函数文件可能进一步封装,而无法轻易观测;
- 下列函数的源码是通过VS安装目录的源文件找到,实际代码调试并不会直接进入;
operator new()与operator delete()函数
operator new[]()与operator delete[]()函数
new / delete 底层流程
定位new表达式(placement-new)
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
// 定位new表达式
class Test
{
public:
// 无参构造函数
Test()
{
cout << "无参构造函数,this:" << this << endl;
}
// 带参构造函数
Test(int a) :_a(a)
{
cout << "带参构造函数,this:" << this << endl;
}
// 析构函数
~Test()
{
cout << "析构函数,this:" << this << endl;
}
private:
int _a;
};
int main()
{
// 通过malloc开辟一段对象大小的空间,但只是一个空间
Test* pt_1 = (Test*)malloc(sizeof(Test));
Test* pt_2 = (Test*)malloc(sizeof(Test));
// 通过定位new表达式为指定空间初始化对象
new(pt_1)Test; //调用无参构造函数
new(pt_2)Test(10); // 调用带参构造函数
// 主动调用析构
pt_1->~Test();
free(pt_1);
pt_2->~Test();
free(pt_2);
return 0;
}
对于堆空间的管理,经常容易引发内存泄漏问题,这对于编程是很难避免的。但仍有一系列方法可以检测和避免内存泄漏,具体内容详见后续博客~



