热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C++秋招记录(四)——内存方面

C秋招记录(四)一、内存管理1、CC内存有哪几种类型?2、堆和栈的区别?3、堆和自由存储区的区别?4、请你说一

C++秋招记录(四)

    • 一、内存管理
      • 1、C/C++内存有哪几种类型?
      • 2、堆和栈的区别?
      • 3、堆和自由存储区的区别?
      • 4、 请你说一说内存溢出和内存泄漏
      • 5、“野指针”产生原因及解决办法如下:
      • 6、new、delete、malloc、free关系
      • 7、 C++中有了malloc / free , 为什么还需要 new / delete?
      • 8、 free、delete为什么不用记录长度?
      • 9、memory alignment and padding, 内存对齐的原理与意义

内存管理、new、delete,内存池相关问题

一、内存管理


1、C/C++内存有哪几种类型?


  • C中,内存分为5个区:堆(malloc)、栈(如局部变量、函数参数)、程序代码区(存放二进制代码)、全局/静态存储区(全局变量、static变量)和常量存储区(常量)。此外,C++中有自由存储区(new)一说。全局变量、static变量会初始化为零,而堆和栈上的变量是随机的,不确定的。
  • 堆(heap)、栈(stack)、代码段(code segment/text segment)、 BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域、数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。
  • C、C++中内存分配方式可以分为三种: 栈和静态内存的对象由编译器自动创建和销毁。
    1)从静态存储区域分配:
    内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在。速度快、不容易出错,因为有系统会善后。例如全局变量,static
    变量类static数据成员以及定义在任何函数外部的变量等,static对象在使用之前分配,程序结束时销毁。
    静态变量什么时候初始化?静态变量存储在虚拟地址空间的数据段和bss段,C语言中其在代码执行之前初始化,属于编译期初始化。而C++中由于引入对象,对象生成必须调用构造函数,因此C++规定全局或局部静态对象当且仅当对象首次用到时进行构造。
    2)在栈上分配:
    在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
    3)从堆上分配:
    即动态内存分配。程序在运行的时候用 malloc 或 new 申请任意大小的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活。如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块。

2、堆和栈的区别?


  • 堆存放动态分配的对象——即那些在程序运行时分配的对象,比如局部变量,其生存期由程序控制;
  • 栈用来保存定义在函数内的非static对象,仅在其定义的程序块运行时才存在;
  • 堆是由低地址向高地址扩展;栈是由高地址向低地址扩展;堆中的内存需要手动申请和手动释放;栈中内存是由OS自动申请和自动释放,存放着参数、局部变量等内存;堆中频繁调用malloc和free,会产生内存碎片,降低程序效率;而栈由于其先进后出的特性,不会产生内存碎片;堆的分配效率较低,而栈的分配效率较高。
  • 栈的效率高的原因:
    栈是操作系统提供的数据结构,计算机底层对栈提供了一系列支持:分配专门的寄存器存储栈的地址,压栈和入栈有专门的指令执行;而堆是由C/C++函数库提供的,机制复杂,需要一系列分配内存、合并内存和释放内存的算法,因此效率较低。

3、堆和自由存储区的区别?


  • 总的来说,堆是C语言和操作系统的术语,是操作系统维护的一块动态分配内存;自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。他们并不是完全一样。
  • 从技术上来说,堆(heap)是C语言和操作系统的术语。堆是操作系统所维护的一块特殊内存,它提供了动态分配的功能,当运行程序调用malloc()时就会从中分配,稍后调用free可把内存交还。
  • 而自由存储是C++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区。基本上,所有的C++编译器默认使用堆来实现自由存储,也即是缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现,这时藉由new运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确。

4、 请你说一说内存溢出和内存泄漏

内存溢出
指程序申请内存时,没有足够的内存供申请者使用。内存溢出就是你要的内存空间超过了系统实际分配给你的空间,此时系统相当于没法满足你的需求,就会报内存溢出的错误

原因:

  • 内存中加载的数据量过于庞大,如一次从数据库取出过多数据
  • 集合类中有对对象的引用,使用完后未清空,使得不能回收
  • 代码中存在死循环或循环产生过多重复的对象实体
  • 使用的第三方软件中的BUG 启动参数内存值设定的过小

内存泄漏

内存泄漏是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

分类:

1、堆内存泄漏 (Heap leak)。对内存指的是程序运行中根据需要分配通过malloc,realloc new等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak。
2、系统资源泄露(Resource Leak)。主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。
3、没有将基类的析构函数定义为虚函数。当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,因此造成内存泄露。

面对内存泄漏和指针越界,哪些方法来避免和减少这类错误?

  • 1). 使用的时候要记得指针的长度.
  • 2). malloc的时候得确定在那里free.
  • 3).对指针赋值的时候应该注意被赋值指针需要不需要释放.
  • 4). 动态分配内存的指针最好不要再次赋值.
  • 5).在C++中应该优先考虑使用智能指针

5、“野指针”产生原因及解决办法如下:

(1) 指针变量声明时没有被初始化。解决办法:指针声明时初始化,可以是具体的地址值,也可让它指向 NULL。
(2) 指针 p 被 free 或者 delete 之后,没有置为 NULL。解决办法:指针指向的内存空间被释放后指针应该指向 NULL。
(3) 指针操作超越了变量的作用范围。解决办法:在变量的作用域结束前释放掉变量的地址空间并且让指针指向 NULL。
注意:“野指针”的解决方法也是编程规范的基本原则,平时使用指针时一定要避免产生“野指针”,在使用指针前一定要检验指针的合法性。

  • 空指针是指指向地址为NULL(0)的指针变量
  • 悬垂指针是指指向一个已经不存在的对象的地址的指针
  • 野指针是指因为没有初始化等原因指向一处随机或者无效的地址的指针
  • 智能指针首先是在boost库中实现的,后来被C++标准引用

6、new、delete、malloc、free关系

在这里插入图片描述

  • new是运算符,malloc是C语言库函数

  • new可以重载,malloc不能重载

  • new的变量是数据类型,malloc的是字节大小

  • new可以调用构造函数,delete可以调用析构函数,malloc/free不能

  • new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化

  • malloc分配的内存不够的时候可以使用realloc扩容,new没有这样的操作new内存分配失败抛出bad_malloc,malloc内存分配失败返回NULL值

  • 申请的内存所在位置(3. 堆和自由存储区的区别?)

  • 返回类型安全性

    • new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void* ,需要通过强制类型转换将void*指针转换成我们需要的类型。
  • 内存分配失败时的返回值
    new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。
    在这里插入图片描述

  • 是否需要指定内存大小

    • 使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据数据类型自行计算,而malloc则需要显式地指出所需内存的尺寸。是否调用构造函数/析构函数
  • 对数组的处理

    • new对数组的支持体现在它会分别调用构造函数初始化每个数组元素,释放对象时为每个对象调用析构函数。delete[]要与new[]配套使用,否则数组对象部分释放的现象,造成内存泄漏;malloc,它不知道你在这块内存上要放的数组还是什么,反正它就给你一块原始的内存。如果要动态分配一个数组的内存,还需要我们手动自定数组的大小。
  • 是否调用构造函数
    总之来说,new/delete会调用对象的构造函数/析构函数以完成对象的构造/析构。使用new操作符来分配对象内存时会经历三个步骤:

    • 1.调用operator new 函数(对于数组是operator new[])分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象。
    • 2.编译器运行相应的构造函数以构造对象,并为其传入初值。
    • 3.对象构造完成后,返回一个指向该对象的指针。
  • 使用delete操作符来释放对象内存时会经历两个步骤:

    • 1.调用对象的析构函数。
    • 2.编译器调用operator delete(或operator delete[])函数释放内存空间。
  • 是否可以被重载
    opeartor new /operator delete可以被重载。标准库是定义了operator new函数和operator delete函数的8个重载版本:
    在这里插入图片描述

  • new与malloc是否可以相互调用

    • operator new /operator delete的实现可以基于malloc,而malloc的实现不可以去调用new。下面是编写operator new /operator delete 的一种简单方式,其他版本也与之类似:

在这里插入图片描述

  • 能够直观地重新分配内存
    • 使用malloc分配的内存后,如果在使用过程中发现内存不足,可以使用realloc函数进行内存重新分配实现内存的扩充。realloc先判断当前的指针所指内存是否有足够的连续空间,如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;如果空间不够,先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来的内存区域。new没有这样直观的配套设施来扩充内存。
  • 客户处理内存分配不足
    • 在operator new抛出异常以反映一个未获得满足的需求之前,它会先调用一个用户指定的错误处理函数,这就是new-handler(设计良好的new_handler两个选择:让更多的memory可用或调用abort()或exit())。new_handler是一个指针类型
      在这里插入图片描述

7、 C++中有了malloc / free , 为什么还需要 new / delete?


  • 1). malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
  • 2).对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。在C++中,如下的代码,用new创建一个对象(new会触发构造函数, delete会触发析构函数),但是malloc仅仅申请了一个空间,所以在C++中引入new和delete来支持面向对象。
    3)由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。由于malloc是库函数,需要相应的库支持,因此某些简易的平台可能不支持,但是new就没有这个问题了,因为new是C++语言所自带的运算符。

8、 free、delete为什么不用记录长度?


  • 为了管理malloc的空闲空间,每一个独立块的最前面都包含了一个“头部信息”,属于额外开销。malloc实际分配的内存会大于我们需要的size,主要有两方面因素:
    ① 字节对齐。会对齐到机器最受限的类型。
    ② “块头部信息”,每个空闲块都有“头部”控制信息,其中包含一个指向链表中的下一块的指针、当前块的大小和一个指向本身的指针。为了简化对齐,所有块的大小都必须是头部大小的整数倍,且头部已正确对齐,(malloc返回的是空闲块的首地址,不是首地址;size字段是必要的,因为malloc控制的块不一定是连续的,这样就不能通过指针算数运算得到其大小)
  • 实际分配的内存块将多一个单元,用于头部本身。实际分配的块的大小被记录在头部size中。所以当我们new[n]的时候,释放只需要写delete[],不用注明释放的大小的原因了。每次在释放的时候,会先查看释放的块的头部信息,其中就记录了这个块的大小。
  • C++ 的做法是在分配数组空间时多分配了 4 个字节的大小,专门保存数组的大小,在 delete []时就可以取出这个保存的数,就知道了需要调用析构函数多少次了

9、memory alignment and padding, 内存对齐的原理与意义


  • 作用:结构体以及类成员对齐,意义就是减少cpu读取的次数,提高效率。

    • 比如一个int变量长度为4个字节,cpu一次读4个字节,当然是一次读取比较好。但是如果前面有一个char,地址为0-1。那么这个int的地址就为1-4。导致cpu,分两次读取int值。
  • 内存对齐的原则:

    • 从0位置开始存储;
    • 变量存储的起始位置是该变量大小的整数倍;
    • 结构体总的大小是其最大元素的整数倍,不足的后面要补齐;
    • 结构体中包含结构体,从结构体中最大元素的整数倍开始存,如果加入pragma pack(n) ,取n和变量自身大小较小的一个。

结构体:1、数据成员对齐规则:结构体(struct)的数据成员,第一个数据成员放在offset为0的地方,之后的每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机子上为4字节,所以要从4的整数倍地址开始存储)。
2、结构体作为成员:如果一个结构体里同时包含结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储(如struct a里有struct b,b里有char,int ,double等元素,那么b应该从8(即double类型的大小)的整数倍开始存储)。
3、结构体的总大小:即sizeof的结果。在按之前的对齐原则计算出来的大小的基础上,必须还得是其内部最大成员的整数倍,不足的要补齐(如struct里最大为double,现在计算得到的已经是11,则总大小为16)。
在这里插入图片描述


推荐阅读
  • 单片微机原理P3:80C51外部拓展系统
      外部拓展其实是个相对来说很好玩的章节,可以真正开始用单片机写程序了,比较重要的是外部存储器拓展,81C55拓展,矩阵键盘,动态显示,DAC和ADC。0.IO接口电路概念与存 ... [详细]
  • Spring – Bean Life Cycle
    Spring – Bean Life Cycle ... [详细]
  • MySQL 5.7 学习指南:SQLyog 中的主键、列属性和数据类型
    本文介绍了 MySQL 5.7 中主键(Primary Key)和自增(Auto-Increment)的概念,以及如何在 SQLyog 中设置这些属性。同时,还探讨了数据类型的分类和选择,以及列属性的设置方法。 ... [详细]
  • Cookie学习小结
    Cookie学习小结 ... [详细]
  • 2020年9月15日,Oracle正式发布了最新的JDK 15版本。本次更新带来了许多新特性,包括隐藏类、EdDSA签名算法、模式匹配、记录类、封闭类和文本块等。 ... [详细]
  • 高端存储技术演进与趋势
    本文探讨了高端存储技术的发展趋势,包括松耦合架构、虚拟化、高性能、高安全性和智能化等方面。同时,分析了全闪存阵列和中端存储集群对高端存储市场的冲击,以及高端存储在不同应用场景中的发展趋势。 ... [详细]
  • EST:西湖大学鞠峰组污水厂病原菌与土著反硝化细菌是多重抗生素耐药基因的活跃表达者...
    点击蓝字关注我们编译:祝新宇校稿:鞠峰、袁凌论文ID原名:PathogenicandIndigenousDenitrifyingBacte ... [详细]
  • 【妙】bug称它为数组越界的妙用
    1、聊一聊首先跟大家推荐一首非常温柔的歌曲,跑步的常听。本文主要把自己对C语言中柔性数组、零数组等等的理解分享给大家,并聊聊如何构建一种统一化的学习思想 ... [详细]
  • 本文详细介绍了Java代码分层的基本概念和常见分层模式,特别是MVC模式。同时探讨了不同项目需求下的分层策略,帮助读者更好地理解和应用Java分层思想。 ... [详细]
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • IOS Run loop详解
    为什么80%的码农都做不了架构师?转自http:blog.csdn.netztp800201articledetails9240913感谢作者分享Objecti ... [详细]
  • 本文详细介绍了如何解决DNS服务器配置转发无法解析的问题,包括编辑主配置文件和重启域名服务的具体步骤。 ... [详细]
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • 多线程基础概览
    本文探讨了多线程的起源及其在现代编程中的重要性。线程的引入是为了增强进程的稳定性,确保一个进程的崩溃不会影响其他进程。而进程的存在则是为了保障操作系统的稳定运行,防止单一应用程序的错误导致整个系统的崩溃。线程作为进程的逻辑单元,多个线程共享同一CPU,需要合理调度以避免资源竞争。 ... [详细]
  • 解决Bootstrap DataTable Ajax请求重复问题
    在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
author-avatar
亲爱的jackvan叔叔
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有