DeepRoute Lab | C++ 内存管理介绍

C++中内存管理的五种方法 在C++中,内存管理可以说是非常重要的一环。从申请到释放、分配到回收都离不开对内存的正确管理,否则就无法避免内存泄漏、堆栈溢出等严重的问题。因此,掌握好C++中的内存管理机制无疑是十分重要的。这篇文章将通过介绍五种常用的内存管理方法,帮助读者了解如何高效地管理内存,提升代码质量。 阅读详情

文章概述

内存管理对于每种开发语言来说都是一个十分重要的话题;即使像Java这种拥有“复杂”垃圾收集器的语言,也会面临GC带来的各种困扰。

C++程序设计中的很多bug也是因为内存管理不善导致的,而且难以发现和排除;如何有条理地管理内存,对于C++开发尤为重要。

“他山之石可以攻玉”,在研究如何做好C++内存管理之前,我们也可以看下其他语言是怎么做内存管理的,有什么模式或者模型能为我们所借鉴,能够更好地帮助我们理解和做好C++的内存管理。

01

不同语言的内存管理机制介绍

C/C++、Java、Objective-C/Swift 和Golang 是几种使用广泛的语言,内存管理机制也相对典型。

1.1 C/C++

  C  也常被称作“可移植的汇编”,诞生之初主要是解决汇编语言的移植问题,在嵌入式和操作系统等相对底层的开发领域应用广泛;对于复杂的业务问题,因为它没有面向对象的能力(不好抽象业务逻辑),显得难以应付。

  C++ 是一种很弱的面向对象的语言(模版和Interface几乎是一种相互违背的思想)。为了兼容几乎所有的C特性,背上了比较重的历史包袱。在C++11之后,这种现象有了比较大程度的改善,各种新的语言特性可以让C++开发者开发出更优雅、健壮的代码。

C语言的内存管理是典型的手动管理机制,通过malloc申请,free释放。

C++语言除了手动管理之外,还拥有弱的“垃圾回收机制”(智能指针的支持)。

C/C++中常见的内存管理问题有:

a. 数组访问越界

(Java语言可抛出ArrayIndexOutOfBoundsException)

b. 内存访问超越生命周期

‣ 栈弹出之后,依旧进行访问

(函数返回内部栈地址)

‣ 堆内存释放,依旧进行访问

c. 内存泄露 (没有释放不再使用的内存)

d. 悬空指针导致的问题

‣ 指针指向内存释放之后,指针没有复位(设置为nullptr)

‣ 使用没有复位(不为null)的无效内存(已释放或者未申请的内存)

e. C++独有的问题

‣ 非预期内的拷贝构造函数调用带来的过度复制(性能问题)

‣ 不合理的复制、拷贝构造函数的实现,导致的意外数据共享(没有设置为nocopyable)

1.2 Java

Java 是一种面向对象的现代语言,有着丰富的语言特性和开发生态。Java语言是为了实现下一代智能家电的通用系统而设计的。在借鉴C++语言的基础上,又摒弃了C++

的一些复杂特性(可能降低软件开发质量)。

比如:

a. 不允许多继承

b. 更纯粹的interface

c. 所有皆对象(基础类型除外)

d. non-static方法默认支持多态

e. 等等

不想“有心栽花花不开,无心插柳柳成荫”。

Java 在家电市场毫无起色,却因为优异的网络编程支持能力、平台无关性、垃圾回收等能力,加上恰逢互联网时代的到来,而后在企业级市场上大放异彩。

Java 因为有虚拟机的支持(先编译成字节码,由虚拟机解释成不同平台的“语言”),可以做到“一次编译,到处运行”。

Java 目前在后台开发、大数据以及App开发领域(Kotlin也是类Java语言)有着非常广泛的应用。

Java 的内存管理依托于JVM的垃圾回收器(Garbage Collections)

一般而言,垃圾回收的步骤包括两步:

a. 找到可被回收的对象;

b. 进行内存回收和整理

JVM(HotSpot)的GC也是如此。

(一)可收回对象判断

 JVM GC基于可达分析,来查找可回收对象;可以避免引用计数方案的循环引用问题。

    (图1 基于根的可达对象分析)

(二)可回收策略和算法

几乎所有的垃圾回收器,都存在STW问题,高效回收以及降低对业务代码执行的影响是一件很难的事情。为了尽可能地优化性能,GC采用 分代收集 和 标记-清除/标记-清除-整理/标记-复制 进行内存回收。

‣ 分代收集 (新生代和老年代)

不难理解,新申请的内存比较大的概率可以在不久后删除;如果一个内存存在比较久了,那么接下来被回收的概率就会比较低;新生代的回收会比较轻量和高效,老年代的GC相对会比较重。

- G1之前基本都是下图这种典型的分代内存模型。

(图2 典型的内存分代模型)

- G1仍然保留了新生代和老年代的概念,但是新生代和老年代的内存区域不再固定,都是一系列的动态集合。

(图3 G1的内存分代模型)

‣ 标记-清除/标记-清除-整理/标记-复制

- 标记-清除 方法相对简单、高效,但是会存在内存碎片;

- 标记-清除-整理 可以解决内存碎片的问题,但是会增加GC的持续时间(好处大于坏处);

- 标记-复制 方法类似于ping-pang机制,需要有两片内存区域;在内存清理阶段,会将存活对象统一放到一个区域,而释放另外一个区域;和整理方法一样,也不会产生内存碎片,并且复制和标记可以同时进行,但是需要更多的内存区域。

JVM有多种垃圾收集器可供选择,需要根据业务需求(低延迟or高吞吐)进行权衡,CMS和G1使用相对较多。

a. CMS用于老年代的垃圾回收,追求最短停顿;

b. G1老年代和新生代都可以使用,并且相对高效;

c. Java11 推出的Z Garbage Collector(ZGC)有着不错的性能,目前基本可以投入生产。

(https://docs.oracle.com/en/java/javase/11/gctuning/z-garbage-collector1.html)

1.3 Objective-C/Swift

 Objective-C 是基于C语言发展出的面向对象的开发语言(Objective);Objective-C的语法相对繁琐、不够便捷,所以苹果在2014推出了Swift,拥有脚本语言般的表现力。

Objective-C 的内存管理基于简单的引用计数,

可以分为两类:

‣ MRR:Manual Retain-Release

(图4 Objective-C MRR机制)

‣ ARC:Automatic Reference Counting

ARC底层还是MRR,只是由编译器在恰当的位置帮我们插入retain和release。是否开启ARC支持和编译器版本以及编译器选项有关。

1.4 Golang

 Golang 也是具备垃圾回收的一种语言,主要应用在后端开发领域;回收策略也是基于可达对象分析和标记-清除-整理/复制算法。

和Java的比对,可以参考以下链接:

https://blog.mooncascade.com/go-vs-java-we-chose-go-and-you-should-too/

02

引用模型对对象生命周期的影响

不同的引用类型对对象的生命周期影响不一样,从语义上可分为三类:

‣ 强引用(Strong reference)

- 强引用对象,不可以被回收

- 如果是基于引用计数,引用计数会被影响

‣ 软引用(Soft reference)

- 非必要不回收,比如JVM在OOM之前会尝试对Soft reference对象进行回收

- 如果基于引用计数,会退化为弱引用

‣ 弱引用(Weak reference)  

- 不影响对象生命期

- 如果基于引用计数,不会影响引用计数

03

C++的内存管理方案

原则:尽量使用智能指针,不要担心智能指针带来性能损耗。

3.1 手动管理内存

在某些场景下,C++需要手动管理内存;我们可以使用一些技巧来更安全地使用和管理内存。

a. 避免悬空指针

(点击查看大图)

b. 基于Allocator 策略进行内存分配

- 通过Allocator可以改变stl容器的内存分配机制,比如为vector在栈上分配内存;或者使用内存池进行内存管理;

(点击查看大图)

3.2 COM 接口式内存管理

COM (Component Object Model)是微软在1993年提出的一种二进制兼容的方案或者标准,其中的思想还是挺值得插件开发借鉴(非windows平台)。

3.2.1 使用COM接口的优势

(一)COM接口可以解决插件开发领域的两个典型的兼容问题

a. 接口的内存布局结构变化带来的兼容问题

(图5 接口的内存布局变化导致的兼容问题)

b. 不同的编译器、不同系统源码库带来的兼容问题

(图6 内存管理不同版本带来的兼容问题)

(二)COM接口为什么可以解决上述的问题?

a. COM强调面向接口,插件的边界只能是interface,COM接口不允许有任何的数据域

(图7 COM严格以接口为边界)

b. COM接口需要暴露AddRef和Release接口,用来进行闭环(插件申请插件释放)的内存管理

3.2.2 COM接口例子

‣ 场景:

   Application需要一个插件来提供读和写的功能

‣ 原则:

所有的接口都要继承 IUnknown ,发布的interface都需要有唯一ID

‣ DEMO:

a. com.h

(点击查看大图)

b. interface.h 插件对外发布的接口

(点击查看大图)

c. export_api.h  是插件的唯一接口暴露点

(点击查看大图)

d. interface_impl.cpp 插件的功能实现,可以使用继承,也可以使用聚合的方法

​

(点击查看大图)

e. 插件的使用

(点击查看大图)

‣ Output:

(点击查看大图)

3.3 C++ 智能指针

C++11的很多特性都是先从boost引入技术报告(TR),然后进入到C++标准,智能指针就是如此。

(图8 C++标准演进)

不同类型智能指针的比较:

(点击查看大图)

3.3.1 shared_ptr

shared_ptr是使用最广泛的智能指针,可以进行所有权共享;当没有任何人持有,引用计数为0的时候内存自动释放。

智能指针内部有两个重要的块:

a.  数据块 指向内存地址的指针

b.  控制块 存放引用计数等信息

(图9 shared_ptr原理)

3.3.2 weak_ptr

weak_ptr是shared_ptr的伴生品,weak_ptr没有独立存在意义。

(图10 weak_ptr和shared_ptr的关系)

weak_ptr 可以解决share_ptr在两个场景下的问题:

a. shared_ptr的循环引用,会造成内存泄露

b. 观察者模式 被观察对象subject不应该影响observer的生命周期

(点击查看大图)

Output:

(点击查看大图)

3.3.3 unique_ptr

unique_ptr 指向对象的所有权独享,在出作用域unique_ptr析构时释放内存(和boost::scoped_ptr类似)。

如果要转移所有权,需要使用std::move。(类似的有std::thread,所有权独占)

(图11 unique_ptr的所有权转移)

3.3.4 intrusive_ptr

侵入式(智能)指针,和share_ptr用起来很像。intrusive_ptr提供了自定义引用计数的能力,适合用来管理第三方接口。比如用intrusive_ptr来管理COM接口。

只需要实现 IUnknown 类型的 intrusive_ptr_add_ref 和 intrusive_ptr_release 方法,就可以像share_ptr一样来使用COM接口了。

(点击查看大图)

Output:

(点击查看大图)

3.3.5 utilities

a. 使用make_shared/make_unique构造智能指针,减少构造性能损耗;(https://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared)

b. owner-based (as opposed to value-based) order  (https://en.cppreference.com/w/cpp/memory/shared_ptr/owner_before)

‣ owner-based order 

可以看作为控制块的比较,看受智能指针影响生命期的对象是不是一个;

‣ value-based order

可以看作数据块的比较,比较内存地址

(点击查看大图)

Output:

(点击查看大图)

c. shared_from_this

‣ 使用场景:

一个被智能指针管理的对象(class A的对象),在对象的内部,又要调用一个使用std::shared_ptr的函数。

‣ 网络场景示例:

connection表示一个链接,连接成功之后,在 run 函数内部调用 async_run ,实现异步读操作;这个时候需要把自己的智能指针传递进去,从而进行生命期的托管。

(点击查看大图)

模拟一个网络连接产生

(点击查看大图)

测试

(点击查看大图)

即使马上将connection变量释放,出了作用域之后,我们仍然可以进行read操作。

- 例子使用的Thread pool

(点击查看大图)

std::enable_shared_from_this本质上是一个语法糖,在基类中使用weak_ptr来帮我们避免了循环引用(自己引用自己)。

(点击查看大图)

谢谢观看!

R语言计算植物群落重要值:从基础概念到优势种判定 在植被生态学研究中,重要值是衡量物种在群落中地位的核心指标,它综合了相对多度、相对频度与相对显著度三个维度,能够客观反映物种的数量优势、分布广度与空间占据能力。理解重要值的生态学逻辑,是开展群落分类、优势种判定及多样性分析的基础。而R语言凭借其批量处理、可复现和与后续多元统计无缝衔接的优势,成为生态学研究者处理样方数据的高效工具。从野外记录的宽表到标准化长表,再到分组汇总计算相对指标,R语言能够显著提升数据处理效率与准确性。本文以实测群落数据为例,系统梳理重要值的两种算法口径、基于dplyr和tidyr的完 阅读详情

相关推荐

Dify+工作流,通过多模态实现发票自动识别

上个章节我们用Dify实现了基于本地知识库的聊天助手,本节我们通过工作流 ,可视化界面快速构建复杂的工作流和自动化流程。结合 OCR(光学字符识别)技术和工作流引擎,可以实现发票的自动识别、数据提取以及后续的审批流程。本文将详细介绍如何使用 Dify 和工作流来实现发票的自动识别与审批。

odin2005的博客 7425

C++内存的手动分配与释放

C++的内存管理,相对于高级语言Java、C#等有自己的GC回收机制而言,C++是需要程序员更主动来管理内存。查资料说C++的内存管理,有很多种:手动、RAII释放、scope_guard/defer释放技术、Boehm GC库等。一个好的C++资源管理策略,对您的游戏性能影响是非常大的。 本篇主要介绍手动分配与释放内存案例。new与delete运算符地运用。 /* * Author:W; * 内存手动管理 * 1.内存分配:new 运算符,为给定类型的变量在运行时分配堆内的内存,并返回所分配的空间地

成长ing中的w的专栏 3699

卫星图像检测,分割,跟踪,超分辨率,数据集调研,以及论文笔记

目标检测,针对物体有很多类如桥,烟囱,罐子等,有静态图像下的检测,也就是图像源头多来自谷歌地图,但是前后帧没有任何联系,并且谷歌地图相对来说比较清楚,难度小一点,也有粗粒度以及较细粒度的检测,然后卫星视频的数据集较少,则可以通过前后帧的联系来辅助检测,并且卫星帧基本上只对车,船,飞机,火车进行检测,没有进行细粒度的检测即将车辆分类为卡车,皮卡,小轿车,但是关船,则可以分类为货船以及军舰,因为目前的卫星视频中小车实在太少了,难以进行更细粒度的检测。luojia 三号卫星。

zhangzhao147的博客 4707

在C++中,哪些场景需要手动释放内存

在 C++ 中,手动管理内存和资源的责任可以很广泛。虽然现代 C++ 提供了智能指针和 RAII 技术来减少手动资源管理的需求,但在某些低层次编程和性能关键的应用中,手动管理内存和资源仍然是必不可少的。确保正确释放这些资源对于避免内存泄漏和其他资源管理问题至关重要。

aichu6610的博客 1144

别再手动抄表了!用PaddleOCR超轻量模型5分钟搞定数字仪表识别(附完整Python代码)

本文介绍如何利用PaddleOCR超轻量模型快速实现工业仪表数字识别自动化。通过5行核心代码和关键预处理步骤,显著提升识别准确率至98.2%,解决传统OCR在工业场景中的体积大、抗干扰弱等问题,并提供完整Python代码和边缘部署优化方案。

weixin_31062533的博客 350

OsgEarth —— 笔记1 - 创建地球的earth文件

效果         正文      1、准备一张全球的渲染图(tif格式)、一张全球的高程图(tif格式)        2、创建并编写.earth文件           在步骤1目录下创建china-simple.earth,并将如下内容写入。 <map name="Globe" type="geocentric" version="2"> <!-- 全球影像图 --> <image name="GlobeImage" driver="gdal"> &l

7681

C/C++内存管理【C++】

/ 例如 链表_val = 0;int _val;//构造,_val(val){}int main()//new操作符 在为自定义类型申请空间时,会去调用构造函数//如果new失败了后抛异常,不需要手动检查//delete操作符 会去调用析构函数return 0;在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。

加油!!! 1462

闲鱼爬虫实战:模拟手机端破解反爬策略,爬取指定商品搜索数据,爬取闲鱼搜索指定商品(需手机端模拟)o 技术点:抓包分析、cookie与token

技术点作用抓取手机 App 请求,分析 API 结构、Header、参数加密情况requestsPython 中最常用的 HTTP 库,用于发送模拟请求Session 对象自动管理 Cookie,保持会话User-Agent & 设备特征模拟手机型号、系统版本,绕过服务端检测闲鱼部分接口存在签名,需要逆向或复用有效 Token多线程/异步提高爬取效率(需节制,避免封 IP)解析 HTML 或 JSON 数据注意:闲鱼的接口加密程度中等偏上,部分接口存在动态签名(如x-signx-mini-wua。

2201_76125261的博客 712

C++初学者指南 内存管理篇--3.C++手动内存管理

本文介绍了C++手动内存管理的基础知识,包括栈内存、堆内存的分配和释放,以及避免内存泄漏和其他常见内存管理错误的建议和工具。重点强调了现代C++使用智能指针与之前手动管理原始指针的区别,并介绍了内存检查工具ASAN和Valgrind。文章还探讨了异常处理对内存管理的影响,以及如何通过合理的编程实现异常安全性。

silencestarsky的博客 1138

传感器校准与标定:传感器的灵敏度校准_(11).常见传感器的灵敏度校准方法

线性校准:适用于传感器输出与输入之间存在线性关系的情况。通过收集数据并使用最小二乘法拟合线性方程,可以确定斜率和截距,从而实现校准。非线性校准:适用于传感器输出与输入之间存在非线性关系的情况。通过收集数据并使用非线性拟合方法(如最小二乘法),可以确定多项式或其他非线性方程的参数,从而实现校准。温度补偿校准:适用于传感器在不同温度下输出特性变化的情况。通过收集不同温度下的数据并使用温度补偿方程,可以提高传感器在不同温度下的测量精度。其他校准方法。

2401_87715305的博客 917

C#联合Halcon机器视觉框架源码

本软件参考EV开发,共用模块,基于WPF+Halcon,是非常不错的学习框架,需要什么功能可以自己添加,插件式开发非常方便。

Z's Palace 4829

C/C++内存管理

delete p2;// new/delete和malloc/free最大区别是new/delete对于自定义类型除了开空间,还会调用构造函数和析构函数free(p3);delete p4;//内置类型几乎是一样的free(p5);return 0;*/运行结果说明:new和delete调用了构造函数和析构函数。

m0_74402251的博客 1865

arcgis计算栅格平均值

打开FC栅格数据和非水体矢量数据 利用矢量数据提取对应栅格的非水体部分 2.1打开空间分析工具à提取分析à按掩膜提取 输入对应栅格数据和矢量数据 2.2提取结果如下 进行计算FC平均值 3.1打开空间分析工具à多元分析à波段集统计 输入对应栅格和输出路径 3.2统计结果如下 ...

Z_W_H_的博客 5371

C/C++内存管理 ——

C/C++内存管理 —— C/C++内存分布、C语言中动态内存管理方式:malloc/calloc/realloc/free、C++内存管理方式、operator new与operator delete、new和delete的实现原理、定位new表达式(placement-new) 、malloc/free和new/delete的区别

祯的博客 841

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

C&C++内存管理

C&C++内存管理 1.内存分布 栈又叫堆栈,非静态局部变量/函数参数/返回值等等。 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。 堆用于程序运行时动态内存分配。 数据段存储全局变量和静态数据。 代码段可执行的代码/只读常量。 #include<stdio.h> int a = 0; //a在数据段 static int b = 0; //b在数据段 in...

weixin_43554618的博客 414

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

【C++】内存管理

c/c++内存管理

qq_40376612的博客 721

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

C++动态内存管理

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。...

ll1175555的博客 527

C/C++内存管理详解

本文主要从C/C++五大内存布局入手,整体贯穿性地介绍C++中new/delete的用法,带你掌握并吃透内存管理技术

做技术人 · 产优质博客 · 找好工作 4622

【重学C++】01| C++ 如何进行内存资源管理?

大家好,我是只讲技术干货的会玩code,今天是【重学C++】的第一讲,我们来学习下C++的内存管理。

zsgg123的博客 291

C++——内存管理

之前的文章我们把类和对象都给讲述完了,本篇文章也是一个很重要的部分,就是我们的内存管理,知道内存中的各个区域,以及它们分别都存储什么,那我们就开始正文了!new的原理1. 调用operator new函数申请空间2. 在申请的空间上执行构造函数,完成对象的构造delete的原理1. 在空间上执行析构函数,完成对象中资源的清理工作2. 调用operator delete函数释放对象的空间new T[N]的原理。

2401_84771520的博客 1279

浅析 C++内存管理

作为一个 C++程序员,理解内存分配和释放是很有必要的,本文将由浅入深,结合代码示例,浅析 C++中的内存管理机制。

qq_47353388的博客 1859

C&C++动态分配内存(手动分配内存)三种方式

1. malloc函数 函数原型:void *malloc(unsigned int size)函数的作用是:在内训的动态存储区开辟一个size个字节的连续空间,返回所分配区域的首字节地址。可以看到,函数返回值是一个void指针,请注意,void指针不是一个可以指向任何类型数据的指针,而是 说,不指向任何类型的数据,仅仅是提供了一个地址。因而,你想让这个指针指向int型数据,要进行显式的类型转...

weixin_30485799的博客 465

如何优雅地管理C++ 中的内存

C++的内存管理 C++是一门Native Language,而说到Native Languages就不得不说资源管理,其中内存管理又是资源管理中的一个大问题,由于堆内存需要手动分配和释放,所以必须确保申请的内存得到正确的释放。对此一般的原则是"谁分配的谁释放",但即便如此任然会出现内存泄漏,野指针等问题。 托管语言们为了解决这个问题引入了GC(Garbage Collection),它们认为”内...

记录学习的过程 909

自己管理C++内存(内存池)

内存管理的目的:提高速度,降低空间浪费率 减少malloc的使用。(可以先分配一大块,再一点一点的使用) 较少cookie的使用。 版本一 #include &lt;iostream&gt; using namespace std; class Screen { public: Screen(int x):i(x) {} int get() {...

幻想之渔 929
上一篇: Clang 新手教程来啦!
下一篇: DeepRoute Lab | 点云分割
DeepRoute_Lab
博客等级 码龄5年 25粉丝 15原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值