C/C++开发,无可避免的内存管理(篇五)-c++与垃圾回收.水中捞月

news2025/1/18 4:32:56

一、垃圾回收机制

        虽然智能指针帮助开展者简化了堆内存回收问题,但是它需要开发者显式声明,需要使用时判断等,还是不够便捷。而像java、Python、C#等开发语言直接支持垃圾回收机制。程序开发上,通常会将不再使用或没有任何指针指向的内存空间称为“垃圾”,将这些“垃圾”收集起来再次利用的机制就是“垃圾回收机制”。

        垃圾回收一般采用对象引用计数或对象关系图谱跟踪这两个策略。

        对象引用计数,就是使用系统记录对象被引用(引用、指针)的次数,当对象引用的次数变为0是,该对象内存就被视为“垃圾”而回收。使用引用计数的优点就是实现简单,计数的增减与对象使用紧密结合,不会造成程序暂停,也不会对系统缓存或交换内存造成冲击,“副作用”较少。但缺点就比较难处理“环形引用”的问题,另外每个对象内存管理都引入计数,还是有不少额外内存开销。

        对象关系跟踪处理,通过跟踪对象内存使用的关系图,通过标记-清除,标记-整理,标记拷贝等垃圾回收算法来实现垃圾内存回收。

        标记-清除,分为标记阶段和清扫阶段。主要工作是在标记阶段,将程序中正使用的对象视为"根对象",从根对象开始查找他们所引用的堆空间,斌在这些堆空间做标记,当标记结束后,所有被标记的对象为可达对象或者活对象,而没有被标记的对象被认为是“垃圾”,这些垃圾在清扫阶段就会被回收,清扫过后,会出现内存碎片问题。

        标记-整理,和标记-清除类似,只是在标记完成之后,不再变量所有对象执行清扫,而是将活对象向“左”靠齐重新整理,解决垃圾回收和内存碎片问题。但是由于移动了活的对象操作,因此会造成堆对象引用的更新。

        标记-拷贝,该算法会将堆空间划分为from、to两部分,系统只从from部分分配内存,当from部分分配满时,就开始垃圾回收:在from堆空间查找出所有活对象,并拷贝到to堆空间内及紧凑排列。然后将from整个空间清除,最后交换from和to空间角色,即from空间变为to空间,to空间为from空间,系统从新的from空间分配内存。这个算法会造成堆内存利用率只有一半。

二、c++最小垃圾回收支持

        2.1 最小垃圾回收支持由来

        c/c++一些第三方的库已经支持标记-清除方法的垃圾回收,比如一个比较著名的C/C++垃圾回收库-Boehm。该垃圾回收器需要程序员使用库中的堆内存分配函数显式地替代 malloc,继而将堆内存的管理交给垃圾回收器来完成垃圾回收。不过由于C/C++中指针类型的使用非常灵活,这样的库在实际使用中会有一些限制,可移植性也不好。

        为了解决垃圾回收中的安全性和可移植性问题,在2007年,惠普的Hans-J.Bochm (Boehm的作者)和赛门铁克的Mike Spertus共同向C++委员会递交了一个关于C++中垃圾回收的提案。该提案通过添加 gc_forbidden、gc_relaxed、gc_required、gc_safe、gc_strict 等关键字来支持C++语言中的垃圾回收。该提案甚至可以让程序员显式地要求垃圾回收。刚开始这得到了大多数委员的支持,后来却在标准的初稿中删除了,原因是该特性过于复杂,并且还存在一些问题(比如与显式调用析构函数的现有的库的兼容问题等)。所以,Boehm和Spertus对初稿进行了简化,仅仅保留了支持垃圾回收的最基本的部分,即通过对语言的约束,来保证安全的垃圾回收。这也是我们现在看到的C++11标准中的“最小垃圾回收支持”的历史来由。

        2.2 c++11标准的最小垃圾回收支持试水

        c++11标准做了最小垃圾回收支持,即设计了安全派生指针,支持以下操作:

  • 在解引用基础上的引用,例如:&*p。
  • 定义明确的指针操作,例如:p+1。
  • 定义明确的指针转换,例如:static_cast<void*>(p)。
  • 指针与整型之间的reinterpret_cast转移,例如:reinterpret_cast<long long>(p)。

        除了智能指针及上述操作支持外,c++11标准在头文件 <memory>还提供了垃圾收集相关函数支持,但又在新的c++23标准中移除:

//(C++11)(C++23 中移除)
/*
*声明一个对象不能被回收:声明 p 所引用的对象可抵达。可抵达对象将不为垃圾收集器删除,
*或不被泄漏检测器认为是泄露,即使所有指向它的指针都被销毁。对象可以声明为多次可抵达,
*该情况下会需要多次调用 *std::undeclare_reachable 以移除此属性。
*/
void declare_reachable( void* p );    
/*
*声明一个对象可以被回收:移除指针p所引用对象的可抵达状态,若先前为std::declare_reachable所设。
*若多次声明对象为可抵达,则需要相等次数的到 undeclare_reachable 的调用移除此属性。
*一旦对象不再声明为可抵达,且没有指针引用它,则它可能为垃圾收集器所回收,
*或被泄露检测器报告为泄露。
*/               
template< class T > T* undeclare_reachable( T* p ); 

/*
*告诉垃圾收集器或泄漏检测器,指定的内存区域(从 p 所指向的字节开始的 n 个字节)不含可追踪指针。
*若区域的任何部分在分配的对象内,则同一对象中必须含有整个区域
*/
void declare_no_pointers( char *p, std::size_t n );  
/*解除先前注册std::declare_no_pointers 的效果。*/
void undeclare_no_pointers( char *p, std::size_t n );//(函数)   
   
/*列出指针安全模式(枚举)*/
enum class pointer_safety { relaxed, preferred, strict};
/*返回当前的指针安全模式,即获得实现定义的指针安全模式,它是 std::pointer_safety 类型的值。*/       
std::pointer_safety get_pointer_safety() noexcept;

        在C++11的规则中,最小垃圾回收支持是基于安全派生指针这个概念的。程序员可以通过get_pointer_safety函数查询来确认编译器是否支持这个特性。get_pointer_safety的原型如下:
pointer_safety get_pointer_safety() noexcept其返回一个 pointer_safety 类型的值。如果该值为 pointer_safety::strict,则表明编译器支持最小垃圾回收及安全派生指针等相关概念,如果该值为 pointer_safety:relax 或是 pointer_safety:preferred,则表明编译器并不支持,基本上跟没有垃圾回收的C和C++98一样。不过按照一些解释,pointer_safety:preferred和pointer_safety:relax也略有不同,前者垃圾回收器可能被用作一些辅助功能,如内存泄露检测或检测对象是否被一个错误的指针解引用(事实上,几乎没有编译器实现了最小垃圾回收支持,甚至连get_pointer_safety 这个函数接口都还没实现)。
        此外,如果编译器支持到最小垃圾回收支持的话,程序员在代码中出现了指针不安全使用的状况,C++11允许程序员通过一些API来通知垃圾回收器不得回收该内存。C++11的最小垃圾回收支持使用了垃圾回收的术语,即需声明该内存为“可到达”的。

void declare_reachable( void* p );
template <class T>T *undeclare_reachable(T *p) noexcept;

        declare_reachable()显式地通知垃圾回收器某一个对象应被认为可达的,即使它的所有指针都对回收器不可见。undeclare_reachable()则可以取消这种可达声明。

#include <iostream>
#include <memory>

using namespace std;
void func1(void)
{
    int *pi = new int(1);
    declare_reachable(pi);  //在pi被隐藏前声明为可达的
    int *qi = (int*)((long long)pi^2023);
    cout << "*pi  = " << *pi << "\n";
    cout << "*qi  = " << *qi << "\n";
    //解除可达声明
    qi = undeclare_reachable<int>((int*)((long long)qi^2023));
    *pi = 11;
    cout << "*pi  = " << *pi << "\n";
    cout << "*qi  = " << *qi << "\n";
    *qi = 22;
    cout << "*pi  = " << *pi << "\n";
    cout << "*qi  = " << *qi << "\n";
}

int main(int argc, char* argv[])
{
    func1();
    return 0;
}
//out log
*pi  = 1
*qi  = 538976288    //随机
*pi  = 11
*qi  = 11
*pi  = 22
*qi  = 22

        上述代码是一个能够运行的例子。这里,我们在p指针被不安全派生(隐藏)之前使用declare reachable 声明其是可达的。这样一来,它会被垃圾回收器忽略而不会被回收。而在我们通过可逆的异或运算使得q指针指向p所指对象时,我们则使用了undeclare reachable来取消可达声明。注意undeclare reachable不是通知垃圾回收器 p所指对象已经可以回收。实际上,declare reachable和undeclare_reachable只是确立了一个代码范围,即在两者之间的代码运行中,p所指对象不会被垃圾回收器所回收。
        declare_reachable只需要传入一个简单的void*指针,但undeclare reachable却被设计为一个函数模板。这是一个极不对称的设计。但事实上undeclare_reachable使用模板的主要目的是为了返回合适类型以供程序使用。而垃圾回收器本来就知道指针所指向的内存的大小,因此 declare reachable 传入 void*指针就已经足够了。
        有的时候程序员会选择在一大片连续的堆内存上进行指针式操作,为了让垃圾回收器不关心该区域,也可以使用declare_no_pointers 及undeclare_no_pointers 函数来告诉垃圾回收器该内存区域不存在有效的指针。

void declare_no_pointers(char *p,size_t n) noexcept;
void undeclare_no_pointers(char *p, size_t n) noexcept;

        其使用方式与declare_reachable及undeclare_reachable类似,不过指定的是从p开始的连续n的内存。

        此外,C++11标准中对指针的垃圾回收支持仅限于系统提供的new 操作符分配的内存,而 malloc分配的内存则会被认为总是可达的,即无论何时垃圾回收器都不予回收。因此使用malloc等的较老代码的堆内存还是必须由程序员自己控制。

        2.3 基于boost::pool的垃圾回收机制

        Pool库 的作者是 Steve Cleary,boost::pool库提供了一个内存池分配器,它是一个工具,用于管理在一个独立的、大的分配空间里的动态内存。当你需要分配和回收许多不的对象或需要更高效的内存控制时,使用内存池是一个好的解决方案。

        boost::pool每次向系统申请一大块内存,然后分成同样大小的多个小块,形成链表连接起来。每次分配的时候,从链表中取出头上一块,提供给用户。链表为空的时候,pool继续向系统申请大块内存。

        另外还有在此基础上提供的boost::object_pool,它与boost::pool的区别在于pool需要指定每次分配的块的大小,object_pool需要指定每次分配的对象的类型。

         要注意的是,boost::pool库虽然可以按开发者意愿实现垃圾自动回收或主动回收,但它本质上不是一种通用的垃圾回收机制,仅是对象管理库。

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

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

相关文章

译文《Learning to Drive in a Day》

摘要——我们展示了深度强化学习在自动驾驶中的首次应用。从随机初始化的参数中&#xff0c;我们的模型能够使用单个单眼图像作为输入&#xff0c;在少数训练集中学习车道跟随策略。我们提供一个通用且易于获得的奖励&#xff1a;车辆在没有安全驾驶员控制的情况下行驶的距离。…

numpy常用操作

文章目录1 numpy库2 数组对象 ndarray2.1 数组对象的创建2.1.1 利用array函数创建ndarray对象2.1.2 np.ones()和np.zeros()函数2.1.3 np.random.rand()函数2.1.4 np.arange()函数2.1.5 np.linspace()函数2.1.6 np.empty()函数2.2 ndarray对象常用属性2.3 ndarray常用操作2.3.1 …

SkyWalking简介和安装

APM系统 早期的监控系统功能比较单一&#xff0c;主要以监控CPU、内存、网络、I/O等基础设置为主&#xff08;cacti、nagios&#xff09; 后来随着中间件技术的不断发展&#xff0c;监控系统也开始监控缓存、数据库、MQ等各种基础组件的性能&#xff08;zabbix、prommethus&a…

马斯克被曝正在“招兵买马”,准备进军AI赛道

知情人士透露&#xff0c;马斯克最近几周接触了人工智能研究人员&#xff0c;打算成立一个新的研究实验室&#xff0c;开发聊天机器人ChatGPT的竞品。 为了开展这项工作&#xff0c;马斯克一直在向Igor Babuschkin抛橄榄枝&#xff0c;这位研究人员曾经在谷歌和OpenAI任职&…

MySQL数据同步至ElasticSearch的相关实现方法

Python&#xff1a; MySQL数据同步到ES集群(MySQL数据库与ElasticSearch全文检索的同步) 通过logstash将mysql数据同步至es中 SpringbootElasticSearch构建博客检索系统-学习笔记01SpringbootElasticSearch构建博客检索系统-学习笔记02P43 43.新闻案例-数据库数据导入到ES中 13…

面了一个测试工程师,明显感觉他背了很多面试题...

最近有朋友去字节面试&#xff0c;面试前后进行了20天左右&#xff0c;包含4轮电话面试、1轮笔试、1轮主管视频面试、1轮hr视频面试。 据他所说&#xff0c;80%的人都会栽在第一轮面试&#xff0c;要不是他面试前做足准备&#xff0c;估计都坚持不完后面几轮面试。 其实&…

医疗床头卡(WIFI方案)

一、产品特性 7.5寸墨水屏显示WIFI无线通信&#xff0c;极简部署&#xff0c;远程控制按键及高亮LED指示灯指示800*480点阵屏幕锂电池供电&#xff0c;支持USB充电DIY界面支持文本/条码/二维码/图片超低功耗/超长寿命&#xff0c;一次充电可用一年基于现有Wifi环境&#xff0c…

【数据结构(四)】树

文章目录树1 树的基本概念1.1 树的定义1.2 基本术语1.3 数的性质2 二叉树的概念2.1 二叉树的定义与特性2.1.1 定义2.1.2 二叉树的性质2.2 几种特殊的二叉树2.2.1 满二叉树2.2.2 完全二叉树2.3 二叉树的存储结构2.3.1 顺序存储2.3.2 链式存储3 二叉树的遍历和线索二叉树3.1 二叉…

云端IDE系列教程:云原生 Terminal 大比拼 (看 ChatGPT 如何选择)

原文作者&#xff1a;行云创新技术总监 邓冰寒 概述 上一期在使用 Ubuntu 20.04 作为基础镜像而定制的 WeTTY 成功的在 TitanIDE 运行起来了 &#xff0c;非常适合程序员使用。能作为云原生 Terminal 的候选项目有 WeTTY、GoTTY、ttyd。接下来&#xff0c;我将在这三者中选择…

Java学习笔记 --- CSS

一、CSS技术介绍 CSS是「层样式表单」。是用于&#xff08;增强&#xff09;控制网页样式并允许将样式信息与网页内容分离的一种标记性语言。 二、CSS语法规则 三、CSS和HTML结合方式 第一种 在标签的style属性上设置key:value value”&#xff0c;修改标签样式 <!DOCTYP…

Zynq UltraScale系列使用MIPI CSI-2 RX Subsystem 解码MIPI视频PD输出 提供2套工程源码和技术支持

目录1、前言2、设计思路和架构3、vivado工程详解4、上板调试验证5、福利&#xff1a;工程代码的获取1、前言 本设计采用OV5640摄像头MIPI模式作为输入&#xff0c;分辨率为1280x72060Hz&#xff0c;MIPI解码方案采用Xilinx官方提供的MIPI CSI-2 RX Subsystem IP解码MIPI视频&a…

开源商城系统怎么选择?

随着电子商务的发展&#xff0c;开源商城系统越来越受到市场的重视&#xff0c;无论是大型企业还是小型企业&#xff0c;都在使用开源商城系统来提高自身的竞争力。 开源商城系统的优势&#xff1f; 开源商城系统可以节省企业的开发成本&#xff0c;减少企业的研发时间&#x…

【2023unity游戏制作-mango的冒险】-5.攻击系统的简单实现

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 收录于专栏&#xff1a;unity游戏制作 ⭐攻击系统的简单实现⭐ 文章目录⭐攻击系统的简单实现⭐&#x1f468;‍&#x1…

Ip2Region的Java本地实现

最近有个需求是将ip地址转换为对应的行政区划&#xff0c;存入数据库&#xff0c;在网上查阅相关资料&#xff0c;有ip2Region实现方案&#xff0c;记录下来&#xff0c;方便备查。 一、知识介绍 ip2region.xdb 即离线 IP 数据管理框架和定位库&#xff0c;免费。 数据格式&a…

北极星指标体系

北极星指标体系 每个产品都有很多指标,每个指标都反映了对应业务的经营情况。但是在实际业务经营中,却要求我们在不同的产品阶段寻找到合适的指标,让这个指标可以代表当前产品阶段的方向和目标,让这个指标不仅对业务经营团队,而且对产品的用户、对产品的价值都能有很好的…

多智能体集群协同控制笔记(1):线性无领航多智能体系统的一致性

对于连续时间高阶线性多智能体系统的状态方程为&#xff1a; x˙i(t)Axi(t)Bui(t),i1,2..N\dot {\mathbf{x}}_i(t)A\mathbf{x}_i(t)B\mathbf{u}_i(t),i1,2..N x˙i​(t)Axi​(t)Bui​(t),i1,2..N 下标iii代表第iii个智能体&#xff0c;ui(t)∈Rq1\mathbf{u}_i(t)\in R^{q \time…

【vulhub漏洞复现】Thinkphp 2.x 任意代码执行

一、漏洞详情影响版本 thinkphp 2.x但是由于thinkphp 3.0版本在Lite模式下没有修复该漏洞&#xff0c;所以也存在该漏洞漏洞原因&#xff1a;e 和 /e模式匹配路由&#xff1a;e 配合函数preg_replace()使用, 可以把匹配来的字符串当作正则表达式执行; /e 可执行模式&#xff0c…

vue入门

一、准备 本文是基于HBuilderX工具来讲解的。 1、工具 HBuilderX 2、 二、介绍 Vue是一个做网页的框架。 一个网页都是由多个模块&#xff08;组件&#xff09;拼成的。而Vue的组件化技术正式应用了这个思想。 三、创建项目 使用HBuilderX 创建项目。 1、创建空白项目…

【联机对战】微信小程序联机游戏开发流程详解

现有一个微信小程序叫中国象棋项目&#xff0c;棋盘类的单机游戏看着有缺少了什么&#xff0c;现在给补上了&#xff0c;加个联机对战的功能&#xff0c;增加了可玩性&#xff0c;对新手来说&#xff0c;实现联机游戏还是有难度的&#xff0c;那要怎么实现的呢&#xff0c;接下…

MySQL表的增删查改(基础)

gitee:博客中的所有操作整合新增语法:insert [into] table_name values(value_list)[案例] 创建一个学生表进行数据插入1.1单行数据全列插入[提示]我们可以想在记事本上写下命令,让后复制到数据库客户端,这样可以在出错的时候进行快速修改.同时为了美观和明了,我们可以进行适当…