C++开发必知的内存问题及常用的解决方法-经典文章

news2024/10/7 4:29:29

1. 内存管理功能问题

由于C++语言对内存有主动控制权,内存使用灵活和效率高,但代价是不小心使用就会导致以下内存错误:

• memory overrun:写内存越界 • double free:同一块内存释放两次 • use after free:内存释放后使用 • wild free:释放内存的参数为非法值 • access uninitialized memory:访问未初始化内存 • read invalid memory:读取非法内存,本质上也属于内存越界 • memory leak:内存泄露 • use after return:caller访问一个指针,该指针指向callee的栈内内存 • stack overflow:栈溢出

常用的解决内存错误的方法

  • 代码静态检测

静态代码检测是指无需运行被测代码,通过词法分析、语法分析、控制流、数据流分析等技术对程序代码进行扫描,找出代码隐藏的错误和缺陷,如参数不匹配,有歧义的嵌套语句,错误的递归,非法计算,可能出现的空指针引用等等。统计证明,在整个软件开发生命周期中,30%至70%的代码逻辑设计和编码缺陷是可以通过静态代码分析来发现和修复的。在C++项目开发过程中,因为其为编译执行语言,语言规则要求较高,开发团队往往要花费大量的时间和精力发现并修改代码缺陷。所以C++静态代码分析工具能够帮助开发人员快速、有效的定位代码缺陷并及时纠正这些问题,从而极大地提高软件可靠性并节省开发成本。

静态代码分析工具的优势:

1、自动执行静态代码分析,快速定位代码隐藏错误和缺陷。

2、帮助代码设计人员更专注于分析和解决代码设计缺陷。

3、减少在代码人工检查上花费的时间,提高软件可靠性并节省开发成本。

一些主流的静态代码检测工具,免费的cppcheck,clang static analyzer;

商用的coverity,pclint等

各个工具性能对比:

 

  • 代码动态检测

所谓的代码动态检测,就是需要再程序运行情况下,通过插入特殊指令,进行动态检测和收集运行数据信息,然后分析给出报告。

1.为了检测内存非法使用,需要hook内存分配和操作函数。hook的方法可以是用C-preprocessor,也可以是在链接库中直接定义(因为Glibc中的malloc/free等函数都是weak symbol),或是用LD_PRELOAD。另外,通过hook strcpy(),memmove()等函数可以检测它们是否引起buffer overflow。

  1. 为了检查内存的非法访问,需要对程序的内存进行bookkeeping,然后截获每次访存操作并检测是否合法。bookkeeping的方法大同小异,主要思想是用shadow memory来验证某块内存的合法性。至于instrumentation的方法各种各样。有run-time的,比如通过把程序运行在虚拟机中或是通过binary translator来运行;或是compile-time的,在编译时就在访存指令时就加入检查操作。另外也可以通过在分配内存前后加设为不可访问的guard page,这样可以利用硬件(MMU)来触发SIGSEGV,从而提高速度。

3.为了检测栈的问题,一般在stack上设置canary,即在函数调用时在栈上写magic number或是随机值,然后在函数返回时检查是否被改写。另外可以通过mprotect()在stack的顶端设置guard page,这样栈溢出会导致SIGSEGV而不至于破坏数据。

工具总结对比,常用valgrind(检测内存泄露),gperftools(统计内存消耗)等:

DBI:动态二进制工具 CTI:编译时工具 UMR:未初始化的存储器读取 UAF:释放后使用(又名悬挂指针) UAR:返回后使用 OOB:越界 x86:包括32和64-少量。在GCC 4.9中已删除了 Mudflap,因为它已被AddressSanitizer取代。 Guard Page:一系列内存错误检测器(Linux上为电子围栏或DUMA,Windows上为Page Heap,OS X上为 libgmallocgperftools:与TCMalloc捆绑在一起的各种性能工具/错误检测器。堆检查器(检漏器)仅在Linux上可用。调试分配器同时提供了保护页和Canary值,以更精确地检测OOB写入,因此它比仅保护页的检测器要好。

2. C++内存管理效率问题

1、内存管理可以分为三个层次

 自底向上分别是:

  • 第一层:操作系统内核的内存管理-虚拟内存管理
  • 第二层:glibc层维护的内存管理算法
  • 第三层:应用程序从glibc动态分配内存后,根据应用程序本身的程序特性进行优化, 比如SGI STL allocator,使用引用计数std::shared_ptr,RAII,实现应用的内存池等等。

当然应用程序也可以直接使用系统调用从内核分配内存,自己根据程序特性来维护内存,但是会大大增加开发成本。

2、C++内存管理问题

  • 频繁的new/delete势必会造成内存碎片化,使内存再分配和回收的效率下降;
  • new/delete分配内存在linux下默认是通过调用glibc的api-malloc/free来实现的,而这些api是通过调用到linux的系统调用:

 

brk()/sbrk() // 通过移动Heap堆顶指针brk,达到增加内存目的 mmap()/munmap() // 通过文件影射的方式,把文件映射到mmap区

分配内存 < DEFAULT_MMAP_THRESHOLD,走brk,从内存池获取,失败的话走brk系统调用

分配内存 > DEFAULT_MMAP_THRESHOLD,走mmap,直接调用mmap系统调用

其中,DEFAULT_MMAP_THRESHOLD默认为128k,可通过mallopt进行设置。

sbrk/brk系统调用的实现:分配内存是通过调节堆顶的位置来实现, 堆顶的位置是通过函数 brk 和 sbrk 进行动态调整,参考例子:

(1) 初始状态:如图 (1) 所示,系统已分配 ABCD 四块内存,其中 ABD 在堆内分配, C 使用 mmap 分配。为简单起见,图中忽略了如共享库等文件映射区域的地址空间。

(2) E=malloc(100k) :分配 100k 内存,小于 128k ,从堆内分配,堆内剩余空间不足,扩展堆顶 (brk) 指针。

(3) free(A) :释放 A 的内存,在 glibc 中,仅仅是标记为可用,形成一个内存空洞 ( 碎片 ),并没有真正释放。如果此时需要分配 40k 以内的空间,可重用此空间,剩余空间形成新的小碎片。

(4) free(C) :C 空间大于 128K ,使用 mmap 分配,如果释放 C ,会调用 munmap 系统调用来释放,并会真正释放该空间,还给 OS ,如图 (4) 所示。

 所以free的内存不一定真正的归还给OS,随着系统频繁地 malloc 和 free ,尤其对于小块内存,堆内将产生越来越多不可用的碎片,导致“内存泄露”。而这种“泄露”现象使用 valgrind 是无法检测出来的。

 

  • 综上,频繁内存分配释放还会导致大量系统调用开销,影响效率,降低整体性能;

相关视频推荐

linux内存管理-庞杂的内存问题,如何理出自己的思路出来

内存泄漏的3个解决方案与原理实现,知道一个可以轻松应对开发

免费学习地址:C/C++Linux服务器开发/后台架构师

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

 

3. 常用解决上述问题的方案

内存池技术

内存池方案通常一次从系统申请一大块内存块,然后基于在这块内存块可以进行不同内存策略实现,可以比较好得解决上面提到的问题,一般采用内存池有以下好处:

1.少量系统申请次数,非常少(几没有) 堆碎片。 2.由于没有系统调用等,比通常的内存申请/释放(比如通过malloc, new等)的方式快。 3.可以检查应用的任何一块内存是否在内存池里。 4.写一个”堆转储(Heap-Dump)”到你的硬盘(对事后的调试非常有用)。 5.可以更方便实现某种内存泄漏检测(memory-leak detection)。

6.减少额外系统内存管理开销,可以节约内存;

内存管理方案实现的指标:

  • 额外的空间损耗尽量少
  • 分配速度尽可能快
  • 尽量避免内存碎片
  • 多线程性能好
  • 缓存本地化友好
  • 通用性,兼容性,可移植性,易调试等

各个内存分配器的实现都是在以上的各种指标中进行权衡选择.

4. 一些业界主流的内存管理方案

SGI STL allocator

是比较优秀的 C++库内存分配器(细节参考上面描述)

ptmalloc

是glibc的内存分配管理模块, 主要核心技术点:

 

 

  1. Arena-main /thread;支持多线程
  2. Heap segments;for thread arena via by mmap call ;提高管理
  3. chunk/Top chunk/Last Remainder chunk;提高内存分配的局部性
  4. bins/fast bin/unsorted bin/small bin/large bin;提高分配效率

ptmalloc的缺陷

  • 后分配的内存先释放,因为 ptmalloc 收缩内存是从 top chunk 开始,如果与 top chunk 相邻的 chunk 不能释放, top chunk 以下的 chunk 都无法释放。
  • 多线程锁开销大, 需要避免多线程频繁分配释放。
  • 内存从thread的areana中分配, 内存不能从一个arena移动到另一个arena, 就是说如果多线程使用内存不均衡,容易导致内存的浪费。比如说线程1使用了300M内存,完成任务后glibc没有释放给操作系统,线程2开始创建了一个新的arena, 但是线程1的300M却不能用了。
  • 每个chunk至少8字节的开销很大
  • 不定期分配长生命周期的内存容易造成内存碎片,不利于回收。64位系统最好分配32M以上内存,这是使用mmap的阈值。

tcmalloc

google的gperftools内存分配管理模块, 主要核心技术点:

 

  1. thread-localcache/periodic garbagecollections/CentralFreeList;提高多线程性能,提高cache利用率

TCMalloc给每个线程分配了一个线程局部缓存。小分配可以直接由线程局部缓存来满足。需要的话,会将对象从中央数据结构移动到线程局部缓存中,同时定期的垃圾收集将用于把内存从线程局部缓存迁移回中央数据结构中:

 2. Thread Specific Free List/size-classes [8,16,32,…32k]: 更好小对象内存分配;

每个小对象的大小都会被映射到170个可分配的尺寸类别中的一个。例如,在分配961到1024字节时,都会归整为1024字节。尺寸类别这样隔开:较小的尺寸相差8字节,较大的尺寸相差16字节,再大一点的尺寸差32字节,如此类推。最大的间隔(对于尺寸 >= ~2K的)是256字节。一个线程缓存对每个尺寸类都包含了一个自由对象的单向链表

 3. The central page heap:更好的大对象内存分配,一个大对象的尺寸(> 32K)会被除以一个页面尺寸(4K)并取整(大于结果的最小整数),同时是由中央页面堆来处理 的。中央页面堆又是一个自由列表的阵列。对于i < 256而言,第k个条目是一个由k个页面组成的自由列表。第256个条目则是一个包含了长度>= 256个页面的自由列表:

 4. Spans:

TCMalloc管理的堆由一系列页面组成。连续的页面由一个“跨度”(Span)对象来表示。一个跨度可以是_已被分配_或者是_自由_的。如果是自由的,跨度则会是一个页面堆链表中的一个条目。如果已被分配,它会是一个已经被传递给应用程序的大对象,或者是一个已经被分割成一系列小对象的一个页面。如果是被分割成小对象的,对象的尺寸类别会被记录在跨度中。

由页面号索引的中央数组可以用于找到某个页面所属的跨度。例如,下面的跨度_a_占据了2个页面,跨度_b_占据了1个页面,跨度_c_占据了5个页面最后跨度_d_占据了3个页面。

 tcmalloc的改进

  • ThreadCache会阶段性的回收内存到CentralCache里。解决了ptmalloc2中arena之间不能迁移的问题。
  • Tcmalloc占用更少的额外空间。例如,分配N个8字节对象可能要使用大约8N * 1.01字节的空间。即,多用百分之一的空间。Ptmalloc2使用最少8字节描述一个chunk。
  • 更快。小对象几乎无锁, >32KB的对象从CentralCache中分配使用自旋锁。并且>32KB对象都是页面对齐分配,多线程的时候应尽量避免频繁分配,否则也会造成自旋锁的竞争和页面对齐造成的浪费。

jemalloc

FreeBSD的提供的内存分配管理模块, 主要核心技术点:

1. 与tcmalloc类似,每个线程同样在<32KB的时候无锁使用线程本地cache;

  1. Jemalloc在64bits系统上使用下面的size-class分类:

Small: [8], [16, 32, 48, …, 128], [192, 256, 320, …, 512], [768, 1024, 1280, …, 3840] Large: [4 KiB, 8 KiB, 12 KiB, …, 4072 KiB] Huge: [4 MiB, 8 MiB, 12 MiB, …]

  1. small/large对象查找metadata需要常量时间, huge对象通过全局红黑树在对数时间内查找
  2. 虚拟内存被逻辑上分割成chunks(默认是4MB,1024个4k页),应用线程通过round-robin算法在第一次malloc的时候分配arena, 每个arena都是相互独立的,维护自己的chunks, chunk切割pages到small/large对象。free()的内存总是返回到所属的arena中,而不管是哪个线程调用free().

 上图可以看到每个arena管理的arena chunk结构, 开始的header主要是维护了一个page map(1024个页面关联的对象状态), header下方就是它的页面空间。Small对象被分到一起, metadata信息存放在起始位置。large chunk相互独立,它的metadata信息存放在chunk header map中。

  1. 通过arena分配的时候需要对arena bin(每个small size-class一个,细粒度)加锁,或arena本身加锁。并且线程cache对象也会通过垃圾回收指数退让算法返回到arena中。

 jemalloc的优化

  • Jmalloc小对象也根据size-class,但是它使用了低地址优先的策略,来降低内存碎片化。
  • Jemalloc大概需要2%的额外开销。(tcmalloc 1%, ptmalloc最少8B).
  • Jemalloc和tcmalloc类似的线程本地缓存,避免锁的竞争 .
  • 相对未使用的页面,优先使用dirty page,提升缓存命中。

性能比较

测试环境:2x Intel E5/2.2Ghz with 8 real cores per socket,16 real cores, 开启hyper-threading, 总共32个vcpu。16个table,每个5M row。OLTP_RO测试包含5个select查询:select_ranges, select_order_ranges, select_distinct_ranges, select_sum_ranges:

 facebook的测试结果:

 服务器吞吐量分别用6个malloc实现的对比数据,可以看到tcmalloc和jemalloc最好(tcmalloc这里版本较旧)。

总结

可以看出tcmalloc和jemalloc性能接近,比ptmalloc性能要好,在多线程环境使用tcmalloc和jemalloc效果非常明显。一般支持多核多线程扩展情况下可以使用jemalloc;反之使用tcmalloc可能是更好的选择。

思考问题:

1 jemalloc和tcmalloc最佳实践是什么?

2 内存池的设计有哪些套路?为什么?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/412780.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】二叉树顺序结构及实现

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a;初阶数据结构 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对…

Surfshark下载到使用完整教程|2023最新

2023年3月16日更新 在正式介绍surfshark的教程( 教程直达学习地址: qptool.net/shark.html )之前&#xff0c;我们可以来看看最近surfshark的服务与产品退化到什么程度了。我曾经是Surshark两年的忠实用户&#xff0c;但是&#xff0c;现在&#xff0c;作为一个负责人的测评&a…

PostMan动态参数及循环调用

最近需要在测试环境批量创建es索引&#xff0c;也就是某个接口需要循环调用且参数还是变化的&#xff0c;但是我又不想写代码和脚本&#xff0c;于是研究了一下postman一些好玩的功能&#xff0c;希望能节约大家的开发时间 一.设置请求参数 1.获取创建索引的请求以及参数&…

ELK+Filebeat日志分析系统

目录 一.ELK基本介绍 1.ELK是什么&#xff1f; 2.组件简介 2.1 ELK组件介绍 2.2 ELFK组件介绍 2.3 其它组件 4.使用ELK的原因 5.完整日志系统的基本特征 二.Elasticsearch的介绍 三.Logstash的介绍 四.Kibana的介绍 五.ELK的工作原理 六.部署ELK日志分析系统 1.环…

0基础学习软件测试有哪些建议

其实现在基础的资料和视频到处都是&#xff0c;就是看你有没有认真的去找学习资源了&#xff0c;去哪里学习都是要看你个人靠谱不靠谱&#xff0c;再好的教程和老师&#xff0c;你自己学习不进去也是白搭在正式选择之前&#xff0c;大可以在各种学习网站里面找找学习资源先自己…

springboot+vue动物园管理系统java

本系统使用的角色主要有系统管理员、注册用户&#xff0c;本系统分为系统前台和系统后台&#xff0c;首先在系统前台&#xff0c;游客用户可以经过账号注册&#xff0c;管理员审核通过后&#xff0c;用账号密码登录系统前台&#xff0c;查看论坛交流、动物展览、原生动物展览、…

HTML5 <head> 标签、HTML5 <i> 标签

HTML5 <head> 标签 实例 HTML5 <head> 标签表示文档的头部&#xff0c;其中包含了与该文档有关的信息&#xff01; 一份在头部带有 <title> 标签的 HTML 文档&#xff1a; <!DOCTYPE html> <html> <head> <meta charset"utf-8&…

Linux信号sigaction / signal

Linux信号sigaction / signal 文章目录Linux信号sigaction / signal目的函数原型struct sigaction信号枚举值ISO C99 signals.Historical signals specified by POSIX.New(er) POSIX signals (1003.1-2008, 1003.1-2013).Nonstandard signals found in all modern POSIX system…

虹科教您 | 基于Linux系统的RELY-TSN-KIT套件操作指南(1)——硬件设备与操作环境搭建

RELY-TSN-KIT是一款针对TSN的开箱即用的解决方案&#xff0c;它可以无缝实施确定性以太网网络&#xff0c;并从这些技术复杂性中抽象出用户设备和应用。该套件可评估基于IEEE 802.1AS同步的时间常识的重要性&#xff0c;并借助时间感知整形器来确定性地交付实时流量&#xff0c…

判断完全二叉树(层序遍历)| C

层序遍历 基本思路&#xff1a;利用队列&#xff0c;出上一层&#xff0c;带下一层&#xff08;NULL不入队列&#xff09; &#xff08;C语言需要自己构建队列→【队列】&#xff1c;用链表实现队列&#xff1e; | [数据结构] | C语言&#xff09; 代码 #include "Queu…

代码自动发布系统

之前是jenkins发现gitlab代码更新了就自动获取直接部署到服务器 现在是jenkins自动获取Code之后打包成镜像上传到仓库然后通知docker去拉取更新的镜像 分析 旧∶ 代码发布环境提前准备&#xff0c;以主机为颗粒度静态 新: 代码发布环境多套&#xff0c;以容器为颗粒度编译 …

Typora设置修改字体颜色快捷键

目录 1.typora如何设置修改字体颜色快捷键 2. AutoHotKey软件安装 3.typora关于AutoHotKey的具体操作 1.typora如何设置修改字体颜色快捷键 typora本身是不能直接修改字体颜色的&#xff0c;不过若是想修改还是可以用一些代码去改变的&#xff0c;但是每次都修改一次实在麻烦…

mysql常用的基础命令

通过学习mysql命令提高数据处理和工作效率 基础命令 1.登录MySQL mysql -u root -p 2.查看当前系统所有数据库 show databases; 3.切换数据库 use 数据库名称 4.查看数据库下的所有表 show tables; 5.查看表结构&#xff1b; desc 表名&#xff1b; 6.创建数据库 crea…

MAC OS(M1)安装配置miniconda

一、下载安装miniconda miniconde官网&#xff1a;Miniconda — Conda documentation M1最低只能适配到python3.8 打开终端,进入安装包所在文件夹&#xff0c;使用命令进行安装 bash Miniconda3-latest-MacOSX-arm64.sh一路回车 二、配置环境 安装完成后重启终端&#xf…

Unity ads广告插件的使用

介绍 Unity Ads SDK 由领先的移动游戏引擎创建,无论您在 Unity、Xcode 还是 Android Studio 中进行开发,都能为您的游戏提供全面的货币化框架。 使用 Unity Ads 将各种广告格式合并到游戏中的自然呈现点中。例如,您可以实施激励视频广告来构建更强大的游戏经济,同时为您的…

[C++笔记]vector

vector vector的说明文档 vector是表示可变大小数组的序列容器(动态顺序表)。就像数组一样&#xff0c;vector也采用连续的存储空间来储存元素。这就意味着可以用下标对vector的元素进行访问&#xff0c;和数组一样高效。与数组不同的是&#xff0c;它的大小可以动态改变——…

1700页,卷S人的 Java《八股文》PDF手册,涨薪跳槽拿高薪就靠它了

大家好&#xff0c;最近有不少小伙伴在后台留言&#xff0c;又得准备面试了&#xff0c;不知道从何下手&#xff01; 不论是跳槽涨薪&#xff0c;还是学习提升&#xff01;先给自己定一个小目标&#xff0c;然后再朝着目标去努力就完事儿了&#xff01; 为了帮大家节约时间&a…

Mybatis一级缓存和二级缓存(带测试方法)

目录 一、什么是缓存 二、Mabtis一级缓存 &#xff08;1&#xff09;测试一级缓存 &#xff08;2&#xff09;清空一级缓存 三、Mybatis二级缓存 &#xff08;1&#xff09;开启二级缓存 &#xff08;2&#xff09;测试二级缓存 一、什么是缓存 缓存是内存当中一块存储数…

蓝桥杯嵌入式第十一届省赛题目解析

写完第十一届蓝桥杯嵌入式省赛题目&#xff0c;拿出来给大家参考参考&#xff0c;也是让大家一起测试看看有什么问题还需要改进&#xff0c;代码在最后喔。 目录 客观题&#xff1a; 程序设计题 &#xff1a; 题目解析&#xff1a; CubeMX配置 代码演示 &#xff1a; 客观…

Windows环境下实现设计模式——职责链模式(JAVA版)

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天总结一下Windows环境下如何编程实现职责链模式&#xff08;设计模式&#xff09;。 不知道大家有没有这样的感觉&#xff0c;看了一大堆编程和设计模式的书&#xff0c;却还是很难理解设计模式&#xff…