参考:
- 公众号:程序员贺先生——C++八股
- (44条消息) C/C++的堆和栈详解 - C++学习_c++ 堆 栈 地址_逸璞丷昊的博客-CSDN博客
- (44条消息) 一文读懂堆与栈的区别_堆和栈的区别_恋喵大鲤鱼的博客-CSDN博客
- C++面试-内存篇 - 知乎 (zhihu.com)
C++内存分配情况
- 栈:由编译器管理分配和回收,存放局部变量和函数参数。
- 堆:由程序员管理,需要⼿动 new malloc delete free 进⾏分配和回收,空间较⼤,但可能会出现内存泄漏和空闲碎⽚的情况。
- 全局/静态存储区:分为初始化和未初始化两个相邻区域,存储初始化和未初始化的全局变量和静态变量。
- 常量存储区:存储常ᰁ,⼀般不允许修改。
- 代码区:存放程序的⼆进制代码。
常量存放在内存哪个位置
对于局部常量,存放在栈区; 对于全局常量,编译期⼀般不分配内存,放在符号表中以提⾼访问效率; 字⾯值常量,⽐如字符串,放在常量区。
堆和栈区别详解
栈:
由编译器进⾏管理,在需要时由编译器⾃动分配空间,在不需要时候⾃动回收空间,⼀般保存的是局部变量和函数参数等。
- 栈区是高地址向低地址扩展的,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,大小在进程分配时是确定的,具体大小看编译器,操作系统。空间较⼩。
- 函数调用栈:在函数调⽤的时候,⾸先⼊栈的主函数的下⼀条可执⾏指令的地址,然后是函数的各个参数。参数从右向左入栈。本次函数调⽤结束时,局部变量先出栈,然后是参数,最后是栈顶指针最开始存放的地址,程序由该点继续运⾏,不会产⽣碎⽚。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。
堆:
由程序员管理,需要⼿动 new malloc delete free 进⾏分配和回收,如果不进⾏回收的话,会造成内存泄漏的问题。
- 堆区是低地址向高地址扩展的,是不连续的空间。
- 实际上系统中有⼀个空闲链表,当有程序申请的时候,系统遍历空闲链表找到第⼀个⼤于等于申请⼤⼩的空间分配给程序,⼀般在分配程序的时候,也会空间头部写⼊内存⼤⼩,⽅便 delete 回收空间⼤⼩。当然如果有剩余的,也会将剩余的插⼊到空闲链表中,这也是产⽣内存碎⽚的原因。
区别:
-
生长方向:
堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。 -
申请方式
-
申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。 -
申请大小限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 -
申请效率比较
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.为什么栈比堆快
- 操作系统会在底层对栈提供支持,会分配专门的寄存器存放栈的地址,栈的入栈出栈操作也十分简单,并且有专门的指令执行,所以栈的效率比较高也比较快。
- 堆的操作是由C/C++函数库提供的,在分配堆内存的时候需要一定的算法寻找合适大小的内存。并且获取堆的内容需要两次访问,第一次访问指针,第二次根据指针保存的地址访问内存,因此堆比较慢。
-
存储内容
栈:栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等。在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的(存放在数据段或者BSS段)。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
new/delete, malloc/free区别
都可以用来在堆上分配和回收空间。new /delete 是操作符,malloc/free 是库函数。 区别:
- 前者是C++运算符,后者是C/C++语言标准库函数
- new自动计算要分配的空间大小,malloc需要手工计算
- 返回值不同,malloc返回的是一个空指针,new返回的是对应对象类型的指针
- 过程不同
执⾏ new 实际上执⾏两个过程:
- 分配未初始化的内存空间(malloc);
- 使⽤对象的构造函数对空间进行初始化;返回空间的⾸地址。如果在第⼀步分配空间中出现问题,则抛出std::bad_alloc 异常,或被某个设定的异常处理函数捕获处理;如果在第⼆步构造对象时出现异常,则⾃动调⽤ delete 释放内存。
- new 得到的是经过初始化的空间,⽽ malloc 得到的 是未初始化的空间。所以 new 是 new ⼀个类型,⽽ malloc 则是malloc ⼀个字节⻓度的空间。
执⾏ delete 实际上也有两个过程:
- 使⽤析构函数对对象进⾏析构;
- 回收内存空间(free)。
- delete 不仅释放空间还析构对象,delete ⼀个类型,free ⼀个字节⻓度的空间。
为什么有了 malloc/free 还需要 new/delete?
因为对于⾮内部数据类型⽽⾔,光⽤ malloc/free ⽆法满⾜动态对象的要求。对象在创建的同时需要⾃动执⾏构造函数,对象在消亡以前要⾃动执⾏析构函数。
由于 mallo/free 是库函数⽽不是运算符,不在编译器控制权限之内,不能够把执⾏的构造函数和析构函数的任务强加于 malloc/free,所以有了 new/delete 操作符。
内存泄漏的定义,如何检测与避免
内存泄漏简单的说就是申请了⼀块内存空间,使用完毕后没有释放掉。
它的⼀般表现⽅式是程序运⾏时间越⻓,占⽤内存越多,最终⽤尽全部内存,整个系统崩溃。由程序申请的⼀块内存,且没有任何⼀个指针指向它,那么这块内存就泄漏了。
解决内存泄漏问题需要先确定内存泄漏的原因,可以通过以下几个步骤来解决内存泄漏问题:
- 排查代码:查看代码中是否有明显的内存泄漏的情况,例如忘记释放内存等。
- 使用工具检查:可以使用一些内存泄漏检测工具,例如Valgrind、Purify、AddressSanitizer等,来检测程序中的内存泄漏情况。
- 检查资源的使用情况:程序中除了内存泄漏还可能存在其他资源泄漏,例如文件句柄、网络连接等,需要逐一检查并进行相应的释放。
- 使用智能指针:在C++中,可以使用智能指针(shared_ptr、unique_ptr、weak_ptr)等RAII技术来管理动态内存,自动释放资源,避免忘记释放内存的问题。
- 重构代码:如果程序中的内存泄漏问题比较严重,无法通过以上方法解决,可以考虑对代码进行重构,优化内存使用情况,避免内存泄漏的问题。
常见内存泄露
- 使用完对象后未将其引用置为NULL,导致其一直占用内存。
- 内存动态分配后忘记释放。
- 循环引用:多个对象之间相互引用,当有一个引用未释放干净时导致内存泄露。
- 持续增长的缓存:当一个缓存区在使用后没有被清空或者不定期的清理,会导致缓存中的数据越来越多,最终导致内存泄漏。
- 大对象未分配内存池:如果需要频繁地分配、释放大对象(如数组、矩阵等),直接调用系统函数分配内存可能会导致内存碎片化,进而导致系统内存泄漏。此时,可以使用内存池技术来解决这个问题。
C++内存分配可能出现问题:
- 内存泄漏:在使用完堆上的内存后没有及时释放,导致程序运行过程中不断地占用内存。
- 内存溢出:在申请内存时超出了操作系统或程序所能提供的内存上限,导致程序崩溃。
- 悬垂指针:指向已经被释放的内存区域,导致程序访问非法内存而崩溃。
- 双重释放:在释放内存时出现重复释放同一内存区域的情况,导致程序崩溃。
- 内存访问越界:程序访问了已经超出了申请内存空间的范围,导致程序崩溃。
可以说一下你了解的C++得内存管理吗?
在C++中,内存分成5个区,他们分别是堆、栈、全局/静态存储区和常量存储区和代码区。
- 栈,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
- 堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
- 全局/静态存储区,内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据(局部static变量,全局static变量)、全局变量和常量。
- 常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量字符串,不允许修改。
- 代码区,存放程序的二进制代码
被free回收的内存是立即返还给操作系统吗?
不是的,
- 被free回收的内存会首先被ptmalloc使用双链表保存起来,当用户下一次申请内存的时候,会尝试从这些内存中寻找合适的返回。这样就避免了频繁的系统调用,占用过多的系统资源。
- 同时ptmalloc也会尝试对小块内存进行合并,避免过多的内存碎片。
内存池
内存池是一种常见的内存管理技术,它的作用是提高内存的利用率,减少内存碎片,以及提高内存分配和释放的效率。