【C++】异常 智能指针

C++异常 & 智能指针

  • 1.C++异常
    • 1.1.异常的抛出与捕获
    • 1.2.异常体系
    • 1.3.异常安全与规范
    • 1.4.异常优缺点
  • 2.智能指针
    • 2.1.RAII
    • 2.2.智能指针的使用及原理
      • 2.2.1.auto_ptr
      • 2.2.2.unique_ptr
      • 2.2.3.shared_ptr
      • 2.2.4.shared_ptr的循环引用问题 & weak_ptr
    • 2.3.定制删除器

1.C++异常

C++异常是一种处理错误的方式。当一个函数遇到自己无法处理的错误时就可以抛出异常,让该函数的直接或间接调用者通过捕获这个异常来处理错误。
一些异常关键字的介绍:

  • throw:程序是通过throw关键字来抛出异常的
  • catch:异常要通过catch关键字来进行捕获
  • trytry{}中的程序可能会抛出异常
// 异常捕获的基本语法如下
try
{
	// ...
}
catch(Exception_1 e)
{
	// ...
}
catch(Exception_2 e)
{
	// ...
}
...

1.1.异常的抛出与捕获

异常是以对象形式抛出的,当throwtry{}里时,异常抛出,会首先查找匹配的catch。而且该对象的类型决定了由哪个catch进行捕获处理。而被选中的catch处理程序是与对象类型匹配且离异常抛出位置最近的一个。
如果没有匹配的catch则会跳出当前函数栈,在调用该函数的栈中查找匹配的catch。如果直到main函数栈帧都没有匹配的catch,程序则会终止。
异常对象可能是一个临时对象,在被抛出时出作用域会被销毁,所以异常对象的抛出会生成一个拷贝被catch捕获。
catch(...)语句可以捕获任意类型的对象,但是无法确定捕获的错误异常类型是什么。实际中通常会最后再加上一个catch(...),防止程序直接终止。匹配合适的catch处理后,会继续沿着catch语句后面的程序执行。
抛异常时可以拋任意类型的对象,捕获时要求类型匹配。但实际中,抛出的异常对象类型并不一定要求和catch类型完全匹配。因为可以抛出派生类对象,使用基类进行捕获。

异常有时候也会被重新抛出,当前catch不能完全处理这样一个异常,在完成一些校正处理后,通过throw重新抛出给更上层的catch处理。

class Exception
{
public:
	Exception(const string& errmsg, int errid)
		: _errmsg(errmsg)
		, _errid(errid)
	{}

	virtual string what() const
	{
		return _errmsg;
	}

	virtual int get() const
	{
		return _errid;
	}
protected:
	string _errmsg;
	int _errid;
};

class HttpServerException : public Exception
{
public:
	HttpServerException(const string& errmsg, int id, const string& type)
		: Exception(errmsg, id)
		, _type(type)
	{}
	virtual string what() const
	{
		return "HttpServerException:" + _type + ":" + _errmsg;
	}
protected:
	string _type;
};

void SendMsg(const string& str)
{
	srand((unsigned int)time(0));
	if (rand() % 3 == 0)
	{
		throw HttpServerException("网络错误", 333, "get");
	}
	else if (rand() % 4 == 0)
	{
		throw HttpServerException("权限不足", 444, "post");
	}

	cout << str << "发送成功" << endl;
}

void Server()
{
	string str = "hello world";

	// 出现网络错误,重试3次
	int count = 3;
	while (count--)
	{
		try
		{
			SendMsg(str);

			// 程序走到此处,说明没有发生异常
			break;
		}
		catch (const Exception& e)
		{
			if (e.get() == 333 && count > 0)
			{
				continue;
			}
			else
			{
				throw e; // 异常重新抛出
			}
		}
	}
}

void Test1()
{
	while (true)
	{
		try
		{
			Server();
		}
		catch (const Exception& e)
		{
			// 多态
			cout << e.what() << endl;
		}
		catch (...)
		{
			cout << "Unkown Exception" << endl;
		}
		Sleep(1000);
	}
}

1.2.异常体系

C++本身是提供了一系列标准的异常的,这些异常是以父子类层次结构体系组织起来的,我们可以在程序中直接使用这些标准的异常,抛各种子类对象的异常,用一个父类对象进行接收。
在这里插入图片描述
我们也可以去继承execption类来实现自己的异常类。但实际中很多公司会定义一套自己的异常继承体系,一方面是为了规范的异常管理,另一方面C++标准异常还是不够好用。

1.3.异常安全与规范

可以在一个函数的后面接 throw(type...),列出这个函数可能抛出的异常类型有哪些。
如果函数后面接的是throw(),表示函数不抛异常。C++11新增了noexcept,用于表示不会抛异常。

构造函数中最好不要抛异常,否则可能导致对象构造不完整。
析构函数中最好不要抛异常,否则可能导致资源泄露。

1.4.异常优缺点

C语言处理错误的方式有两种:

  • assert终止程序。
  • 返回错误码errno

实际中C语言基本都是使用错误码的方式处理错误,有时候会使用终止程序的方式来处理非常严重的错误。
C++异常的优势主要是相对C语言而言的。

  • 异常对象相比错误码的方式可以展示出错误的各种信息,来帮助更好的定位程序的bug。
  • 错误码的使用有一个很大的问题就是,在函数调用层次中,深层的函数返回错误得层层返回。而异常可以直接跳到catch的地方。

异常也有缺点。

  • 异常可能导致程序的执行流乱跳,使得跟踪调试以及分析程序时更麻烦。
  • 异常很容易导致内存泄漏、死锁等安全问题。但这个基本可以通过RAII处理。

但异常总体而言利大于弊,另外面向对象的语言基本都是使用异常处理错误的,这也是大势所趋。

2.智能指针

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("Division by zero error");
	return a / b;
}

void Func()
{
	// 1、如果p1处new 抛异常会如何?
	// 2、如果p2处new 抛异常会如何?
	// 3、如果div调用处又抛异常会如何?
	int* p1 = new int;
	int* p2 = new int;
	cout << div() << endl;

	delete p1;
	delete p2;
}

void Test2()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
}

通过这个程序我们可以看到,p1抛异常时没什么问题,p1p2都没创建出来;
p2抛异常时,p1创建出来了,但没有释放,导致内存泄露了;
div()抛异常时,p1p2都创建出来了,但都没有释放,导致内存泄露了。
这里简单介绍一下内存泄漏的知识。
内存泄漏是指因为人为的疏忽或错误,而造成的程序未能释放已经不再使用的内存的情况。
内存泄漏并不是物理上内存的消失,而是由于程序失去了对该段内存的控制管理,而造成的内存的浪费。
长期运行的程序如果存在内存泄漏,会导致服务响应越来越慢,直至最终卡死。
C/C++程序一般关注两种方式的内存泄漏:堆内存泄露 和 系统资源泄露。
而内存泄漏的解决方式也分为两种:

  • 事前预防型,如智能指针的使用。
  • 事后差错型,如使用内存泄漏工具进行检测。

2.1.RAII

RAII(Resource Acquisition Is Initialization) - 资源获得即初始化。
RAII是一种通过利用对象生命周期来控制程序资源的简单技术。
在对象构造时获取资源,使控制的资源在对象生命周期内始终保持有效,最后在对象析构时释放资源。
这样做就不需要显式地释放资源,而且对象所需的资源在其生命周期内始终保持有效。

template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		: _ptr(ptr)
	{}

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

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("Division by zero error");
	return a / b;
}

void Func()
{
	int* p1 = new int;
	SmartPtr<int> sp1(p1);
	int* p2 = new int;
	SmartPtr<int> sp2(p2);

	cout << div() << endl;
}

void Test3()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
}

在这里插入图片描述
这里有了RAII的设计,就不用担心p1p2的内存泄漏问题了。

2.2.智能指针的使用及原理

像上面的SmartPtr还不能称其为智能指针,因为它还不具备指针的行为。因此还需要重载*->,让其像指针一样去使用。

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

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

2.2.1.auto_ptr

C++98库中就提供了auto_ptr智能指针。其实现原理就是将资源管理权进行转移。

// auto_ptr 简单模拟实现
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;
	}

	auto_ptr<T>& operator=(auto_ptr<T>& ap)
	{
		if (this != &ap)
		{
			// 被赋值对象需要先被清理
			delete _ptr;

			_ptr = ap._ptr;
			ap._ptr = nullptr;
		}
		
		return *this;
	}

	~auto_ptr()
	{
		delete _ptr;
	}

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

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

auto_ptr可以说是一个失败的设计, 所以能不用就不用。

2.2.2.unique_ptr

unique_ptr的实现就是简单粗暴的防拷贝。

// unique_ptr 简单模拟实现
template<class T>
class unique_ptr
{
public:
	unique_ptr(T* ptr = nullptr)
		: _ptr(ptr)
	{}

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

	~unique_ptr()
	{
		delete _ptr;
	}

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

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

2.2.3.shared_ptr

shared_ptr支持拷贝。
shared_ptr是通过引用计数的方式来支持拷贝,支持多个shared_ptr对象共享资源的。
shared_ptr中,为每份资源都维护着一份计数。当一个shared_ptr对象被析构时,引用计数就减一,直到引用计数减为0,才释放资源。

// shared_ptr 简单模拟实现
template<class T>
class shared_ptr
{
public:
	// 构造 引用计数初始化为1
	shared_ptr(T* ptr = nullptr)
		: _ptr(ptr)
		, _pCount(new int(1))
	{}
	
	// 拷贝 ++引用计数
	shared_ptr(const shared_ptr<T>& sp)
		: _ptr(sp._ptr)
		, _pCount(sp._pCount)
	{
		++(*_pCount);
	}
	
	// 赋值 ++引用计数
	shared_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			// 需要先处理好当前资源的释放
			Release();

			_ptr = sp._ptr;
			_pCount = sp._pCount;
			++(*_pCount);
		}
		return *this;
	}

	void Release()
	{
		if (--(*_pCount) == 0)
		{
			cout << "void Release()" << endl;
			delete _ptr;
			delete _pCount;
		}
	}
	
	// 直到引用计数减为0才彻底释放资源
	~shared_ptr()
	{
		Release();
	}

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

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

	int use_count()
	{
		return *_pCount;
	}

	T* get()
	{
		return _ptr;
	}
private:
	T* _ptr;
	int* _pCount; // 一个资源,配一个计数,多个智能指针对象共管
};

shared_ptr引用计数不能采用静态计数。静态成员属于整个类,属于类的所有对象。如果申请了两份相同类型的资源,无法做到区分。

2.2.4.shared_ptr的循环引用问题 & weak_ptr

class Node
{
public:
	int _val;
	std::shared_ptr<Node> _prev;
	std::shared_ptr<Node> _next;

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

void Test4()
{
	std::shared_ptr<Node> n1(new Node);
	std::shared_ptr<Node> n2(new Node);
	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;
}

在这里插入图片描述
上面的程序结束后资源有没有被释放呢?
结果告诉我们是没有的(如果释放了的话会调用析构打印~Node())。
那为什么没有释放呢?
如图,展示了程序中节点的指向情况:
在这里插入图片描述
在Test4()结束的时候,n2先析构,n1再析构,此时左边的节点和右边的节点,它们的引用计数都由2减为1;
此时,由左边节点的_next管着右边的节点,由右边节点的_prev管着左边的节点;
只要左边节点的_next被析构,右边的节点引用计数减为0,就可以被delete;
而左边节点的_next要析构,就必须让左边的节点被delete,左边节点的_next作为成员才会被析构;
而左边的节点要被delete,就必须析构右边节点的_prev,使左边的节点引用计数减为0;
而右边节点的_prev要析构,就必须让右边的节点被delete,右边节点的_prev作为成员才会被析构;
而右边的节点要被delete,就必须析构左边节点的_next,使右边的节点引用计数减为0。

由于左边节点的_next和右边节点的_prev相互牵制着,最后谁也释放不了。这就是循环引用问题。
weak_ptr可以用于解决循环引用问题,而且weak_ptr正是作为辅助型智能指针,为了配合解决shared_ptr的循环引用问题才被设计出来的。

// weak_ptr 的简单模拟实现
template<class T>
class weak_ptr
{
public:
	weak_ptr()
		: _ptr(nullptr)
	{}

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

	weak_ptr(const weak_ptr<T>& wp)
		: _ptr(wp._ptr)
	{}

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

	weak_ptr<T>& operator=(const weak_ptr<T>& wp)
	{
		_ptr = wp._ptr;
		return *this;
	}

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

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

weak_ptr不是常规的智能指针,它没有RAII,它可以访问和修改资源,但不参与资源释放管理。
weak_ptr主要是用shared_ptr构造,用来解决shared_ptr的循环引用问题。

class Node
{
public:
	int _val;
	std::weak_ptr<Node> _prev;
	std::weak_ptr<Node> _next;

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

在这里插入图片描述
智能指针总结
智能指针的设计主要考虑三点:

  1. 利用RAII思想来管理释放资源
  2. 保持有指针一样的行为
  3. 解决拷贝问题

2.3.定制删除器

对于申请的资源需要释放,但对于不同的申请方式,就需要不同的释放方式。定制删除器正是为了使申请与释放的方式进行匹配。

void Test5()
{
	// shared_ptr 支持构造时传删除器 仿函数版
	std::shared_ptr<Node> n1(new Node[5], DeleteArray<Node>());
	std::shared_ptr<Node> n2(new Node, Delete<Node>());

	std::shared_ptr<int> n3(new int[5], DeleteArray<int>());
	std::shared_ptr<int> n4(new int, Delete<int>());

	std::shared_ptr<int> n5((int*)malloc(sizeof 12), Free<int>());
	
	// shared_ptr lambda版
	std::shared_ptr<Node> n6(new Node[5], [](Node* ptr) {cout << "delete[] ptr;" << endl; delete[] ptr; });
	std::shared_ptr<Node> n7(new Node, [](Node* ptr) {cout << "delete ptr;" << endl; delete ptr; });

	std::shared_ptr<int> n8(new int[5], [](int* ptr) {cout << "delete[] ptr;" << endl; delete[] ptr; });
	std::shared_ptr<int> n9(new int, [](int* ptr) {cout << "delete ptr;" << endl; delete ptr; });

	std::shared_ptr<int> n10((int*)malloc(sizeof 12), [](int* ptr) {cout << "free(ptr);" << endl; free(ptr); });

	std::shared_ptr<FILE> n11(fopen("test.cpp", "r"), [](FILE* ptr) {cout << "fclose(ptr);" << endl; fclose(ptr); });
	
	// unique_ptr 支持类模板传迭代器类型,不支持构造时传迭代器
	std::unique_ptr<Node, DeleteArray<Node>> n12(new Node[5]);
}

在这里插入图片描述

看到这里,你能回答下列问题了吗?

  1. 为什么需要智能指针?
  2. 什么是RAII
  3. auto_ptr/unique_ptr/shared_ptr/weak_ptr之间的使用区别?
  4. 什么是循环引用?如何解决循环引用?

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

UML类图绘制指南

目录 类图简介 什么是类图 类图的作用 应用场景 类图中的元素 类和接口 六大关系 强弱关系 依赖关系&#xff1a; 关联关系&#xff1a; 聚合关系&#xff1a; 组合关系&#xff1a; 实现关系&#xff1a; 继承关系&#xff1a; 画图注意事项 总结 类图的重要…

NIO讲解

一&#xff1a;什么是NIO? 二&#xff1a;NIO三大组件 1. channel channel 有一点类似于 stream&#xff0c;它就是读写数据的双向通道&#xff0c;可以从 channel 将数据读入 buffer&#xff0c;也可以将 buffer 的数据写入 channel&#xff0c;而之前的 stream 要么是输入…

P1547 [USACO05MAR] Out of Hay S 题解

文章目录 题目描述输入格式输出格式样例样例输入样例输出 完整代码 题目描述 Bessie 计划调查 N N N&#xff08; 2 ≤ N ≤ 2 000 2 \leq N \leq 2\,000 2≤N≤2000&#xff09;个农场的干草情况&#xff0c;它从 1 1 1 号农场出发。农场之间总共有 M M M&#xff08; 1 ≤…

阻塞队列+定时器+常见的锁策略

1)阻塞队列:是一个线程安全的队列&#xff0c;是可以保证线程安全的 1.1)如果当前队列为空&#xff0c;尝试出队列&#xff0c;进入阻塞状态&#xff0c;一直阻塞到队列里面的元素不为空 1.2)如果当前队列满了&#xff0c;尝试入队列&#xff0c;也会产生阻塞&#xff0c;一直阻…

(论文阅读24/100)Visual Tracking with Fully Convolutional Networks

文献阅读笔记&#xff08;sel - CNN&#xff09; 简介 题目 Visual Tracking with Fully Convolutional Networks 作者 Lijun Wang, Wanli Ouyang, Xiaogang Wang, and Huchuan Lu 原文链接 http://202.118.75.4/lu/Paper/ICCV2015/iccv15_lijun.pdf 【DeepLearning】…

easyexcel==省市区三级联动

省市区三级联动&#xff0c;不选前面的就没法选后面的 package com.example.demoeasyexcel.jilian2; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; import org.apache.poi.ss.use…

IT 基础设施监控工具

IT 基础架构监控作为一个整体&#xff0c;是关于跟踪网络环境中所有 IT 资产的运行状况和性能&#xff0c;网络管理系统收集有关各种指标的数据&#xff0c;例如可用性、运行状况、性能和利用率&#xff0c;然后&#xff0c;IT 基础架构监控将这些数据转换为有用的统计数据&…

【Python】数据分析案例:世界杯数据可视化 | 文末送书

文章目录 前期数据准备导入数据 分析&#xff1a;世界杯中各队赢得的比赛数分析&#xff1a;先打或后打的比赛获胜次数分析&#xff1a;世界杯中的抛硬币决策分析&#xff1a;2022年T20世界杯的最高得分者分析&#xff1a;世界杯比赛最佳球员奖分析&#xff1a;最适合先击球或追…

Python+reuqests自动化接口测试

1.最近自己在摸索Pythonreuqests自动化接口测试&#xff0c;要实现某个功能&#xff0c;首先自己得有清晰的逻辑思路&#xff01;这样效率才会很快&#xff01; 思路--1.通过python读取Excel中的接口用例&#xff0c;2.通过python的函数调用&#xff0c;get/Post 进行测试&…

【QT】qt打包程序后无法正常启动

本人在自己电脑上打包Qt程序后可以正常运行&#xff0c;但换了个电脑就无法运行了&#xff0c;显示应用程序无法正常启动&#xff08;0xc000007b&#xff09;。 造成这种情况的原因是因为系统变量的原因&#xff0c;我用的win10自带的cmd。 应该采用Qt自带的cmd&#xff0c;打开…

五种常见的IO模型

目录 一. IO的概述 1.1 什么是IO 1.2 IO的效率问题 1.3 同步IO和异步IO的概念 二. 阻塞式IO 三. 非阻塞式IO 四. 信号驱动式IO 五. IO多路复用 六. 异步IO 七. 总结 一. IO的概述 1.1 什么是IO IO&#xff0c;表示输入输出&#xff0c;即&#xff1a;InPut / OutPut…

Day22力扣打卡

打卡记录 替换子串得到平衡字符串&#xff08;滑动窗口&#xff09; 链接 由于是以后统计替换的子串&#xff0c;不可以直接使用hash表统计的每个次数大于 n / 4 的字符&#xff0c;再将其次数减去平衡数来得到答案&#xff0c;根据字符串的连贯性&#xff0c;使用 滑动窗口 …

Linux服务器配置信息查询命令

Linux服务器配置信息查询命令 一、查看CPU信息 查询系统的CPU的详细信息&#xff0c;包括每个处理器的型号、频率、缓存等级以及每个核心的数量。cat /proc/cpuinfo二、查看内存信息 查询系统的内存信息&#xff0c;包括可用内存、已用内存和缓存等。cat /proc/meminfo三、查…

飞控硬件介绍及其主要传感器特性解析

飞行控制器是无人机的关键组件之一&#xff0c;它主要由主控单片机、IMU传感器、电源和输出IO等部分构成。这些硬件和传感器的特性对于无人机的二次开发至关重要&#xff0c;其性能和质量直接关系到无人机的稳定性、飞行性能和功能扩展能力。 本文将带领新手开发者深入了解飞行…

【文件IO】认识文件

文章目录 认识文件文件的结构和目录文件路径 认识文件 我们先来认识狭义上的文件(file)&#xff0c;针对硬盘这种持久化存储的I/O设备&#xff0c;当我们想要进行数据保存时&#xff0c;往往不是保存一个整体&#xff0c;而是独立成一个个单位进行保存&#xff0c;这个独立的单…

jbase代码生成器(成型篇)

上一篇说到通用码表可以解决百分之八十的基础维护功能&#xff0c;剩下的百分二十的需要级联维护的界面可以用代码生成器生成代码&#xff0c;基于生成的代码拷贝再组装界面&#xff0c;来解决这百分之二十的工作量里的百分之八十工作量。 首先实现代码生成器 Class Jbase.Ma…

创建一个事务级临时表或者会话级临时表继续测试,在什么情况下临时表里的数据会消失

目录 一、测试事务级临时表 1、创建事务级临时表 2、插入测试数据 3、查看表中的数据 4、提交事务 5、再次查看表中数据 二、测试会话级临时表 1、创建会话级临时表 2、插入测试数据 3、查看表中的数据 4、提交事务再次查看数据 5、关闭当前会话 6、再次进入数据库…

项目管理之如何出道(下)

前言 是谁用烛火照亮整个中国&#xff1f;是一伙伙行走在高压线上的电力工人&#xff1b; 是谁用水枪保护千家万户&#xff1f;是一组组穿梭于大街小巷的消防队伍&#xff1b; 是谁用身体捍卫国防边境&#xff1f;是一队队跋涉在高山深林的可爱战士。 那么作为IT业界的我们&…

GPIO实验:ARM汇编代码实现LED灯亮灭控制

GPIO实验&#xff1a;ARM汇编代码实现LED灯亮灭控制 一、 汇编工程模板Makefile分析 NAMEasm-led #指定编译的源文件名字 CROSS_COMPILE arm-linux-gnueabihf- #指定交叉编译工具链前缀CC $(CROSS_COMPILE)gcc #指定gcc名字LD $(CROSS_COMPILE)ld #指定链接器名字…

“第六十五天”

固态硬盘&#xff1a;SSD 原理&#xff1a;基于闪存技术Flash Memory &#xff0c;属于电可擦除ROM&#xff0c;即EEPROM&#xff1b; 由闪存翻译层和存储介质组成&#xff1b;闪存翻译层负责翻译逻辑块号&#xff0c;找到对应页&#xff0c;存储介质是由多个闪存芯片构成的&…