内存分配、内存溢出和内存泄漏

内存分配

1、栈区(stack)——由编译器自动分配释放 ,存放函数的参数值局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) —— 由new分配的内存块,一般由程序员分配释放, 若程序员不释放,程序结束时可能由操作系统回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3、全局区(静态区)(static)——全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量、 未初始化的全局变量和未初始化的静态变量在C++里面没有这个区分了,他们共同占用同一块内存区。
4、文字常量区(常量存储区)——常量字符串就是放在这里的, 不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多)
5、程序代码区(自由存储区)——由malloc等分配的内存块,存放函数体的二进制代码。
参考链接: 关于堆栈的讲解(我见过的最经典的)

⭐静态全局变量是位于数据段并且在程序开始运行的时候被加载。而程序的动态的局部变量则分配在堆栈里面。

⭐对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。

⭐分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。

栈分配

一个线程的栈内存是有限的,通常来说是 8M 左右(取决于运行的环境)。栈上的内存通常是由编译器来自动管理的。当在栈上分配一个新的变量时,或进入一个函数时,栈的指针会下移,相当于在栈上分配了一块内存。我们把一个变量分配在栈上,也就是利用了栈上的内存空间。当这个变量的生命周期结束时,栈的指针会上移,相同于回收了内存。
由于栈上的内存的分配和回收都是由编译器控制的,所以在栈上是不会发生内存泄露的,只会发生栈溢出(Stack Overflow),也就是分配的空间超过了规定的栈大小。

⭐由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间。

堆分配

堆上的内存是由程序直接控制的,程序可以通过 malloc/freenew/delete来分配和回收内存,如果程序中通过 malloc/new 分配了一块内存,但忘记使用 free/delete 来回收内存,就发生了内存泄露。

⭐需要程序员自己申请,并指明大小

参考链接: C++ 如何避免内存泄漏

堆、栈申请

在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

⭐只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

⭐堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。

内存溢出

官方解释:内存溢出(Out of Memory)是应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。
通俗点就是程序在申请内存时,没有足够的内存空间供其使用。

由于每一个线程的开启都要占用系统内存,因此当线程数量太多时,也有可能导致内存溢出。由于线程的栈空间也是在堆外分配的,因此和直接内存非常相似,如果想让系统支持更多的线程,那么应该使用一个较小的堆空间。

内存泄漏

官方解释:内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
通俗点就是指由于疏忽或者错误造成程序未能释放已经不再使用的内存,不再用到的内存却没有及时释放,从而造成内存上的浪费。

内存溢出是指程序在申请内存时没有足够的内存空间供其使用。原因可能如下:
(1)malloc/new申请的内存没有主动释放;
使用 malloc 申请的内存要主动调用 free,new 申请的内存要主动调用 delete,否则就会导致内存泄漏。

(2)使用free释放new申请的内存;
malloc/free以及new/delete必须是各自成对出现,如果混用,就会导致意想不到的情况出现。

(3)使用delete去删除数组;
使用 new 申请的数组,释放的时候要用 delete[] 删除,如果错误地使用 delete 删除,就会造成内存泄漏。

(4)delete掉一个void*类型的指针;
没有调用到对象的析构函数,析构的所有清理工作都没有去执行从而导致内存的泄露。

(5)基类的析构函数没有定义为虚函数;
当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,因此造成内存泄露。

⭐内存泄露是造成内存溢出的其中一个原因,但是内存泄露不一定会造成内存溢出。简单来说,内存溢出就是占用内存太大,超过了系统可以承受的范围;而内存泄露则是由于对程序运行分配的对象回收不及时甚至于脆没有被回收,久而久之,则在系统分配的堆空间里面产生了很多无用的引用。

⭐程序为了临时存取数据的需要,一般会分配一些内存空间称为缓冲区。如果向缓冲区中写入缓冲区无法容纳的数据,机会造成缓冲区以外的存储单元被改写,称为缓冲区溢出。而栈溢出是缓冲区溢出的一种,原理也是相同的。分为上溢出和下溢出。其中,上溢出是指栈满而又向其增加新的数据,导致数据溢出;下溢出是指空栈而又进行删除操作等,导致空间溢出。
**注意:**缓冲区溢出和内存溢出的区别,前者是溢出后的数据会覆盖到计算机内存中以前的内容。除非这些被覆盖的内容被保存或能够恢复,否则就会永远丢失。黑客入侵的一种就是用精心编写的入侵代码(一种恶意程序)使缓冲区溢出,然后用自己预设的方法处理缓冲区,并且执行,从而达到入侵操纵。而后者内存溢出是系统自身内存有限无法满足申请需求。

内存泄漏和内存溢出区别

相同与不同:
内存溢出:实实在在的内存空间不足导致;
内存泄漏:该释放的对象没有释放,多见于自己使用容器保存元素的情况下。
如何避免:
内存溢出:检查代码以及设置足够的空间
内存泄漏:一定是代码有问题
⭐很多情况下,内存溢出往往是内存泄漏造成的。

避免内存泄漏

1、不要手动管理内存,可以尝试在适用的情况下使用智能指针。

2、使用string而不是char*。string类在内部处理所有内存管理,而且它速度快且优化得很好。

3、除非要用旧的lib接口,否则不要使用原始指针。

4、在C++中避免内存泄漏的最好方法是尽可能少地在程序级别上进行new和delete调用–最好是没有。任何需要动态内存的东西都应该隐藏在一个RAII对象中,当它超出范围时释放内存。RAII在构造函数中分配内存并在析构函数中释放内存,这样当变量离开当前范围时,内存就可以被释放。
(注:RAII资源获取即初始化,也就是说在构造函数中申请分配资源,在析构函数中释放资源)

5、使用了内存分配的函数,要记得使用其想用的函数释放掉内存。可以始终在new和delete之间编写代码,通过new关键字分配内存,通过delete关键字取消分配内存。

内存泄漏检测

1.CRT库 为我们提供了检测和识别内存泄漏的有效方法,原理:内存分配要通过CRT在运行时实现,只要分配内存和释放内存时分别做好记录,程序结束时对比分配内存和释放内存的记录就可以确定是不是内存泄漏。

#include<crtdbg.h>// 进行内存泄漏定位调试的库
#ifdef _DEBUG //包含三行信息,可显示定位内存泄漏的行号
#define new new(_NORMAL_BLOCK,_FILE_,_LINE_)
#ENDIF

int main(){
_CrtDumpMemoryLeaks(); //调用该函数,可在窗口显示内存泄漏信息
return 0;//如果没有内存泄漏,_CrtDumpMemoryLeaks()不会显示任何信息
}

注意: debug模式下在堆上分配空间时,编译器为了调试方便会多分配36bytes,使用_CrtDumpMemoryLeaks检测时有时会误报这个36bytes的内存泄漏。

2.MFC宏定义:

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

当在debug模式下时,我们分配内存时的new被替换成DEBUG_NEW,而这个DEBUG_NEW不仅要传入内存块的大小,还要传入源文件名和行号。在程序退出时,如果发生内存泄漏,我们可以在编译器的输出界面清楚地看到发生内存泄漏的代码文件以及行号,以便于我们直接定位问题。

内存泄漏检测工具:

  1. Valgrind
  2. PurifyPlus
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值