void Func(void) {
A* a = new A;
if (a == NULL) {
exit(1);
六丶内存泄漏
内存泄漏:由于疏忽或错误导致的程序未能释放已经不再使用的内存
进一步解释:并非指内存从物理上消失,而是指程序在运行过程中,由于疏忽或错误而失去了对该内存的控制,从而造成了内存的浪费
内存泄露的分类:
1.堆内存泄露:堆内存指的是程序运行中根据需要分配malloc,realloc,new等从堆中分配的一块内存,再是完成后必须通过调用对应的free或者delete删掉,如果程序的设计的错误到这这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生堆内存泄露
2.系统资源泄露:主要指程序使用系统分配的资源比如bitmap,handle,socket等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定
3.没有将基类的析构函数定义为虚函数。当基类指针指向子类对象的时候,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确释放,因此造成内存泄漏
内存泄漏和内存溢出的区别和联系
1.内存泄漏是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出
2.内存溢出:所谓的内存溢出指的是程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错,比如说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也会产生空间溢出,成为下溢
防止内存泄漏的方法
1.内部封装:
将内存的分配和释放封装到类中,在构造的时候申请内存,析构的时候释放内存
说明:这样做并不是最佳做法,在类的对象赋值的时候,程序会出现同一块内存空间释放两次的情况
可以增加引用计数来避免这种情况
2.智能指针
智能指针是C++中已经对内存泄漏封装好了的一个工具
检测内存泄露
请你回答一下如何判断内存泄漏
1.一方面可以使用Linux环境下的内存泄漏检查工具valgrind,另一方面我们在写代码时可以添加内存申请和释放的统计功能,统计当前申请和释放的内存是否一致,来判断内存是否泄露
2.windows使用运行时库CRT,可以定位到内存泄露的地方
如何定位内存泄漏
C++程序缺乏相应的手段来检测内存信息,只能通过使用top指令观察进程的动态内存总额,而且程序退出时,我们无法获知任何内存泄露的信息
内存泄漏检测工具的实现原理
这里重点介绍一下valgring
valgrind是一套Linux下开发源码的仿真调试工具集合,包括以下工具
1.Memcheck:内存检查器,能够发现开发中绝大多数内存错误的使用情况,比如:使用未初始化的内存,使用已经释放的内存,内存访问越界等
2.Callgrind:检查程序中函数调用过程中出现的问题
3.Cachegrind:检查程序中缓存使用出现的问题
4.Helgrind:检查多线程中出现的竞争问题
5.Massif:检查程序中堆栈使用中出现的问题
6.Extension:可以利用core提供的功能,自己编写特定的内存调试工具
Memcheck能够检测出内存问题,关键在于其建立了两个全局表:
Valid-Value表:对于进程的整个地址空间中的每一个字节,都有与之对应的8bits,对于CPU的每个寄存器,也有一个与之对应的bit向量,这些bits负责记录该字节或者寄存器值是否具有有效的,已初始化的值
Valid-Address表:对于进程整个地址空间中的每一个字节(byte),还有与之对应的1个bit,负责记录地址是否能够被读写
检测原理:
1.当要读写内存中的某个字节时,首先检查这个字节对应的Valid-Address表中对应的bit。如果该bit显式该位置是无效位置,Memcheck则报告读写错误
2.内核类似一个虚拟的CPU环境,这样当内存中的某个字节被加载到真实的CPU中时,该字节在Valid-Value表中对应的bits也被加载到虚拟的CPU环境中,一旦寄存器中的值,被用来产生内存地址,或者该值能够影响程序输出,则Memcheck会检查Valid-Value表对应的bits,如果该值尚未初始化,则会报告使用未初始化内存错误
malloc原理,brk系统调用和mmap系统调用的作用分别是什么
malloc函数用于动态分配内存,为了减少内存碎片和系统调用的开销,malloc采用了内存池的方式,先申请大块的内存作为堆区,然后将堆区区分为多个内存块,以块作为内存管理的基本单位,当用户申请内存时,直接从堆区分配一块合适的空闲块,malloc采用隐式链表结构将堆区分成连续的,大小不一的块,包含已分配块和未分配块,同时malloc采用显示链表结构来管理所有的空闲块,即使用一个双向链表将空闲块链接起来,每一个空闲块记录了一个连续的,未分配的地址
当进行内存分配时,malloc会通过隐式链表遍历所有空闲块,选择满足要求的的块进行分配,当进行内存合并时,malloc采用边界标记法,根据每个块的前后块是否已经分配来决定是否进行块合并
malloc在申请内存时,一般会通过brk或者mmap系统调用进行申请。其中当申请的内存小于128k时,会使用系统函数调用brk(),通过移动_enddata来实现堆区中的分配;而当申请内存大于128k时会使用系统函数mmap在虚拟地址空间映射区分配
malloc的底层实现
brk()函数实现原理:像高地址的方向移动指向数据段的高地址的指针_enddata
mmap内存映射原理:
1.进程启动映射过程,并在虚拟低地址空间中为映射创建虚拟映射内存;
2.调用内核空间的系统调用函数mmap(),实现文件物理地址和进程虚拟地址的一一映射关系
3.进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝
malloc,calloc,realloc,alloca
1.malloc:申请指定字节数的内存。申请到的内存中的初始值不确定
2.calloc:为指定对象长大夫,分配能容纳其指定个数的内存,申请到的内存中的每一位(bit)都初始化为0
3.realloc:更改以前分配的内存长度(增或减)。当增加长度时,可能需将以前分配区的内容移到另一个足够大的区域,而新增区域内的初始值不确定
4.alloca:在栈上申请内存。程序在出栈的时候,会自动释放内存,但需要注意的是,alloca不具有可移植性,而且没有在传统堆栈的机器上很难实现。alloca不宜使用在必须广泛移植的程序中。
new和malloc的区别
特征 | new/delete | malloc/free |
---|
分配内存的位置 | 自由存储区 | 堆 |
内存分配失败返回值 | 完美类型指针 | void* |
内存分配失败安全性 | 默认抛出异常 | 返回NULL |
分配内存大小 | 编译器根据类型计算得出 | 必须显式的指定字节数 |
处理数组 | 有处理数组的new[]版本 | 需要用户计算数组的大小后进行内存分配 |
已分配内存的扩充 | 无法直观的处理 | 使用realloc简单完成 |
是否相互调用 | 可以,看具体的operator new/delete实现 | 不可以调用new |
分配内存时内存不足 | 客户能够指定处理函数或者重新制定分配器 | 无法通过用户代码进行处理 |
函数重载 | 允许 | 不允许 |
构造函数与析构函数 | 调用 | 不调用 |
2.堆(Heap),通过new 申请的内存,由delete或delete[]释放
3.自由存储区(Free Storage),由程序员用malloc,calloc/realloc分配,free进行释放,忘记free,会导致内存泄漏,程序结束时,该区域内存由OS回收
4.全局区/静态区(Global Static Area),全局变量和静态变量存储区。程序编译完成后,该区域便存在,在C++中,由于全
文章目录:c/c++内存分布C语言中的动态内存管理c++的内存管理operator new与operator delete函数
c/c++内存分布
🌎首先我们来看一看以下代码中变量在内存中的存储位置。
c/c++内存分配图:
1.栈又叫做堆栈,存储非静态局部变量/函数参数/返回值等等,栈是向下增长的。
2.内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。
3.堆用于程序运行时动态内存分配,堆上可以向上增长的。
4.数据段 - 用于存储全局数据
目录内存内存分区模型代码区:存放函数体的二进制代码 由操作系统进行管理代码区特点:全局区:存放全局变量和静态变量以及常量栈区:由编译器自动分配释放 存放函数的参数值 局部变量等堆区:由程序员分配和释放 也可程序结束时由操作系统回收分四个区的意义
内存分区模型
内存大致分为四个区域
在代码运行前就存在 代码区和全局区 在运行之后才有堆区和栈区
代码区:存放函数体的二进制代码 由操作系统进行管理
存放了CPU的执行的机器指令(就是把你在编译器内写的代码翻译成一个机器可以执行的二进制命令 存放在代码区)
写服务端的,内存是一个绕不过的问题,而用C++写的,这个问题就显得更严重。进程的内存持续上涨,有可能是正常的内存占用,也有可能是内存碎片,而C++写的,还有可能是内存泄漏,那就需要一些方法来检测到底是哪些问题引起的。
内存耗尽和处理方式,指针为NULL的情况,内存分配失败
堆内存耗尽和处理方式:
如果在申请动态内存时找不到足够大的内存块,malloc和new都会返回空指针宣告内存申请失败。
因此只需要用if(NULL == p)判断一下就可以知道指针是否申请到了内存,然后进行处理,
1、是返回当前函数并做出提示,
2、是写入日志并终止进程(exit(1)语句),
3、设置异常处理函数等。
方法1、2很常见,但方法1在函数需要申请多个内存时不好用,因为函数返回以后之前申请成功的内存因失去控制而泄露,故应使用方法2。
栈区(stack):由编译器自动分配与释放,存放为运行时函数分配的局部变量、函数参数、返回数据、返回地址等。其操作类似于数据结构中的栈。
堆区(heap):一般由程序员自动分配,如果程序员没有释放,程序结束时可能有OS回收。其分配类似于链表。
全局区(静态区static):存放全局变量、静态数据、常量。程序结束后游戏厅释放。全局区分为已初...
②.栈:又叫做堆栈,非静态局部变量/函数形参/返回值/表达式中间结果/某些寄存器信息等等,栈是向下增长的.
③.内存映射段: 是高效的I/O映射方式,用于装载一个共享的动态内存库,用户可以使用系统接口创建共享内存,进行进程间通信.
④.堆:用于程序运行时动态内存分配,堆是向上增长的.
⑤.数据段:存储全局数据和静态数据...