[cpp进阶]C++智能指针

news2024/11/15 13:22:24

文章目录

  • 为什么需要智能指针?
  • 智能指针的原理及使用
    • 智能指针的原理
    • 智能指针的使用
  • C++中的智能指针
    • C++智能指针的发展历程
    • std::auto_ptr
      • std::auto_ptr的使用
      • std::auto_ptr的模拟实现
    • std::unique_ptr
    • std::unique_ptr的使用
    • std::unique_ptr的模拟实现
    • std::shared_ptr
      • std::shared_ptr的使用
      • std::shared_ptr的模拟实现
      • std::shared_ptr的线程安全问题
    • std::weak_ptr
      • std::shared_ptr的循环引用
      • std::weak_ptr的使用
      • std::weak_ptr的模拟实现
    • 智能指针的定制删除器

为什么需要智能指针?

下面我们分析一段关于异常安全的代码:

#include <iostream>
using namespace std;

double division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "division by zero condition!";
	}
	return (double)a / (double)b;
}

void func()
{
	// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
	// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
	// 重新抛出去。
	int* array = new int[10];
	int len, time;
	cin >> len >> time;

	try
	{
		cout << division(len, time) << endl;
	}
	catch (...)  // 拦截异常,不是要处理异常,而是要正常释放资源
	{
		cout << "delete []" << array << endl;
		delete[] array;
		throw;  // 捕获到什么对象就重新抛出什么对象
	}

	cout << "delete []" << array << endl;
	delete[] array;
}

int main()
{
	try
	{
		func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
		// 记录日志
	}

	return 0;
}

由于division函数可能会抛异常,所以如果直接在main函数捕捉异常,就会导致申请出来的array资源没有得到释放,就会导致内存泄漏问题。

怎么解决这个问题呢?

  1. 在Func函数中拦截异常,将array资源释放掉,再将异常重新抛出。
  2. 申请资源后将指针交给智能指针管理。

智能指针的原理及使用

上述的异常问题,可以通过智能指针来解决。

智能指针的原理

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

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。

智能指针的使用

设计一个智能指针SmartPtr:

  • 在构造SmartPtr时,将用户传入的资源管理起来
  • 在析构SmartPtr时,将管理的资源释放
  • 智能指针SmartPtr能像指针一样进行解引用操作
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

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

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

	~SmartPtr()
	{
		cout << "delete: " << _ptr << endl;
		delete _ptr;
	}

private:
	T* _ptr;
};

实验代码:

double division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "division by zero condition!";
	}
	return (double)a / (double)b;
}

void func()
{
	int* ptr = new int;
	SmartPtr<int> sp(ptr); // 使用智能指针管理ptr资源
	int len, time;
	cin >> len >> time;
	
	cout << division(len, time) << endl;
}

int main()
{
	try
	{
		func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}

	return 0;
}

实验结果:

在这里插入图片描述

在func函数中,申请了ptr资源并交于SmartPtr进行管理,当出现除0错误,异常由division函数抛出,main函数捕捉时,func函数的函数栈帧销毁,SmartPtr对象也被回收,SmartPtr对象销毁之前会调用析构函数释放管理的资源,这样就避免了内存泄漏的问题

但是这样的SmartPtr智能指针还有问题:

int main()
{
	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(sp1);
	return 0;
}

运行结果:

在这里插入图片描述

SmartPtr智能指针不能进行拷贝构造,因为这样会释放两次资源,导致程序崩溃。


C++中的智能指针

C++智能指针的发展历程

  • C++98标准中产生了第一个智能指针auto_ptr
  • C++ boost库中给出了更实用的scoped_ptr、shared_ptr和weak_ptr
  • C++11标准中引入了unique_ptr、shared_ptr和weak_ptr,这些智能指针的实现原理都是参考boost库的,unique_ptr参考的是scoped_ptr

std::auto_ptr

std::auto_ptr的使用

C++ auto_ptr智能指针文档

std::auto_ptr的实现原理是管理权的转移。

实验代码:

#include <iostream>
#include <memory>

int main()
{
	std::auto_ptr<int> ap1(new int(10));
	std::auto_ptr<int> ap2(ap1);

	return 0;
}

实验结果:

在这里插入图片描述
std::auto_ptr解决智能指针拷贝构造的问题是用管理权转移解决的,但是随之而来会带来另一个问题,管理权转移让ap1指针悬空了,如果用户对std::auto_ptr不熟悉,继续使用ap1进行一系列操作,而ap1已经悬空,这势必会导致程序的崩溃。

实验代码:

#include <iostream>
#include <memory>

int main()
{
	std::auto_ptr<int> ap1(new int(10));
	std::auto_ptr<int> ap2(ap1);
	*ap1 = 20;

	return 0;
}

实验结果:

在这里插入图片描述


std::auto_ptr的模拟实现

下面简化模拟实现了一份auto_ptr来了解它的原理:

namespace cwx
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}

		auto_ptr(auto_ptr<T>& ap)
			:_ptr(ap._ptr)
		{
			// 管理权的转移
			ap._ptr = nullptr;
		}

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

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

	private:
		T* _ptr;
	};
}

std::unique_ptr

std::unique_ptr的使用

std::auto_ptr管理权转移的方式,经过时间的证明,它是一个失败的设计。C++11中引入更加实用的std::unique_ptrstd::shared_ptr

std::unique_ptr是参考了boost库中的scoped_ptr,它的原理非常简单粗暴,它解决智能指针拷贝构造的方式是直接防拷贝。

实验代码:

#include <iostream>
#include <memory>

int main()
{
	std::unique_ptr<int> up1(new int);
	std::unique_ptr<int> up2(up1);       // error

	return 0;
}

实验结果:
在这里插入图片描述


std::unique_ptr的模拟实现

下面简化模拟实现了一份unique_ptr来了解它的原理:

namespace cwx
{
	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}

		// 防拷贝
		unique_ptr(const unique_ptr<T>&) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;

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

		T* operator->()
		{
			return _ptr;
		}
		
		~unique_ptr()
		{
			if (_ptr)
			{
				delete _ptr;
			}
		}

	private:
		T* _ptr;
	};
}

std::shared_ptr

std::shared_ptr的使用

std::unique_ptr实现了简单粗暴的防拷贝,但是难以避免要需要用到拷贝。C++11引入了std::unique_ptrstd::unique_ptr使用了引用计数的技术来实现智能指针的拷贝问题。

std::unique_ptr的原理:是通过引用计数的方式来实现多个std::unique_ptr对象之间共享资源。

  • std::unique_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  • 在对象被销毁时(也就是析构函数调用),就说明当前对象不使用该资源了,对象的引用计数减一。
  • 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源。
  • 如果引用计数不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

实验代码:

#include <iostream>
#include <memory>

int main()
{
	std::shared_ptr<int> sp1(new int(10));
	std::shared_ptr<int> sp2(sp1);

	std::shared_ptr<int> sp3(new int(20));
	sp3 = sp2;

	return 0;
}

实验结果:

在这里插入图片描述

原理:

  • std::shared_ptr<int> sp1(new int(10));定义了智能指针sp1,管理new出来的int空间,sp1对象中的引用计数加一,refs = 1
  • std::shared_ptr<int> sp2(sp1);定义了智能指针sp2,拷贝sp1,sp1和sp2对象中的引用计数加一,refs = 2
  • std::shared_ptr<int> sp3(new int(20));定义了智能指针sp3,管理new出来的int空间,sp3对象中的引用计数加一,refs = 1
  • sp3 = sp2;,sp3对象赋值拷贝sp2,原来sp3指向的空间的引用计数减一,refs = 0,则释放值为20的空间,sp3现在指向值为10的空间,sp1、sp2和sp3的引用计数加一,refs = 3
    在这里插入图片描述

std::shared_ptr的模拟实现

namespace cwx
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pRefCount(new int(1))
		{}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pRefCount(sp._pRefCount)
		{
			++(*_pRefCount);
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				if (--(*_pRefCount) == 0 && _ptr)
				{
					delete _ptr;
					delete _pRefCount;
				}
				
				_ptr = sp._ptr;
				_pRefCount = sp._pRefCount;
				++(*_pRefCount);
			}

			return *this;
		}

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

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

		~shared_ptr()
		{
			if (--(*_pRefCount) == 0 && _ptr)
			{
				delete _ptr;
				delete _pRefCount;
			}
		}

	private:
		T* _ptr;
		int* _pRefCount;
	};
}

std::shared_ptr的线程安全问题

模拟实现的shared_ptr还存在线程安全的问题,由于管理同一个资源的多个对象的引用计数是共享的。

在多线程环境下可能会同时对同一个引用计数进行自增或自减操作,而自增和自减操作都不是原子操作,因此需要通过加锁来对引用计数进行保护,否
则就会导致线程安全问题。

加锁后代码:

  • 将进行引用计数自增自减的代码封装成函数,便于加锁
  • 引入mutex,对自增自减操作进行加锁
namespace cwx
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pRefCount(new int(1))
			, _pmtx(new mutex)
		{}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pRefCount(sp._pRefCount)
			, _pmtx(sp._pmtx)
		{
			AddRef();
		}

		void AddRef()
		{
			_pmtx->lock();

			++(*_pRefCount);

			_pmtx->unlock();
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				Release();

				_ptr = sp._ptr;
				_pRefCount = sp._pRefCount;
				AddRef();
			}

			return *this;
		}

		void Release()
		{
			_pmtx->lock();

			bool flag = false;
			if (--(*_pRefCount) == 0 && _ptr)
			{
				delete _ptr;
				delete _pRefCount;
				flag = true;
			}

			_pmtx->unlock();
			if (flag)
			{
				delete _pmtx;
			}
		}

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

		T* operator->()
		{
			return _ptr;
		}
		
		T* get() const
		{
			return _ptr;
		}
		
		~shared_ptr()
		{
			Release();
		}

	private:
		T* _ptr;
		int* _pRefCount;
		mutex* _pmtx;
	};
}

std::weak_ptr

std::shared_ptr的循环引用

实验代码:

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

int main()
{
	cwx::shared_ptr<ListNode> node1(new ListNode);
	cwx::shared_ptr<ListNode> node2(new ListNode);

	node1->_next = node2;
	node2->_prev = node1;

	return 0;
}

实验结果:

在这里插入图片描述

实验原理

程序创建了node1和node2两个链表结点,用shared_ptr智能指针管理,node1和node2的引用计数当前分别为1,node1的_next指向node2,node2的_prev指向node1,此时node1和node2的引用计数分别为2。

在这里插入图片描述

程序运行结束后,node1和node2调用析构函数,引用计数减一,此时引用计数为1,然而程序已经结束,node1和node2开辟的空间并没有被释放,这种现象叫做循环引用。


std::weak_ptr的使用

在上述的实验场景中,循环引用导致资源没有被释放的问题,需要使用std::weak_ptr来解决。

std::weak_ptr的原理是可以获取并访问指向的资源,但是不参与引用计数。

实验代码:

struct ListNode
{
	int _val;
	weak_ptr<ListNode> _prev;
	weak_ptr<ListNode> _next;

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

int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);

	node1->_next = node2;
	node2->_prev = node1;

	return 0;
}

实验结果:

在这里插入图片描述


std::weak_ptr的模拟实现

下面简化模拟实现了一份weak_ptr来了解它的原理:

namespace cwx
{
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();

			return *this;
		}

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

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

	private:
		T* ptr;
	};
}

智能指针的定制删除器

模拟实现的智能指针默认都用delete释放资源,但是如果是申请一个数组资源,fopen打开一个文件,delete就会不匹配或者程序直接就崩溃了。

C++文档中,智能指针的接口定义了一个模板D,就是定制删除器。定制删除器本质是一个可调用对象,比如函数指针、仿函数或者lambda表达式。

在这里插入图片描述

下面给unique_ptr实现一个简化的定制删除器:

namespace cwx
{
	template<class T, class D = default_delete<T>>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}

		// 防拷贝
		unique_ptr(const unique_ptr<T>&) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;

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

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

		~unique_ptr()
		{
			if (_ptr)
			{
				D del;
				del(_ptr);
			}
		}

	private:
		T* _ptr;
	};
}

template<class T>
struct DelArray
{
	void operator()(const T* ptr)
	{
		delete[] ptr;
	}
};

template<class T>
struct DelFile
{
	void operator()(FILE* file_name)
	{
		cout << "fclose: " << file_name << endl;
		fclose(file_name);
	}
};

int main()
{
	unique_ptr<int, DelArray<int>> up1(new int[10]);
	unique_ptr<FILE, DelFile<FILE>> up2(fopen("test.txt", "w"));

	return 0;
}

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

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

相关文章

Springboot @InitBinder处理from-data表单传参,指定参数默认新增前缀

前言 有兄弟突然找到我&#xff0c;江湖救急&#xff0c;我以为是啥问题呢&#xff1f; 一看这位小兄弟也是半路出家&#xff0c; 没有对springboot的常用注解有过研究。 不过没大碍&#xff0c;还是那句话&#xff0c; 学习的事情&#xff0c;只有先知和后知 现在你看完这篇…

纳米软件分享:光伏逆变器ATE测试系统,逆变器测试解决方案

光伏并网逆变器&#xff08;以下简称“逆变器”&#xff09;是光伏发电系统的核心部件之一&#xff0c;其主要功能是将光伏阵列的直流逆变为符合电网接入要求的交流电并入电网。并网逆变器ATE测试平台&#xff0c;主要是模拟光伏阵列特性输入的直流电源、模拟电网电源、系统控制…

Java垃圾分类查询管理系统源码+数据库,基于SpringBoot+mybatis-plus,垃圾分类查询及预约上门回收

垃圾分类查询管理系统 完整代码下载地址&#xff1a;Java垃圾分类查询管理系统源码数据库 1.介绍 垃圾分类查询管理系统&#xff0c;对不懂的垃圾进行查询进行分类并可以预约上门回收垃圾。 让用户自己分类垃圾&#xff0c; 按国家标准自己分类&#xff0c; 然后在网上提交订…

java学习之main方法

目录 一、main方法的注意事项 二、在IDEA中传入参数 一、main方法的注意事项 形式&#xff1a;public static void main(String[] args){}&#xff0c;main方法是一个静态方法&#xff0c;访问修饰符是&#xff1a;public&#xff0c;形参是String数组 args 注意事项&#xf…

go语言--函数

package mainimport "fmt"func main(){//功能: 10 20var num1 int 10var num2 int 20var sum int 0sum num1sum num2fmt.Println(sum) }为什么使用函数 提高代码的复用性&#xff0c;减少代码冗余, 代码维护性也提高了 函数的定义 为完成摸一个功能的程序指令(…

IRCNN-FPOCS 文章解读

1、论文概述 1&#xff09;待解决的问题&#xff1a;地震道数据缺失&#xff08;野外地震数据的质量往往受到地质环境或设备参数的影响&#xff0c;这些数据可能在空间上不连续&#xff0c;导致地震痕迹缺失&#xff0c;也称为下采样观测&#xff09; 2&#xff09;目的&…

从菜鸟到团队协同大神:产品经理工具技能修炼

现在&#xff0c;自雇人士和自由职业者越来越普遍。受环境影响&#xff0c;员工们正在放弃朝九晚五的工作&#xff0c;转而采用更灵活的远程工作&#xff0c;另一方面&#xff0c;随着办公数字化转型、远程办公的需求强烈、在线协协同管理工具飞项等一批知名互联网工具如雨后春…

c#入门-捕获变量

局部变量捕获 局部函数&#xff0c;匿名函数&#xff0c;可以直接在函数体内使用局部变量。 如果作为委托使用&#xff0c;传递给别人。可能在别人使用委托的时候&#xff0c;这些局部变量的作用域就已经消失了。 因此&#xff0c;编译器会对这些变量做出特殊的操作&#xff…

优维低代码:I18n 国际化

优维低代码技术专栏&#xff0c;是一个全新的、技术为主的专栏&#xff0c;由优维技术委员会成员执笔&#xff0c;基于优维7年低代码技术研发及运维成果&#xff0c;主要介绍低代码相关的技术原理及架构逻辑&#xff0c;目的是给广大运维人提供一个技术交流与学习的平台。 连载…

第8季3:使用字库字符实现区域显示

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 一、代码框架分析 在第8季2&#xff1a;OSD实验演示与代码分析中&#xff0c;我们分析了SAMPLE_RGN_CreateVideoRegion函数&#xff0c;该函数完成了左下角的logo区域显示、右下角的时间区域显示&a…

Ubuntu18.04安装教程

Ubuntu18.04安装教程# 阿里云源 deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-updates ma…

【Vue+Springboot】综合程序设计后端部分实现(含跨越访问)

文章目录1、功能展示2、前端部分&#xff1a;VueAPI3、后端部分&#xff1a;Springboot1、功能展示 大致分为用户管理&#xff0c;商品管理&#xff0c;收藏管理&#xff0c;购物车管理&#xff0c;订单管理五个模块。 2、前端部分&#xff1a;VueAPI Vue 使用 axios 库进行网…

如何在Unity中实现MVC模式?

MVC是什么&#xff1f; ​ MVC即Model View Controller&#xff0c;是模型(model)【数据层】&#xff0d;视图(view)【表现层】&#xff0d;控制器(controller)【逻辑层】的缩写。主要应用于网站开发&#xff0c;在游戏开发方面&#xff0c;因为不同游戏的需求、功能不尽相同&…

数字化转型迫在眉睫,企业应该如何面对?

火热的数字化转型&#xff0c;在国企、央企公布数字化转型规划后&#xff0c;进一步向各行各业开始扩散&#xff0c;吸引了很多对于数字化并不熟悉的企业。这些企业大多没有一个确定的目标&#xff0c;只是想要让企业完成转型改革&#xff0c;所以对于怎样做没有什么好的规划。…

Unity3d C#实现基于SocketIOUnity的与后端(node.js为例)Socket通信功能(含工程)

#前言 该功能主要是项目上的需求&#xff0c;按后端的需求就是我们通过SocketIO进行通信&#xff0c;之前游戏通信功能大多是基于原始的Socket进行封装&#xff0c;需要对包体进行设计&#xff0c;还需要粘包拆包等系列操作&#xff0c;属实有点麻烦。这次尝试了SocketIOUnity的…

Exynos_4412——RTC实验

目录 一、ADC小作业 二、RTC简介 三、Exynos_4412下的RTC控制器 四、RTC中的寄存器 五、RTC编程 六、RTC小作业 一、ADC小作业 电压在1501mv~1800mv时&#xff0c;LED2、LED3、LED4、LED5点亮 电压在1001mv~1500mv时&#xff0c;LED2、LED3、LED4点亮 电压在501mv~1000m…

什么是芯片老化测试?芯片老化测试系统NSAT-2000解决方案

随着半导体电子技术的进步&#xff0c;老化测试已成为保证产品质量的关键流程。除了半导体元件外&#xff0c;PCB、IC 和处理器部件也都需要在老化条件下进行测试。本篇文章纳米软件Namisoft小编将带大家分享一下关于芯片老化测试系统的相关知识。 一、什么是芯片老化测试&…

开发工具篇第十二讲:常用开发库 - Lombok工具库详解

开发工具篇第十二讲&#xff1a;常用开发库 - Lombok工具库详解 Lombok是一款非常实用Java工具&#xff0c;可用来帮助开发人员消除Java的冗长代码&#xff0c;尤其是对于简单的Java对象&#xff08;POJO&#xff09;。实际上我并不推荐使用Lombok&#xff08;不主动使用它&…

CANoe测试TC8

OPEN联盟发布的TC8是目前行业内关于车载以太网的标准测试规范之一。 CANoe环境需要硬件和软件: 硬件是CANoe设备&#xff0c;用来连接电脑和DUT&#xff0c;TC8测试的是以太&#xff0c;那么CANoe设备必须支持以太才行&#xff0c;目前VN5640以上都是支持的。 软件需要安装CANo…

对git rebase 和git merge的理解

一、是什么 在使用 git 进行版本管理的项目中&#xff0c;当完成一个特性的开发并将其合并到 master 分支时&#xff0c;会有两种方式&#xff1a; git mergegit rebase git rebase 与 git merge都有相同的作用&#xff0c;都是将一个分支的提交合并到另一分支上&#xff0c;…