内存泄漏问题,4种智能指针(介绍+模拟实现)

news2024/11/15 5:01:41

目录

内存泄漏

介绍

分类 

堆内存泄漏

系统资源泄漏

检测内存泄漏的方式

智能指针

引入

介绍

原理 

引入

RAII原则

指针性质

拷贝 

auto_ptr

介绍

代码

boost库

unique_ptr

介绍

代码 

shared_ptr

介绍

删除器 

代码 

问题(循环引用)

weak_ptr 

介绍

代码 


内存泄漏

介绍

内存泄漏是指在计算机程序中分配的动态内存(通常是堆内存)未被释放或回收的情况

这意味着程序在分配内存后,却没有及时释放它,使系统中的可用内存逐渐减少,最终可能导致程序运行变慢,系统崩溃,或者需要重新启动

分类 

堆内存泄漏

  • 程序执行中必须要通过malloc / calloc / realloc / new等方式,从堆中分配的一块内存
  • 用完后必须通过调用相应的 free或者delete 删掉
  • 假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak

系统资源泄漏

  • 指程序使用系统分配的资源,比方套接字、文件描述符、管道等
  • 从我们浅薄的linux知识可以知道,系统的各种结构是需要被管理起来的,这也就需要用一些资源去管理
  • 但如果没有使用对应的函数释放掉,就会导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定

检测内存泄漏的方式

在linux下内存泄漏检测:Linux下几款C++程序中的内存泄露检查工具_c++内存泄露工具分析-CSDN博客
在windows下使用第三方工具:VS编程内存泄漏:VLD(Visual LeakDetector)内存泄露库-CSDN博客
其他工具:https://www.cnblogs.com/liangxiaofeng/p/4318499.html

智能指针

引入

前面已经介绍了内存泄漏,但之前我们遇到的内存泄漏问题大多都是因为自己疏忽了,补上delete就行

但在接触了c++中的异常机制后,内存泄漏的问题就变得不好处理了

因为捕捉到异常后,会改变当前的执行流,可能会跳出好几层,一旦跳出后,之前申请到的资源就不好释放了

即使你可以在捕捉到异常后,先释放资源再抛出,一旦套了好几层写代码可累死

所以,智能指针就被研究了出来

介绍

是C++中用于管理动态内存分配的对象的指针,它们可以帮助开发人员避免内存泄漏和资源管理的复杂性

主要提供了auto_ptr,unique_ptr,shared_ptr和weak_ptr这四种指针

原理 

引入

  • 还记得我们遇到的问题吗,是遇到抛异常的情况会不好释放资源,而且有时候我们也会忘记释放
  • 究其根本我们必须要手动释放
  • 如果我们可以让资源自动释放,尤其是出了作用域之后自动释放,也就是借助对象的特性
  • 我们的对象都是出作用域后自动析构
  • 而这个特性其实就是RAII原则

RAII原则

RAII 是一种编程范式,代表资源获取即初始化

  • 它是一种用于资源管理的重要原则,尤其在C++中广泛应用
  • RAII 的核心思想是,资源(如内存、文件句柄、数据库连接等)的获取和释放应该与对象的生命周期相关联
  • 资源获取即初始化:在对象被创建时,资源也应该被分配
  • 对象超出作用域时,析构函数会自动调用,从而释放资源
  • 这样能确保资源不会泄漏,即使在出现异常的情况下也能正确处理资源

指针性质

除此之外,智能指针也需要具有指针的特性

我们虽然让他借助对象的特性,但也不能失去指针的性质

所以,我们需要在类内部重载->和*

拷贝 

智能指针之间最显著的区别就是处理拷贝的方法

最先在c++98就有auto_ptr的出现,但是在当时被骂惨了,现在公司也明确不能使用这玩意,就是因为它处理拷贝的方式很怪

auto_ptr
介绍
  • 具有独占所有权的特性,也就是在拷贝或赋值时接管内存的所有权,从而避免了多个智能指针同时管理同一块内存的情况
  • 但可能有些人不知道这个特性,使用了被拷贝对象,这样就可能导致不可预测的行为
代码
#include "head.h"

//拷贝时,将被拷贝对象置空
namespace my_auto_ptr
{
    template <class T>
    class auto_ptr
    {
    public:
        auto_ptr(T *p) : _ptr(p)
        {
        }
        auto_ptr(auto_ptr<T> &p) : _ptr(p._ptr)
        {
            p._ptr = nullptr;
        }
        auto_ptr(auto_ptr<T> &&p) : _ptr(p._ptr)
        {
            p._ptr = nullptr;
        }
        auto_ptr<T> &operator=(auto_ptr<T> &p)
        {
            _ptr = p._ptr;
            p._ptr = nullptr;
            return *this;
        }
        T &operator*(){
            return *_ptr;
        }
        T *operator->(){
            return _ptr;
        }

        ~auto_ptr()
        {
            delete _ptr;
        }

    private:
        T *_ptr;
    };
}

然后,在boost库中,提供了更加实用的的scoped_ptr和shared_ptr和weak_ptr

boost库

Boost C++ 库是一个开源的、高质量的C++库集合,它扩展和增强了C++语言的功能,提供了许多工具和组件,用于各种领域的应用开发

Boost库的目标是成为C++标准库的候选扩展,因此它的设计非常高质量,且符合现代C++编程标准

而它也不负众望的被c++标准库采用了

c++11提供了unique_ptr和shared_ptr和weak_ptr,其中unique_ptr对应boost 的scoped_ptr

并且这些智能指针的实现原理是参考boost中的实现的

unique_ptr
介绍
  • 它是一种独占所有权的智能指针,意味着只有一个实例可以拥有和管理特定资源,它负责在对象不再需要时自动释放资源
  • 虽然和auto_ptr产生的是一样的结果,但处理方式不同,unique_ptr是直接禁止拷贝,而不是像auto_ptr那样,不禁止却不能多个指向一份资源
代码 
#include "head.h"

//不允许拷贝
namespace my_unique_ptr
{
    template <class T>
    class unique_ptr
    {
    public:
        unique_ptr(T *p) : _ptr(p)
        {
        }
        unique_ptr(unique_ptr<T> &p) = delete; //直接定义为删除的函数
        unique_ptr(unique_ptr<T> &&p) : _ptr(p._ptr)
        {
            p._ptr = nullptr;
        } 
        unique_ptr<T> &operator=(unique_ptr<T> &p) = delete;  //同理
        T &operator*()
        {
            return *_ptr;
        }
        T *operator->()
        {
            return _ptr;
        }

        ~unique_ptr()
        {
            delete _ptr;
        }

    private:
        T *_ptr;
    };
}
shared_ptr
介绍
  • shared_ptr才是我们的重头戏,因为只有他支持了正常的拷贝操作,是我们最为实用的智能指针
  • 允许多个智能指针共享同一个资源,这意味着它可以用于协同管理资源,特别是在涉及共享拥有权的情况下非常有用
  • 他内部维护了一个引用计数,可以记录当前资源有多少个指针引用,当引用计数降至零时,资源会被自动释放
  • 如何保证引用计数可以让每份资源对应一个计数值呢?
  • 如果是int类型成员变量,每个指针就有独立的引用计数了,毫无意义,我们要让指向一片资源的共享引用计数
  • 所以可以考虑动态开辟一个引用计数,在资源被申请时开辟,拷贝时直接拷贝指针即可
删除器 
  • 外部开辟空间的方式有很多种,我们必须得依据开辟方式来确定析构方式
  • 所以我们可以考虑直接向类中传递析构方式(因为在内部无法判断是哪种)
  • 传递也有两种方式,给类传还是给构造函数传
  • 由于库中是给构造函数传的,所以我们也这样做
  • 但是,我们需要一个统一的类型来接收删除器啊
  • 哎~之前学过的适配器就可以用上了,因为需要传的都是一个指针,且没有返回值
  • 所以适配器的类型就是 -- function<void(T *)>
代码 
#include "head.h"

#pragma once

// 可以拷贝,但在循环引用的情况下,无法使用
// 外部开辟空间有多种方式:new/new[]/malloc,所以需要传入删除器
namespace my_shared_ptr
{
    template <class T>
    class shared_ptr
    {
    public:
        shared_ptr(T *p = nullptr) : _ptr(p)
        {
            _count = new int(1); // 每份资源对应一个计数
        }
        shared_ptr(const shared_ptr<T> &p, function<void(T *)> del) //不传默认是delete
            : _ptr(p._ptr), _count(p._count),_del(del)
        {
            ++(*_count);
        }
        shared_ptr(shared_ptr<T> &&p)
            : _ptr(p._ptr),_del(p._del)
        {
            --(p._count);
            p._ptr = nullptr;

            _count = new int(1);
        }
        shared_ptr<T> &operator=(shared_ptr<T> &p)
        {
            if (p._ptr == this->_ptr) // 防止自赋值(空间会提前释放)/引用同一片资源对其赋值(效率低)
            {
                return *this;
            }
            if (--this->_count == 0) // 如果this指向的空间已经没有人引用了,需要手动释放(因为当前该指针的生命周期还没有结束)
            {
                _del(_ptr);
                delete _count;
            }
            _ptr = p._ptr;
            _count = p._count;
            _del=p._del;
            ++(*_count);
            return *this;
        }
        T &operator*() const
        {
            return *_ptr;
        }
        T *operator->() const
        {
            return _ptr;
        }
        T *get() const //给weak_ptr使用的
        {
            return _ptr;
        }

        ~shared_ptr()
        {
            if (--(*_count) == 0)  //只有当引用计数为0时才释放空间
            {
                _del(_ptr);
            }
        }

    private:
        T *_ptr;
        int *_count; // 引用计数
        function<void(T *)> _del = [](T *p)
        { delete p; }; // 删除器
    };
}
问题(循环引用)

看着似乎shared_ptr完美无缺了,但是,当遇到下面这种情况时,会发生内存泄漏

struct ListNode
{
 int _data;
 shared_ptr<ListNode> _prev;
 shared_ptr<ListNode> _next;
 ~ListNode(){ cout << "~ListNode()" << endl; }
};

int main()
{
 shared_ptr<ListNode> node1(new ListNode);
 shared_ptr<ListNode> node2(new ListNode);
 cout << node1.use_count() << endl;
 cout << node2.use_count() << endl;

 node1->_next = node2;
 node2->_prev = node1;
 cout << node1.use_count() << endl;
 cout << node2.use_count() << endl;
 return 0;
}

本身,他俩各自引用计数都为1,但是!!!其内部还有俩shared_ptr指针,然后互相一指,让他俩计数都变成2

最后,当俩对象析构后,引用计数仍为1,这样就会导致内存泄漏

为什么呢?

  • node1如果要释放,需要_p释放,也就是node2析构,但node2被node1指着,只有当node1释放才行,这就又回到最开始了
  • 也就是node1释放需要node2先释放,而node2也一样,它释放需要让node1先释放,两者成为一种纠缠态
  • 这也被叫做"循环引用"问题
  • 所以,为了解决这个问题,提出了weak_ptr
weak_ptr 
介绍

用于解决潜在的循环引用问题,它允许你共享资源的引用,但不会增加资源的引用计数,从而避免了循环引用导致的内存泄漏

代码 
#include "shared_ptr.hpp"

// 由shared_ptr构造,没有其他功能
namespace my_weak_ptr
{
    template <class T>
    class weak_ptr
    {
    public:
        weak_ptr()
            : _ptr(nullptr) {}
        weak_ptr(const my_shared_ptr::shared_ptr<T> &p)
            : _ptr(p.get()) {}

        weak_ptr<T> &operator=(const my_shared_ptr::shared_ptr<T> &p)
        {
            _ptr = p.get();
            return *this;
        }

        T &operator*()
        {
            return *_ptr;
        }
        T *operator->()
        {
            return _ptr;
        }

        ~weak_ptr()
        {
            _ptr=nullptr;
        }

    private:
        T *_ptr;
    };
}

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

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

相关文章

Visual Studio 2019部署桌面exe(笔记)

一、使用Visual Studio自带的Publish功能 上述两张图片一般会自动加载&#xff0c;只需要查看一下即可。 签名问题&#xff1a; 生成exe执行文件 双击setup.exe 桌面生成&#xff08;默认图标&#xff09; 换图标&#xff1a; 对应桌面生成的exe

10个免费的logo设计神器

logo是标志或商标的英文声明&#xff0c;是指企业为自己设计logo的行为。随着技术的发展&#xff0c;许多logo设计在线生成器已经在互联网上诞生&#xff0c;供您使用和参考。通过图像logo设计&#xff0c;消费者可以记住公司的业务或品牌文化&#xff0c;并发挥识别和推广的作…

最新校园说明会日程安排-ABeam(德硕)旗下艾宾信息技术开发(上海) 德硕管理咨询(深圳)

艾宾信息技术开发&#xff08;上海&#xff09; 2024校园招聘 招聘岗位 公司介绍 福利待遇 联系我们 行程一览 华东理工大学校园宣讲会 日期&#xff1a;2023年10月23日&#xff08;周一&#xff09; 时间&#xff1a;14:00-16:00 地点&#xff1a;上海市徐汇区梅陇…

语雀崩了,免费送VIP6个月,赶紧薅!!

一、前言 在一个无聊的周一&#xff0c;下午浑浑噩噩的时候&#xff0c;一条公众号信息引起我的关注。 什么东西&#xff1f;语雀这种量级的产品也能崩&#xff1f; 看了一下还真是官方公众号发的&#xff01;&#xff01; 心里不由得出现&#xff0c;完蛋整个团队要打包遣…

Python武器库开发-面向对象篇(五)

面向对象篇(五) 面向对象编程 是最有效的软件编写方法之一。在面向对象编程中&#xff0c;你编写表示现实世界中的事物和情景的类&#xff0c;并基于这些类来创建对象。编写类时&#xff0c;你定义一大类对象都有的通用行为。基于类创建对象 时&#xff0c;每个对象都自动具备…

Android Studio 查看Framework源码

1、背景 安卓系统源码量很庞大&#xff0c;选择好的开发工具和方式去开发可以提升开发效率&#xff0c;常用的开发工具有Source Insight 、Visual Studio Code、Android Studio&#xff0c;vscode适合C和C代码开发&#xff0c;java层代码无法跳转和提示&#xff0c;因此&#…

Java——关于实现多线程的测试小题,帮助我们更好的理解多线程的使用方法

前面讲解了关于多线程的使用方法&#xff0c;这篇文章则是进行实战&#xff0c;做几道测试题。 感兴趣的情况下可以看一下Java多线程 多线程练习1 (卖电影票) 一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒要求:请用多线程模拟卖票过程并打印剩余电影…

37 深度学习(一):查看自己显卡的指令|张量|验证集|分类问题|回归问题

文章目录 查看自己显卡的指令框架选什么张量的阶数验证集存在的意义分类问题一般的全连接的代码格式&#xff08;板子&#xff09;上面训练的详解一些省略梯度消失和梯度爆炸Dropout 回归问题一般回归的全连接的板子 batch-size超参数搜索策略 此系列的深度学习主要是理论性的介…

Linux docker 安装 部署

docker 安装 linux系统离线安装docker 如何使用docker部署c/c程序 常用命令 给予 docker 访问 gui 的权限 在 /etc/profile 末尾添加 if [ "$DISPLAY" ! "" ] thenxhost fi在执行 更新 source /etc/profiledocker下载镜像 docker search gcc #搜索d…

EtherNet Ip工业RFID读写器与欧姆龙PLC 配置示例说明

一、准备阶段 POE交换机欧姆龙PLC 支持EtherNet Ip协议CX-Programmer 9.5配置软件 二、配置读卡器 1、打开软件 2、选择网卡&#xff0c;如果多网卡的电脑请注意对应所接的网卡&#xff0c;网卡名一般为“Network adapter Realtek PCIe GBE Family” 3、点击“选择网卡”&…

数据可视化报表分享:区域管理驾驶舱

在零售数据分析中&#xff0c;区域管理驾驶舱报表是用来分析企业运营数据&#xff0c;以制定销售策略和提高利润。因此这张报表需要整合大量数据&#xff0c;数据整合、分析、指标计算的工作量极大&#xff0c;在讲究高效率、高度及时性的大数据时代&#xff0c;BI数据可视化分…

APP上架怎么避免麻烦应对解决方案和替代方案

在当今数字化的时代&#xff0c;应用程序已成为现代生活中连接人与科技的桥梁。各个行业精准地抓住这一趋势&#xff0c;踊跃地推出自家APP&#xff0c;为用户提供一站式的便捷服务。然而&#xff0c;APP上架的过程并非一帆风顺。许多开发者会在上架过程中遇到麻烦&#xff0c;…

DC电源模块的短期过载能力

BOSHIDA DC电源模块的短期过载能力 DC电源模块是一种专门用来将交流电源转换为稳定直流电源的电子元件&#xff0c;适用于各种场合&#xff0c;如电子产品制造、通信、无线电、医疗等。在使用DC电源模块时&#xff0c;短期过载能力是考察其质量的重要指标之一。 短期过载能力…

共赢未来 | 大势智慧与安康市自然资源信息科技有限公司达成战略合作

10月17日至18日&#xff0c;安康市自然资源信息科技有限公司总经理黄光俊带领技术团队到武汉大势智慧科技有限公司围绕“实景三维中国、数字化建设”开展交流调研&#xff0c;并签署战略合作协议。 双方表示将以市场需求为导向&#xff0c;以技术创新为依托&#xff0c;建立长期…

nginx 动静分离 防盗链

一、动静分离环境准备静态资源配置(10.36.192.169)安装nginx修改配置文件重启nginx 动态资源配置(192.168.20.135)yum安装php修改nginx配置文件重启nginx nginx代理机配置&#xff08;192.168.20.134&#xff09;修改nginx子自配置文件重启nginx 客户端访问 二、防盗链nginx防止…

使用非空断言解决Typescript报错:对象可能为 “null“

现象如下&#xff1a; 解决办法&#xff1a;在报错的属性后面加惊叹号&#xff01;&#xff0c; 也就是非空断言 问题解决&#xff1a;

01.5.Binding

参考JusterZhu视频和文档 <TextBox.Text><Binding Path"GivenName" UpdateSourceTrigger"PropertyChanged" Mode"TwoWay"><Binding.ValidationRules><local:AgeRangeRule></local:AgeRangeRule></Binding.Val…

uni-app医院智能导诊系统源码

随着科技的迅速发展&#xff0c;人工智能已经逐渐渗透到我们生活的各个领域。在医疗行业中&#xff0c;智能导诊系统成为了一个备受关注的应用。本文将详细介绍智能导诊系统的概念、技术原理以及在医疗领域中的应用&#xff0c;分析其优势和未来发展趋势。 智能导诊系统通过人工…

74 应急响应-winlinux分析后门勒索病毒攻击

目录 操作系统(windows&#xff0c;linux)应急响应&#xff1a;常见日志类别及存储&#xff1a;补充资料&#xff1a;病毒分析病毒查杀病毒动态在线病毒扫描网站 演示案例:攻击响应-暴力破解(RDP,SSH)-Win,Linux控制响应-后门木马(Webshell,PC)-Win,Linux危害响应-病毒感染(勒索…

VSCode 设置热更新

热更新&#xff1a;文件保存后页面自动刷新&#xff0c;用于提高开发效率。 1.打开应用商店&#xff0c;搜索 live server &#xff0c;选择第一个&#xff0c;点击安装。 2.随便打开一个 HTML 文件&#xff0c;在文件中右键点击&#xff0c;选择 Open with Live Server 打开网…