C++面试高频(内存)

内存

1 内存分配的方式⭐⭐⭐⭐⭐

在编程中,内存分配方式主要有三种,分别是静态内存分配、栈内存分配和堆内存分配,下面为你详细介绍:

静态内存分配

  • 分配时机:在程序编译阶段就完成内存分配,编译器会根据程序中定义的静态变量和全局变量,确定它们所需的内存空间,并在可执行文件中预留相应的内存区域。
  • 作用域与生命周期:静态变量和全局变量的作用域和生命周期取决于它们的定义方式。全局变量在整个程序的生命周期内都存在,可在程序的任何地方访问;静态变量的作用域根据其定义位置而定,但其生命周期同样是整个程序运行期间。
  • 使用场景:适用于那些在程序运行过程中不需要动态调整大小,且需要在整个程序生命周期内保持存在的数据,如程序中的常量、配置信息等。
  • 优点:由于在编译时就确定了内存分配,因此分配速度快,不需要在运行时进行额外的内存管理操作。
  • 缺点:缺乏灵活性,一旦程序编译完成,静态变量和全局变量的内存大小就不能再改变。

栈内存分配

  • 分配时机:在程序运行过程中,当函数被调用时,系统会在栈上为该函数的局部变量、参数和返回地址等分配内存空间。当函数执行结束后,这些内存空间会自动被释放。
  • 作用域与生命周期:栈上分配的变量的作用域通常是定义它们的函数内部,其生命周期从变量定义开始,到函数执行结束为止。
  • 使用场景:适用于存储函数内部的临时变量和局部数据,这些数据的生命周期较短,且不需要在函数调用结束后继续存在。
  • 优点:栈内存的分配和释放速度非常快,因为它只需要移动栈指针即可完成操作。此外,栈内存的管理由系统自动完成,程序员无需手动干预,减少了内存泄漏的风险。
  • 缺点:栈的空间通常比较有限,如果在函数中分配了大量的局部变量,可能会导致栈溢出错误。

堆内存分配

  • 分配时机:在程序运行时,通过特定的函数(如 C 语言中的malloccallocrealloc,C++ 中的new)来动态地申请内存空间。程序员可以根据程序的需要,在合适的时机分配和释放内存。
  • 作用域与生命周期:堆上分配的内存的生命周期由程序员手动控制,即通过调用相应的释放函数(如 C 语言中的free,C++ 中的deletedelete[])来释放内存。如果程序员忘记释放内存,就会导致内存泄漏。
  • 使用场景:适用于需要动态调整大小的数据结构,如数组、链表、树等,以及在程序运行过程中需要根据用户输入或其他条件来分配内存的情况。
  • 优点:堆内存的空间相对较大,且可以根据程序的需要动态地分配和释放,具有很高的灵活性。
  • 缺点:堆内存的分配和释放速度相对较慢,因为需要进行复杂的内存管理操作。此外,手动管理堆内存容易出现内存泄漏和悬空指针等问题,增加了程序的复杂性和出错的风险。

2 堆和栈的区别⭐⭐⭐⭐⭐

1 申请方式

栈的空间由操作系统自动分配/释放,堆上的空间手动分配/释放。

2 申请大小的限制

栈空间有限,栈是向低地址拓展的数据结构,是一块连续的内存区域。

堆有很大的自由存储区。堆是向高地址拓展的数据结构,是不连续的内存区域。

3 申请效率

栈是由系统自动分配的,速度比较快。但程序员是无法控制的。

堆是由new分配的的内存,一般速度比较慢,而且容易产生内存碎片。用起来很方便。

3 栈在C++中的作用⭐⭐⭐⭐⭐

1 c++中栈是用来存储临时变量,临时变量包括函数参数和函数内部定义的临时变量。函数中调用中函数调用相关的函数返回地址。

2 多线程编程的基础是栈,栈是多线程编程的基础,每一个线程都最少有一个自己专属的栈,用来存储本线程运行时各个函数的临时变量和函数调用和函数返回值。操作系统最基本的功能是支持多线程编程,支持中断和异常处理,每一个线程都有专属的栈,中断和异常也有专属的栈,栈是操作系统多线程管理的基础。

4 c++的内存管理介绍⭐⭐⭐⭐⭐

在c++中,虚拟内存分为代码段、数据段、BSS段、堆区、文件映射区及栈区

代码段:包括制度存储区、文本区,其中只读存储区存储字符串常量,文本区存储程序的机器代码。

数据段:存储程序中已初始化的全局变量和静态变量。

BSS段:存储未出啥的全局变量和静态变量(局部+全局),以及所有被初始化为的全局变量和静态变量。

堆区:调用new/malloc函数时在堆区动态分配内存,同时需要调用delete/free来手动释放申请的内存。

映射区:存储动态链接库以及调用mmap函数进行的文件映射。

栈区:使用栈空间存储函数的返回地址、参数、局部变量、返回值。

5 内存泄漏⭐⭐⭐⭐⭐

内存泄漏是什么?

内存泄漏是指在计算机程序运行时,内存动态分配后,由于疏忽或者错误导致内存无法被正确释放,最终导致内存被占用过多,从而程序出现错误甚至崩溃的问题。内存泄漏是一种非常严重的程序问题,需要引起足够的重视。

通常,内存泄漏是由于程序员在动态分配内存时,没有正确的释放内存导致的。如果没有进行内存释放操作,就会导致一个不断增长的内存空间,最终用完系统内存资源从而导致程序崩溃。一些常见的内存泄漏情况包括:

1 动态分配内存后,忘记释放掉。

2 对已经释放的内存空间进行

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

嵌入式/C++面试八股文 文章被收录于专栏

该专栏面向嵌入式开发工程师、C++开发工程师,包括C语言、C++,操作系统,ARM架构、RTOS、Linux基础、Linux驱动、Linux系统移植、计算机网络、数据结构与算法、数电基础、模电基础、5篇面试题目、HR面试常见问题汇总和嵌入式面试简历模板等文章。超全的嵌入式软件工程师面试题目和高频知识点总结! 另外,专栏分为两个部分,大家可以各取所好,为了有更好的阅读体验,后面会持续更新!!!

全部评论
点赞 回复 分享
发布于 03-23 22:30 广东

相关推荐

03-22 11:53
已编辑
门头沟学院 C++
1. 被free回收的内存是立即返回给操作系统吗?2. sizeof和strlen的区别?3. new/delete、malloc/free的区别4. 堆快一点还是栈快一点?5. 在main执行之前和执行之后执行的代码可能是什么?6. final和override的关键字?7. 拷贝初始化和直接初始化?8. select、poll、epoll9.Reactor?10. 阻塞、非阻塞、同步、异步?11. Proactor?1. 被free回收的内存首先会被ptmalloc使用双链表保存起来,当有用户申请内存的时候,会直接从这里面找出合适的内存进行返回。这样可以避免操作系统的频繁调用,另外它还会将小内存进行合并,防止产生过多的内存碎片;2. sizeof是运算符,strlen是库函数。sizeof的参数可以是任何数据的类型或数据,strlen的参数只能是字符串的指针,且结束符为'\0'的字符;3. 前者是C++运算符,后者是C/C++库函数;new自动计算要分配的内存对象,malloc需要计算;new是类型安全的,malloc不是;调用new的给自定义对象分配内存的时候,会调用构造函数并分配相应的内存,delete的时候会释放内存并执行析构;4.  栈快一点,操作系统堆栈提供支持,有分配专门的寄存器存放栈的地址,栈的出入栈也十分简单,并且有专门的指令执行,所以栈的效率更高更快;而堆在分配的内存的时候要使用算法寻找合适大小的内存,并且在获取堆的内容的时候需要两次访问,一次指针,一直是指针保存的地址5. 初始化全局变量和静态变量,即.data段数据,将未初始化的全局变量进行赋值;全局对象初始化;将argc、argv传递给main函数;___attribute___(constructor);结束后,全局的析构函数会在main函数执行完后析构,可用一个atexit注册一个函数会在main函数之后执行;___attribute___(destructor)6. override的关键字,指定了子类的这个虚函数必须重写父类的,如果函数名不小心打错了,编译器会进行报错,不会通过;当不希望某个类被继承、或不希望虚函数被重写可以在类名和虚函数后面添加final关键字;7. 直接初始化直接调用与实参匹配的构造函数;拷贝初始化首先使用指定构造函数创建一个临时对象,然后用拷贝构造函数将那个零食对象拷贝到正在创建的对象;8. select函数将已连接的socket都放到一个文件描述符集合,然后调用select函数将文件描述符拷贝到内核中,去遍历进行检测那个套接字发生事件,将其进行标记,后续再复制到用户态,进行遍历找到标记的套接字。这其中发生了2次遍历文件描述符集合和2次拷贝文件描述符集合。poll突破文件描述符个数的限制(1024),通过一个动态数组,以链表的形式管理。其它与select还是一样的。epoll通过在内核中使用红黑树来跟踪进程所有待检测的文件描述符,把需要监控的socket通过epoll_ctl函数加入到内核中的红黑树,第二,epoll使用时间驱动的机制,内核里维护了一个链表来记录就绪事件,当某个socket事件发生时,通过回调函数内核将其加入到就绪事件列表中,当用户调用epoll_wait函数时,只会返回有事件发生的文件描述符的个数。9. reactor模型是 I/O 多路复用监听事件,收到事件后,根据事件类型分配(Dispatch)给某个进程 / 线程。单reactor单进程:reactor对象通过epoll监听事件,收到事件后通过dispatch进行分发,如果是连接建立的事件交个Acceptor对象进行处理,如果不是就交给Handle对象通过read->业务处理->send的流程来完成完整的业务流程。单reactor多线程:对于不是连接建立事件,则交由Handler对象处理,他不负责业务处理,只负责数据的接受和发送,数据会发送给线程池来进行业务处理,处理完成后将业务结果返回给handler对象,然后发送给client;多reactor多线程:主线程只负责接受新连接,子线程负责完成后续的业务处理。主线程只需要把新连接传给子线程,子线程进行业务处理后,直接可以将处理结果发送给客户端。10. 阻塞I/O:当用户执行read,线程会被阻塞,一直等到内核数据准备好[1],并把数据从内核缓冲区拷贝到应用缓冲区中[2],当拷贝完成,read才返回。非阻塞I/O:可以在数据未准备好久立即返回,然后应用程序不断轮训内核,直到数据准备好,[2]操作还是要的;异步I/O:上面的[1]和[2]的步骤都不需要等待,都交由内核完成。11. Reactor模式是基于待完成的I/O事件,而Proactor模式是基于[已完成]的I/O事件。
点赞 评论 收藏
分享
评论
3
7
分享

创作者周榜

更多
牛客网
牛客企业服务