首页 > 系统相关 >C++---智能指针和内存泄露

C++---智能指针和内存泄露

时间:2024-11-18 19:18:11浏览次数:3  
标签:int C++ --- 内存 new shared ptr

文章目录

智能指针

智能指针在<memory>头文件中。

发展历史

C++11前

  • C++标准库提供了一个auto_ptr
    在这里插入图片描述

    • **auto_ptr提供了拷贝,但是其拷贝是 管理权转移 **
      管理权转移:把原智能指针的资源转移到要拷贝到的智能指针中去,原智能指针置为空

      struct Date
      {
          int _year;
          int _month;
          int _day;
      
          Date(int year = 1, int month = 1, int day = 1)
              :_year(year)
              , _month(month)
              , _day(day)
          {}
      
          ~Date()
          {
              cout << "~Date()" << endl;
          }
      };
      int main()
      {
      	auto_ptr<Date> ap1(new Date);
      	// 拷贝时,管理权限转移,被拷贝悬空
      	auto_ptr<Date> ap2(ap1);
      
      	//ap1->_year++;  这里会直接崩,因为ap1的管理权被转移到ap2中,ap1中的指针被置为空了。
      
      	return 0;
      }
      
  • Boost库提供了一些智能指针

    Boost库是为C++语言标准库提供扩展的一些C++程序库的总称。

    Boost库由Boost社区组织开发、维护。其目的是为C++程序员提供免费、同行审查的、可移植的程序库。Boost库可以与C++标准库共同工作,并且为其提供扩展功能。Boost库使用Boost License来授权使用,根据该协议,商业或非商业的使用都是允许并鼓励的。

    Boost社区是由C++标准委员会的成员Dawes发起的,目的是C++的标准化工作提供可供参考的实现。因此Boost库也被很多人当作C++的准标准库,C++后续的更新有很多是将Boost库中一些改造后录入的

    • scoped_ptr/scoped_array:防拷贝(即禁止拷贝),也是C++11中unique_ptr的前身
    • shared_ptr/shared_array:通过引用计数的方式支持拷贝。也是C++11中shared_ptr的前身
    • weak_ptr:不同于上面的智能指针,不支持直接管理资源,配合解决shared_ptr的一个缺陷(循环引用,循环引用会导致内存泄露)。 也是C++11中weak_ptr的前身

C++11引入的智能指针

  • unique_ptr:防拷贝(即禁止拷贝),支持移动构造,但是其拷贝构造 和 赋值 被delete,实现防拷贝
    在这里插入图片描述

    • 当new多个数据的时候,进行析构的方法

      • 通过语法中,第二个 声明语法:template < class T , class D = default_delete<T> > class unique_ptr;。将定制删除器传入第二个模板参数中,这个定制删除器是在析构函数中被调用
        定制删除器其实是 可调用对象。

        struct Date
        {
            int _year;
            int _month;
            int _day;
        
            Date(int year = 1, int month = 1, int day = 1)
                :_year(year)
                , _month(month)
                , _day(day)
            {}
        
            ~Date()
            {
                cout << "~Date()" << endl;
            }
        };
        
        template<class T>
        class DeleteArray
        {
        public:
            void operator()(T* ptr)
            {
                delete[] ptr;
            }
        };
        
        class Fclose
        {
        public:
            void operator()(FILE* ptr)
            {
                cout << "fclose:" << ptr << endl;
                fclose(ptr);
            }
        };
        
        int main()
        {
        
            // 定制删除器
            unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);
            unique_ptr<FILE, Fclose> up3(fopen("test.cpp", "r"));
        
        
            return 0;
        }
        
      • 通过语法中,第二个 声明语法:template <class T , class D> class unique_ptr<T[] , D>;这样的声明在析构时,会析构多个数据

        struct Date
        {
            int _year;
            int _month;
            int _day;
        
            Date(int year = 1, int month = 1, int day = 1)
                :_year(year)
                , _month(month)
                , _day(day)
            {}
        
            ~Date()
            {
                cout << "~Date()" << endl;
            }
        };
        
        int main()
        {
            unique_ptr<Date[]> up2(new Date[5]);
            return 0;
        }
        
    • 一些接口
      在这里插入图片描述

      int main()
      {
          unique_ptr<FILE, Fclose> up3(fopen("test.cpp", "r"));
      }
      
  • shared_ptr:通过引用计数的方式支持拷贝。 引用计数和存储数据是分离的。引用计数需要一小块内存去存储,因此大量使用shared_ptr会有内存碎片的风险,因此shared_ptr的构造函数也支持自己传内存池来管理这些东西。
    在这里插入图片描述

    • 管理多个对象时

      int main()
      {
          shared_ptr<Date[]> sp4(new Date[5]);
      }
      
    • shared_ptr的定制删除器是传入构造函数里,然后在析构函数中调用

      int main()
      {
          shared_ptr<FILE> sp5(fopen("test.cpp", "r"), Fclose());
          
          //上面代码相当于下面代码,shared_ptr是将定制删除器传入构造函数中,unique_ptr是将定制删除器传入模板参数中
      	unique_ptr<FILE, Fclose> up3(fopen("test.cpp", "r"));
      }
      
    • 一些接口
      在这里插入图片描述

      ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=memory_1_6.png&pos_id=img-h4fVHSuy-1731928899472)int main()
      {
      	shared_ptr<Date> up1(new Date);
          
          shared_ptr<Date> up2(up1); 
          
          up2->_year++;
          up1->_year++;
          up1->_day = 10;
      }
      
    • make_shared函数:make_shared创建shared_ptr的时候,是同时分配对象和引用计数控制块,可以减少内存碎片化的问题

      传入 要构造的对象(支持隐式类型转化),返回 对应的shared_ptr
      在这里插入图片描述

      int main()
      {
          shared_ptr<Date> sp6 = make_shared<Date>(2024, 8, 5);//此处的Date有3个参数的构造函数,先构造出Date类型的对象,sp6中的指针指向该对象
      }
      
  • weak_ptr:不支持RAII,weak_ptr不支持管理资源。
    其构造函数
    在这里插入图片描述

    在这里插入图片描述

    如图所示,weak_ptr不支持通过资源进行构造,支持shared_ptr进行构造,shared_ptr进行构造时,不会增加引用计数。operator=支持shared_ptr赋值给weak_ptr
    weak_ptr有自己的引用计数的,weak_ptr的引用计数用来监督

    • 在这里插入图片描述

      weak_ptr 的 lock 函数用于创建一个新的 shared_ptr 对象,该对象共享被管理对象的所有权。如果 weak_ptr 指向的对象已经被释放,则返回一个空的shared_ptr

      struct ListNode
      {
      	int _data;
      	weak_ptr<ListNode> _next;
      	weak_ptr<ListNode> _prev;
      
      	~ListNode()
      	{
      		cout << "~ListNode()" << endl;
      	}
      };
      
      int main()
      {
      	std::weak_ptr<ListNode> wp;
      	std::shared_ptr<ListNode> sp;
      
      	{
      		std::shared_ptr<ListNode> n1(new ListNode);
      		wp = n1;
              //expired()是返回weak_ptr指向的资源是否过期。 过期返回true,不过期返回false
      		cout << wp.expired() << endl;
      		n1->_data++;
      		sp = wp.lock();
      	}
      	cout << wp.expired() << endl;
      
      	return 0;
      }
      

智能指针的使用及原理

  • RAII

    RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内 存、文件句柄、网络连接、互斥量等等)的简单技术。

    在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在 对象析构的时候释放资源。

    借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做 法有两大好处:

    • **不需要显式地释放资源。 **
    • 采用这种方式,对象所需的资源在其生命期内始终保持有效。
    • 使用RAIII思路设计的smartPtr类。
      这里没有提供拷贝,实际上智能指针的拷贝也是一个十分麻烦的事情,因为直接拷贝会导致多次析构的情况

      // 使用RAII思想设计的SmartPtr类
      template<class T>
      class SmartPtr {
      public:
          SmartPtr(T* ptr = nullptr)
              :
              _ptr(ptr)
          {}
          ~SmartPtr()
          {
              if (_ptr)
                  delete _ptr;
          }
      private:
          T* _ptr;
      };
      int div()
      {
          int a, b;
          cin >> a >> b;
          if (b == 0)
              throw invalid_argument("除0错误");
          return a / b;
      }
      void Func()
      {
          SmartPtr<int> sp1(new int);
          SmartPtr<int> sp2(new int);
          //SmartPtr<int> sp3(sp2); 当Func函数栈帧销毁时,这里会导致两次调用析构,导致程序崩溃
          cout << div() << endl;
      }
      

shared_ptr的循环引用

循环引用的场景

struct ListNode
{
	int _data;

	shared_ptr<ListNode> _next;
	shared_ptr<ListNode> _prev;

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

int main()
{
	// 循环引用 -- 内存泄露
	shared_ptr<ListNode> n1(new ListNode);
	shared_ptr<ListNode> n2(new ListNode);

    //n2赋值给n1->_next,使得n2的引用计数++
	n1->_next = n2;
	//n1赋值给n2->_next,使得n1的引用计数++
	n2->_prev = n1;

	//上面使得当n2销毁时,n2的引用计数--,但是析构中没有销毁new出来的ListNode,因为引用计数没有到0。然后轮到n1销毁,销毁时n1的引用计数--,同样,也没有销毁其new出来的ListNode,因为其引用计数没有到0
	return 0;
}

通过weak_ptr来解决shared_ptr的循环引用

struct ListNode
{
	int _data;

	/*bit::shared_ptr<ListNode> _next;
	bit::shared_ptr<ListNode> _prev;*/

	weak_ptr<ListNode> _next;
	weak_ptr<ListNode> _prev;

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

int main()
{
	// 循环引用 -- 内存泄露
	shared_ptr<ListNode> n1(new ListNode);
	shared_ptr<ListNode> n2(new ListNode);

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	// weak_ptr不支持管理资源,不支持RAII
	//std::weak_ptr<ListNode> wp(new ListNode);

	return 0;
}

智能指针的模拟实现

1

内存泄露

内存泄露的概念和危害

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

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现 内存泄漏会导致响应越来越慢,最终卡死。

void MemoryLeaks()
{
	// 1.内存申请了忘记释放
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = new int;
	// 2.异常安全问题
	int* p3 = new int[10];
	Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放
	delete[] p3;
}

内存泄露分类

C/C++程序中一般我们关心两种方面的内存泄漏:

  • 堆内存泄漏(Heap leak)
    堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一 块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分 内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
  • 系统资源泄漏
    指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放 掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

如何检测内存泄露

如何避免内存泄露

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps: 这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智 能指针来管理才有保证。
  2. 采用RAII思想或者智能指针来管理资源。
  3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

总结一下: 内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具

标签:int,C++,---,内存,new,shared,ptr
From: https://blog.csdn.net/2301_78913125/article/details/143864665

相关文章

  • # 20222309 2024-2025-1 《网络与系统攻防技术》实验六实验报告
    1.实验内容本实践目标是掌握metasploit的用法。指导书参考Rapid7官网的指导教程。https://docs.rapid7.com/metasploit/metasploitable-2-exploitability-guide/下载官方靶机Metasploitable2,完成下面实验内容。(1)前期渗透①主机发现(可用Aux中的arp_sweep,search一下就可以use)......
  • 一文带你快速上手 UniApp 组件与 uni-ui
    深入了解UniApp组件与组件框架uni-ui在UniApp的开发中,组件化编程是提升开发效率和应用可维护性的重要手段。通过组件化,开发者可以将应用的不同功能模块进行封装,使得代码更加简洁、可重用,并且可以提升开发体验和效率。本文将重点介绍UniApp的组件及其常用组件框架uni-ui,......
  • 24-OpenCVSharp —- Cv2.GetPerspectiveTransform()函数功能(透视变换矩阵)详解
    专栏地址:《OpenCV功能使用详解200篇》《OpenCV算子使用详解300篇》《Halcon算子使用详解300篇》内容持续更新,欢迎点击订阅Cv2.GetPerspectiveTransform()是OpenCV中用于计算透视变换矩阵的函数。透视变换(PerspectiveTransform)是计算机视觉和图像处理中常见......
  • ChatGPT-o1快速完成论文选题的9类提示词
    学境思源,一键生成论文初稿:AcademicIdeas-学境思源AI论文写作论文选题往往是学术写作的关键第一步,选题的好坏直接影响整个写作的质量和方向。ChatGPT-o1凭借强大的语言生成能力,能够帮助写作者快速构思和选择合适的论文主题。本文将介绍9类提示词,助你利用ChatGPT-o1快速完成......
  • 26-OpenCVSharp —- Cv2.WarpPerspective()函数功能(透视变换)详解
    专栏地址:《OpenCV功能使用详解200篇》《OpenCV算子使用详解300篇》《Halcon算子使用详解300篇》内容持续更新,欢迎点击订阅OpenCVSharp—Cv2.WarpPerspective()函数详解Cv2.WarpPerspective()是OpenCV中用于执行透视变换的函数。透视变换(PerspectiveTra......
  • locust-day2
    一:需要用到with,不用with显示不出数据task(2)和task(1),2比1的概率要大一倍参考脚本Mixtest二:场景:接口顺序执行多任务顺序执行seqTest新版locust继承SequentialTaskSetclass参考脚本.SeqTest三:Locust性能提升,使用大规模压测用这个这个包需要安装geventpip3in......
  • rust学习九.3-集合之哈希映射表
    这里介绍的哈希映射表(HashMap)并非是java那样的万用表,限制很大。不过,话说回来,rust应该是有类似java那样的映射表,不过不是这个哈希映射表。现在先谈论哈希映射表吧。 一、构成和定义HashMap是最不常用的,所以并没有被prelude自动引用。标准库中对HashMap的支持也相对较少......
  • 旺仔水饺-冲刺总结
    作业所属课程https://edu.cnblogs.com/campus/fzu/SE2024作业要求https://edu.cnblogs.com/campus/fzu/SE2024/homework/13305团队名称旺仔水饺102201140黎曼102201138黄俊瑶102201127罗永辉102201130郑哲浩102202144傅钰102202147赖越1722090......
  • 银河护胃队-冲刺总结
    作业所属课程https://edu.cnblogs.com/campus/fzu/SE2024/作业要求https://edu.cnblogs.com/campus/fzu/SE2024/homework/13305作业的目标总结这次alpha冲刺团队名称银河护胃队团队成员学号-名字072208130-曹星才(组长)052205144-张诗悦102201120-陈康培10220......
  • 2024-2025, 四大翻译工具加AI翻译的深度对比
    前言在过去两年中,人工智能技术的迅猛发展对翻译工具产生了深远的影响。本期特意挑选了四款翻译工具以及一个AI翻译工具,对其性能进行评测,看看在AI技术的加持下,它们的质量提升如何。以下是参赛选手的简介:谷歌翻译谷歌翻译自2006年推出以来,一直是全球最受欢迎的翻译工具,稳居翻......