25、必考 | 大厂面试爱考的C++内存相关
C++内存相关
本篇介绍了 C++ 内存相关的知识。
C++内存分区
在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。
- 栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
- 堆:就是那些由
new
分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new
就要对应一个delete
。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。 - 全局/静态存储区:全局变量和静态变量被分配到同一块内存中。在以前的C语言中,全局变量又分为初始化的和未初始化的。在C++里面没有这个区分了,他们共同占用同一块内存区。
- 常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。
- 代码段:代码段(code segment / text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
根据c/c++对象生命周期不同,c/c++的内存模型有三种不同的内存区域,即
-
自由存储区,动态区、静态区。
-
自由存储区:局部非静态变量的存储区域,即平常所说的栈。
-
动态区: 用operator new ,malloc分配的内存,即平常所说的堆。
-
静态区:全局变量 静态变量 字符串常量存在位置。
下图为 C++ 内存模型,来自C++ Essentials。
- .text 部分是编译后程序的主体,也就是程序的机器指令。
- .data 和 .bss 保存了程序的全局变量,.data保存有初始化的全局变量,.bss保存只有声明没有初始化的全局变量。
- heap(堆)中保存程序中动态分配的内存,比如 C 的
malloc
申请的内存,或者C++中new
申请的内存。堆向高地址方向增长。 - stack(栈)用来进行函数调用,保存函数参数,临时变量,返回地址等。
- 共享内存的位置在堆和栈之间。
更详细的内存段解释见C与C++内存管理详解。
下面的文章介绍了Linux虚拟地址空间布局。
C++对象的成员函数存放在内存哪里
类成员函数和非成员函数代码存放在代码段。如果类有虚函数,则该类就会存在虚函数表。虚函数表在Linux/Unix 中存放在可执行文件的只读数据段中(rodata),即前面起到的代码段,而微软的编译器将虚函数表存放在常量段。
堆和栈的区别
管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak
。
空间大小:一般来讲在 32 位系统下,堆内存可以达到 4G 的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,栈顶和栈底是之前预设好的,大小固定,可以通过ulimit -a
查看,使用ulimit -s
修改。
碎片问题:对于堆来讲,频繁的new/delete
势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,它们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出。
生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca
函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
从这里我们可以看到,堆和栈相比,由于大量new/delete
的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。
虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。
堆和栈的访问效率
“野指针”
“野指针”不是NULL
指针,是指向“垃圾”内存的指针。“野指针”的成因主要有三种:
- 指针变量没有被初始化,缺省值是随机的;
- 指针被
free/delete
之后,没有置为NULL
,让人误以为该指针是个合法的指针; - 指针操作超越了变量的作用域范围(内存越界)。
有了malloc/free为什么还要new/delete
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
【C/C++面试必考必会】专栏,直击面试核心,精选C/C++及相关技术栈中面试官最爱的必考点!从基础语法到高级特性,从内存管理到多线程编程,再到算法与数据结构深度剖析,一网打尽。助你快速构建知识体系,轻松应对技术挑战。希望专栏能让你在面试中脱颖而出,成为技术岗的抢手人才。