C++内存管理
C++的内存分配方式
c++内存分配分为堆区,栈区,自由存储区(代码区),全局区(静态区),常量区5部分
堆:堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。
栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限,一般以为MB为单位,一般是1MB。
自由存储区:自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区,一般自由存储区指向堆区,但可以修改自由存储区指向。
全局/静态存储区:这块内存是在程序编译的时候就已经分配好的,在程序整个运行期间都存在。例如全局变量,静态变量。
常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量(const),不允许修改。
New/delete 和malloc/free 的区别
特征 |
new/delete |
malloc/free |
操作对象 |
运算符 |
标准库函数 |
分配内存的位置 |
自由存储区 |
堆 |
内存分配成功的返回值 |
完整类型指针 |
void* |
内存分配失败的返回值 |
默认抛出异常 |
返回NULL |
分配内存的大小 |
由编译器根据类型计算得出 |
必须显式指定字节数 |
处理数组 |
有处理数组的new版本new[] |
需要用户计算数组的大小后进行内存分配 |
已分配内存的扩充 |
无法直观地处理 |
使用realloc简单完成 |
是否相互调用 |
可以,看具体的operator new/delete实现 |
不可调用new |
分配内存时内存不足 |
客户能够指定处理函数或重新制定分配器 |
无法通过用户代码进行处理 |
函数重载 |
允许 |
不允许 |
构造函数与析构函数 |
调用 |
不调用
|
堆区和自由存储区的区别
从技术上来说,堆(heap)是C语言和操作系统的术语。堆是操作系统所维护的一块特殊内存,它提供了动态分配的功能,当运行程序调用malloc()时就会从中分配,稍后调用free可把内存交还。而自由存储是C++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区。基本上,所有的C++编译器默认使用堆来实现自由存储,也即是缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现,这时藉由new运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确。但程序员也可以通过重载操作符,改用其他内存来实现自由存储,例如全局变量做的对象池,这时自由存储区就区别于堆了。我们所需要记住的就是:
- 堆是C语言和操作系统的术语、是操作系统维护的一块内存,而自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。堆与自由存储区并不等价。
- new所申请的内存区域在C++中称为自由存储区。藉由堆实现的自由存储,可以说new所申请的内存区域在堆上。
堆和栈的区别
- 生长方式:堆的生长方向是向上的,也就是向着内存地址增加的方向;而对于栈,其的生长放下是向下的,向着内存地址减小的方向生长。
- 管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。
- 空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,默认的栈空间大小是1M(VS2017 项目-属性-链接器-系统可以修改)。
- 碎片问题:对于堆来讲,频繁的malloc/free势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。栈是先进后出的队列,以至于永远都不可能有一个内存块从栈中间弹出。
- 分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
- 分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
内存泄露
总结: 一是在堆里创建了对象占用了内存,但是没有显式地释放对象占用的内存;二是在类的构造函数中动态的分配了内存,但是在析构函数中没有释放内存或者没有正确的释放内存;
a. 在释放对象数组时在delete中没有使用方括号;
b. 对象数组是指:数组中存放的是对象,只需要delete []p,即可调用对象数组中的每个对象的析构函数释放空间;
c. 指向对象的指针数组是指:数组中存放的是指向对象的指针,不仅要释放每个对象的空间,还要释放每个指针的空间,delete []p只是释放了每个指针,但是并没有释放对象的空间,正确的做法,是通过一个循环,将每个对象释放了,然后再把指针释放了;
d. 没有将基类的析构函数定义为虚函数。基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,因此造成内存泄露;
内存溢出
内存溢出是指程序在申请内存时没有足够的内存空间供其使用。原因可能如下:
a. 内存中加载的数据过于庞大;
b. 代码中存在死循环;
c. 递归调用太深,导致堆栈溢出等;
d. 内存泄漏最终导致内存溢出;
参考链接
【1】https://blog.csdn.net/caogenwangbaoqiang/article/details/79788368
【2】https://www.cnblogs.com/BEN-LK/p/10720265.html
【3】https://www.cnblogs.com/learning-zjx/p/10645659.html
【4】https://www.jianshu.com/p/19771f5a89ea