面试真题 | 大厂面试爱考的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指针,是指向“垃圾”内存的指针。“野指针”的成因主要有三种:

  1. 指针变量没有被初始化,缺省值是随机的;
  2. 指针被free/delete之后,没有置为NULL,让人误以为该指针是个合法的指针;
  3. 指针操作超越了变量的作用域范围(内存越界)。

有了malloc/free为什么还要new/delete

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

ARM/Linux嵌入式真题 文章被收录于专栏

让实战与真题助你offer满天飞!!! 每周更新!!! 励志做最全ARM/Linux嵌入式面试必考必会的题库。 励志讲清每一个知识点,找到每个问题最好的答案。 让你学懂,掌握,融会贯通,因为技术知识工作中也会用到,所以踏实学习哦!!!

全部评论

相关推荐

1. Linux内核同步方式总结2. 为什么自旋锁不能睡眠 而在拥有信号量时就可以?3. Linux下检查内存状态的命令4. Linux的软件中断5. 大小端的区别以及各自的优点,哪种时候用6. 一个程序从开始运行到结束的完整过程(四个过程)7. 什么是堆,栈,内存泄漏和内存溢出?8. 堆和栈的区别9. 死锁的原因、条件 创建一个死锁,以及如何预防10. 硬链接与软链接的区别11. 虚拟内存,虚拟地址与物理地址的转换12. 计算机中,32bit与64bit有什么区别13. 中断和异常的区别14. 中断怎么发生,中断处理大概流程15. Linux 操作系统挂起、休眠、关机相关命令16. 数据库为什么要建立索引,以及索引的缺点17. CPU 内存 虚拟内存 磁盘/硬盘 的关系18. CPU内部结构19. ARM结构处理器简析20. 波特率是什么,为什么双方波特率要相同,高低波特率有什么区别21. ARM和DSP有什么区别22. ROM RAM的概念浅析23. IO口工作方式:上拉输入 下拉输入 推挽输出 开漏输出24. 扇区 块 页 簇的概念25. 简述处理器在读内存的过程中,CPU核、cache、MMU如何协同工作?画出CPU核、cache、MMU、内存之间的关系示意图加以说明26. 请说明总线接口USART、I2C、USB的异同点(串/并、速度、全/半双工、总线拓扑等)27. 什么是异步串口和同步串口28. FreeRTOS同优先级的任务创建的执行顺序是什么?29. STM32的PWM波是如何计算的?30. FreeRTOS和RT-Thread有什么区别?31. 项目让你最难受的地方,分析思路和解决思路?32. 串口中断中数据是怎么处理的?33. 串口数据接收,如果一个较大的数据包发送过来(1K字节以上,带帧头 帧长和校验码)你怎么解析和处理?34. IIC协议解释一下?35. SPI协议解释一下?36. CAN协议解释一下?37. 串口UART与RS232,RS485的区别38. FreeRTOS的任务是如何进行调度的?39. FreeRTOS中什么时候发生任务调度?40. 在FreeRTOS中若是配置为非礼让+非抢占,则当前任务会一直得到执行,为什么?答案附在面经中  c++/嵌入式面经专栏-牛客网 https://www.nowcoder.com/creation/manager/columnDetail/MJNwoM
查看36道真题和解析
点赞 评论 收藏
分享
4 19 评论
分享
牛客网
牛客企业服务