一丶内存分配方式
C++中内存分为五个区,分别是栈(stack),堆(heap),自由存储区,全局/静态存储区(bss),常量存储区

:在执行函数时,函数内局部变量的存储单元,函数参数都可以在栈上创建,函数执行结束时这些存储单元自动被释放,栈内存分配运算内置于处理器指令集中,效率高,但分配内存容量有限。
:那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete,如果程序员没有释放掉,程序结束后,操作系统会自动回收(内存泄漏不是系统无法回收那片内存,而是你自己的应用程序无法使用那片内存。操作系统本身就有管理内存的职责,在进程结束后,操作系统会自动回收内存的)
自由存储区 :就是那些由malloc等分配的内存块,他和堆是十分相似的,不过他是用free来结束自己的生命的
引申出来的问题:
很多编译器中new/delete都是以melloc和/free为基础实现的,那么就会引申出一个问题,自由存储区和堆相同么?
答案:堆是操作系统维护的一块内存,而自由存储时c++中通过new和delete动态分配和释放对象的抽象概念,堆和自由存储区并不等价
全局/静态存储区
全局变量和静态变量被分配到同一块内存中,在以前的c语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区
常量存储区
这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改

二丶堆和栈的区别

void func() {
    int* p = new int[5];

在这条语句里,看到了new就想到分配了一块堆内存,而指针p分配了一块栈内存。这句话的意思是,在栈内存中存放了一个指向对内存的指针p

堆和栈的区别主要分为以下六点:
1.管理方式不同:
对于栈来说,是编译器自动管理的,不需要我们手动控制
对于堆来说,释放工作由程序员控制,容易产生内存泄漏
2.空间大小:
一般在32位系统下,堆内存可以达到4g空间
对于栈来说,一般是有一定的空间大小,vc下面栈的空间默认1M(可以通过编译器设置修改)
3.碎片问题:
对于堆来说,频繁的new/delete势必会造成内存空间不连续,从而造成大量碎片,使程序效率降低
对于栈来说,不会存在这个问题,因为栈使先进后出的队列,他们一一对应,永远不可能由一个内存块从栈中间弹出,在他弹出之前,它上面的后进的栈内容已被弹出
4.生长方向:
对于堆来说,生长方向是向上的,也就是向着内存地址增加的方向
对于栈来说,它的生长方向是向下的,是向着内存地址减小的方向增长
5.分配方式不同:
堆都是动态分配的
栈有两种分配方式动态分配和静态分配,静态分配是编译器完成的,比如局部变量的分配,动态分配是由alloca完成的,栈的动态分配是由编译器进行释放,无需我们手动实现
6.分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。
堆则是C/C++函数库提供的,他的机制很是复杂,例如为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间,就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分配到足够大小的内存,然后进行返回,显然,堆的效率比栈要低的多

虽然栈有众多好处,但是和堆比不是那么灵活,有时候分配大量的内存空间,还是堆好一些

三丶常见的内存错误及其对策
1.内存分配未成功,却使用了它
常见的解决办法是在使用内存之前检查指针是否为NULL。如果指针是函数的参数,那么在函数入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存,应该用if(p==NULL)或if(p!=NULL)进行防错处理

2.内存分配虽然成功,但是尚未初始化就引用它
犯这种错误主要是有两个起因:一是没有初始化的概念,二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。内存的缺省初值究竟是什么没有统一的标准,尽管有时候为零值,我们宁可信其无不可信其有。所以无论如何用哪种方式创建数组,都别忘了赋初值,即使是赋零值也不可省略,不要嫌麻烦。

3.内存分配成功已经初始化,但操作越过了内存的边界
例如在使用数组时经常发生下标”多1“或者”少1“的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组越界

4.忘记了释放内存,造成内存泄露
含有这种错误的函数每被调用一次就会丢失一块内存,刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。动态的申请和释放必须配对,程序中malloc与free的次数一定要相同,否则肯定有错误

5.释放了内存却继续使用它
有三种情况:
1)程序中的对象调用过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理混乱的局面
2)函数的return语句写错了,注意不要返回指向”栈内存“的指针”或者引用“,因为该内存在函数体结束时被自动销毁
3)使用free或delete释放了内存后,没有将指针设置为NULL,导致产生”野指针“

【规则1】在使用malloc或new申请内存之后,应该立即检查指针值是否为NULL,防止使用指针值作为NULL的内存
【规则2】不要忘记为数组或动态内存赋初值。防止将违背初始化的内存作为右值使用
【规则3】避免数组或指针下标越界,特别当心发生”多1“或者”少1“操作
【规则4】动态内存的申请和释放必须配对,防止内存泄露
【规则5】用free或delete释放内存之后,立即将指针设置为NULL,防止产生野指针

四丶野指针
”野指针“不是NULL指针,是指向”垃圾“内存的指针。人们一般不会用错NULL指针,因为用if语句很容易判断,但是野指针是很危险的,if语句对它不起作用,”野指针“的成因主要有两种
1.指针变量没有初始化,任何指针变量被创建时不会自动成为NULL,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存

char *p = NULL;
char *str = (char *)malloc(100);

2.指针p被free或者delete之后,没有置为NULL,让人误以为p是一个合法的指针
3.指针操作超越了变量的作用域范围,这种情况让人防不胜防

class A {
public:
    void Func(void) {
        cout << "Func of class A" << endl;
void Test(void) {
    A *p;
        A a;
        p = &a;//注意a的生命周期
    p->Func();//p是"野指针"

函数Test在执行语句p->Func()时,对象a已经消失,而p是指向a的,所以p就成了野指针,但奇怪的是这个程序没有报错,可能和编译器有关

五丶内存耗尽怎么办
如果在申请动态内存时找不到足够大的内存块,malloc和new将返回NULL指针,宣告内存申请失败。通常有三种方式处理”内存耗尽“问题
1.判断指针是否为NULL,如果是则马上用return语句终止本函数,例如:

void Func(void) {
    A* a = new A;
    if (a == NULL) {
        return;

2.判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行,例如:

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/deletemalloc/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映射方式,用于装载一个共享的动态内存库,用户可以使用系统接口创建共享内存,进行进程间通信. ④.堆:用于程序运行时动态内存分配,堆是向上增长的. ⑤.数据段:存储全局数据和静态数据...