当前位置: 首页 > news >正文

c++解决常见内存泄漏问题——智能指针的使用及其原理

目录

前言:

1. 智能指针的使用及其原理

  1. 1 智能指针的使用场景分析

1.2 RAII和智能指针的设计思路

1.3 C++标准库智能指针的使用 

1.3 1 auto_ptr

1.3 2 unique_ptr

1.3 3 shared_ptr(重)

1.3 4 weak_ptr 

1.3 5 模拟实现删除器

2.智能指针的原理 

 2.1 shared_ptr模拟实现

2.1 1 模拟实现shared_ptr的构造函数

2.1 2 模拟实现shared_ptr的拷贝构造函数 

2.1 3 析构函数 

3. shared_ptr和weak_ptr

3.1 shared_ptr循环引⽤问题

3.2 weak_ptr

4.shared_ptr的线程安全问题 

5.C++11和boost中智能指针的关系 

6.内存泄漏

6.1什么是内存泄漏,内存泄漏的危害


前言:

    在c/c++程序的开发中,一些项目需要向内存手动申请空间,而手动申请的空间在我们不用时,必须手动将其释放,先不说我们其他问题,这种特有的机制,容易让我们手动向内存申请空间而忘记释放的问题。再说其他问题,如某个局部函数内部申请了资源,在它的栈帧销毁之前抛出了异常,此局部函数立即被终止了,而我们申请的资源也没有释放,这就导致了内存泄漏。所以有人提出能否把我们申请的资源交给一个对象来管理,如果我们忘记释放资源让这个对象来释放。

1. 智能指针的使用及其原理

  1. 1 智能指针的使用场景分析

  下⾯程序中我们可以看到,new了以后,我们也delete了,但是因为抛异常导,后⾯的delete没有得到执行,所以就内存泄漏了,所以我们需要new以后捕获异常,捕获到异常后delete内存,再把异常抛出,但是因为new本⾝也可能抛异常,连续的两个new和下⾯的Divide都可能会抛异常,让我们处理起来很⿇烦。智能指针放到这样的场景⾥⾯就让问题简单多了。

double Divide(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
{
throw "Divide by zero condition!";
}
else
{
return (double)a / (double)b;
}
}
void Func()
{
// 这⾥可以看到如果发⽣除0错误抛出异常,另外下⾯的array和array2没有得到释放。
// 所以这⾥捕获异常后并不处理异常,异常还是交给外⾯处理,这⾥捕获了再重新抛出去。
// 但是如果array2new的时候抛异常呢,就还需要套⼀层捕获释放逻辑,这⾥更好解决⽅案
// 是智能指针,否则代码太戳了
int* array1 = new int[10];
int* array2 = new int[10];
// 抛异常呢
try
{
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl;
}
catch (...)
{
cout << "delete []" << array1 << endl;
cout << "delete []" << array2 << endl;
delete[] array1;
delete[] array2;
throw; // 异常重新抛出,捕获到什么抛出什么
}
// ...
cout << "delete []" << array1 << endl;
delete[] array1;
cout << "delete []" << array2 << endl;
delete[] array2;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}

下面引入一个新的概念——RAII。

1.2 RAII和智能指针的设计思路

   RAII是Resource Acquisition Is Initialization的缩写,他是⼀种管理资源的类的设计思想,本质是
⼀种利⽤对象⽣命周期来管理获取到的动态资源,避免资源泄漏,这⾥的资源可以是内存、⽂件指
针、⽹络连接、互斥锁等等
。RAII在获取资源时把资源委托给⼀个对象,接着控制对资源的访问,
资源在对象的⽣命周期内始终保持有效,最后在对象析构的时候释放资源,这样保障了资源的正常
释放,避免资源泄漏问题。

   智能指针类除了满⾜RAII的设计思路,还要⽅便资源的访问,所以智能指针类还会想迭代器类⼀
样,重载 operator*/operator->/operator[] 等运算符,⽅便访问资源。

template<class T>
class SmartPtr
{
public:
// RAII
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr()
{
cout << "delete[] " << _ptr << endl;
delete[] _ptr;
}
// 重载运算符,模拟指针的⾏为,⽅便访问资源
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T& operator[](size_t i)
{
return _ptr[i];
}
private:
T* _ptr;
};
double Divide(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
{
throw "Divide by zero condition!";
}
else
{
return (double)a / (double)b;
}
}
void Func()
{
// 这⾥使⽤RAII的智能指针类管理new出来的数组以后,程序简单多了
SmartPtr<int> sp1 = new int[10];
SmartPtr<int> sp2 = new int[10];
for (size_t i = 0; i < 10; i++)
{
sp1[i] = sp2[i] = i;
}
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}

1.3 C++标准库智能指针的使用 

C++标准库中的智能指针都在<memory>这个头⽂件下⾯,我们包含<memory>就可以是使⽤了,
智能指针有好几种,除了weak_ptr他们都符合RAII和像指针⼀样访问的⾏为,原理上⽽⾔主要是解
决智能指针拷贝时的思路不同:

1.3 1 auto_ptr

  auto_ptr是C++98时设计出来的智能指针,他的特点是拷⻉时把被拷⻉对象的资源的管理权转移给拷⻉对象,这是⼀个⾮常糟糕的设计,因为他会到被拷⻉对象悬空,访问报错的问题,C++11设计出新的智能指针后,强烈建议不要使⽤auto_ptr。其他C++11出来之前很多公司也是明令禁⽌使用这个智能指针的。

假设我们有一个date类:

auto_ptr<Date> ap1(new Date);
// 拷⻉时,管理权限转移,被拷⻉对象ap1悬空
auto_ptr<Date> ap2(ap1);

   将ap1对象拷贝给ap2对象,这样做会让ap1变为空,在我们不知道的情况下,将ap1拷贝给另一个对象,自己变为空之后,还去访问作为空的ap1,程序就崩溃了,所以我们强烈不建议使用auto_ptr。

1.3 2 unique_ptr

 unique_ptr是C++11设计出来的智能指针,他的名字翻译出来是唯⼀指针,他的特点的不⽀持拷
⻉,只⽀持移动。如果不需要拷⻉的场景就⾮常建议使⽤他。

unique_ptr<Date> up1(new Date);
// 不⽀持拷⻉
//unique_ptr<Date> up2(up1);
// ⽀持移动,但是移动后up1也悬空,所以使⽤移动要谨慎
unique_ptr<Date> up3(move(up1));

1.3 3 shared_ptr(重)

 shared_ptr是C++11设计出来的智能指针,他的名字翻译出来是共享指针,他的特点是⽀持拷⻉,
也⽀持移动。如果需要拷⻉的场景就需要使用他了。底层是用引用计数的方式实现的。

shared_ptr<Date> sp1(new Date);
// ⽀持拷⻉
shared_ptr<Date> sp2(sp1);
shared_ptr<Date> sp3(sp2);
cout << sp1.use_count() << endl;
sp1->_year++;
cout << sp1->_year << endl;
cout << sp2->_year << endl;
cout << sp3->_year << endl;
// ⽀持移动,但是移动后sp1也悬空,所以使⽤移动要谨慎
shared_ptr<Date> sp4(move(sp1));

1.3 4 weak_ptr 

weak_ptr是C++11设计出来的智能指针,他的名字翻译出来是弱指针,他完全不同于上⾯的智能指
针,他不⽀持RAII,也就意味着不能⽤它直接管理资源,weak_ptr的产⽣本质是要解决shared_ptr
的⼀个循环引⽤导致内存泄漏的问题。具体细节下⾯我们再细讲。

下面内容请看完shared_ptr再看

    智能指针析构时默认是进⾏delete释放资源,这也就意味着如果不是new出来的资源,交给智能指针管理,析构时就会崩溃。

    智能指针⽀持在构造时给⼀个删除器,所谓删除器本质就是⼀个可调⽤对象,这个可调⽤对象中实现你想要的释放资源的⽅式,当构造智能指针时,给了定制的删除器,在智能指针析构时就会调⽤删除器去释放资源。

    因为new[]经常使⽤,所以为了简洁⼀点,unique_ptr和shared_ptr都特化了⼀份[]的版本,使⽤时 unique_ptr<Date[]> up1(newDate[5]);shared_ptr<Date[]> sp1(new Date[5]); 就可以管理new []的资源。

如果我们要使用shared_ptr来new[]类型的资源,一般是不被允许的,我们来看下面这行代码:

shared_ptr<Date> sp1(new Date[10]);

运行一下:

程序直接崩溃了,但是这样的代码我们可能要经常使用,所以系统直接特化了一份带[]的版本,只要在类型的后面加上[]就可以正常析构了:

可是,在实际使用场景中,我们不不能只会遇到[]的情况,如果我们遇到的是文件类型呢?看下面的代码:

shared_ptr<FILE> sp2(fopen("test.cpp", "r"));

这样的代码又该怎么析构呢,难道系统在底层使用fclose实现了它的析构吗,答案是否定的,遇到这种不常见的场景,我们只能自己使用定制删除器来解决。定制删除器支持仿函数、函数指针、lambda等,目的是释放那些我们无法正常释放的资源,运行上面这行代码:

 终端没有任何显示,说明这个FILE类型的资源没有正常释放,我们先来看文档中的shared_ptr :

它有一个del参数版本的,这个del就是我们传入的删除器,以上面这个文件类型的资源举例,写一个仿函数:

class Fclose
{
public:void operator()(FILE* ptr){cout << "fclose:" << ptr;fclose(ptr);}
};

传给它看看:

shared_ptr<FILE> sp2(fopen("test.cpp", "r"),Fclose());

可以看到资源正常释放了 ,原理如下:

删除器还支持使用lambda:

使用这种方式也可以正常释放资源,再来看函数指针版本:

 三种方法没有好坏,只有适合各自的场景,要使用哪一种需结合场景使用。

而unique_ptr与shared_ptr不同,shared_ptr传删除器是在构造时传,而unique_ptr则在类的声明时传:

这就导致两者的定制删除器使用方式不一样,unique_ptr传仿函数时更加方便:

unique_ptr<FILE,Fclose> sp2(fopen("test.cpp", "r"));

因为在<>中,只能传类型而不能传对象,lambda本质是一个对象,我们拿不到lambda的类型,所以仿函数是unique_ptr使用删除器最好的选择,在我们使用unique_ptr时,推荐使用仿函数来做为删除器的参数。而shared_ptr,三者都可以,相对建议传lambda,因为lambda的实现就在该行中,在这一行中,它的功能一览无余。

1.3 5 模拟实现删除器

实现删除器,我们只需要让它能支持下面这行代码就行了:

test::shared_ptr<Date> sp1(new Date[10], [](Date* ptr) {delete[] ptr; });

从它的构造函数入手,它要传入两个参数,我们的构造函数也要设置一个支持带删除器版本的才能实现删除器的功能,而如果我们这样写:

template<class D>
shared_ptr(T* ptr,D del):_ptr(ptr), _pcount(new int(1))
{}

我们可以多定义一个变量,但是这个D类型在类的内部无法使用,如果将它加到类模板上,那它的使用方法不就与unique_ptr一样要传类型了吗,所以我们在类的内部定义一个function(包装器)类型:

function<void(T*)> _del;

这样就完美解决了既不用传类型来使用删除器,又可以传两个参数的问题,我们的析构函数也要改为使用删除器来释放资源:

~shared_ptr()
{if (--(*_pcount) == 0){_del(_ptr);delete _pcount;}
}

构造函数为:

template<class D>
shared_ptr(T* ptr,D del):_ptr(ptr), _pcount(new int(1)),_del(del)
{}

test::shared_ptr<Date> sp1(new Date);
test::shared_ptr<Date> sp2(new Date[10], [](Date* ptr) {delete[] ptr; });

我们运行运行一下: 

可以看到下面带[]的对象正常释放了,而运行到不带[]的对象这里时却崩溃了 ,这是什么原因呢,因为我们的析构函数被改变了,从默认使用delete来释放资源变成了使用删除器释放,而上面那行我们没有传删除器,所以我们要给删除器加一个缺省参数:

function<void(T*)> _del = [](T* ptr) {delete ptr; };

这样就可以正常释放了:

 (1)template <class T, class... Args> shared_ptr<T> make_shared(Args&&... args); 

(2)shared_ptr 除了⽀持⽤指向资源的指针构造,还⽀持 make_shared ⽤初始化资源对象的值
直接构造。

shared_ptr<Date> sp1(new Date(2024, 11, 19));
shared_ptr<Date> sp2 = make_shared<Date>(2024, 11, 19);

这两句代码的作用是样的,但是make_shared有一个好处。我们知道,shared_ptr底层是用引用计数来维护的,而引用计数要向内存中开辟空间,一个int*类型是四个字节,shared_ptr本身就要开辟空间,引用计数与shared_ptr申请的资源是分开的,如果我们多次使用shared_ptr,就会在内存中开辟很多个小的空间,这样会引起空间碎片过多的问题,而make_shared则是将引用计数和shared_ptr本身开辟的空间放在了一起,比起shared_ptr,使用make_shared可以减少不必要的空间碎片,就可以提高效率。

(3)shared_ptr 和 unique_ptr 都⽀持了operator bool的类型转换,如果智能指针对象是⼀个
空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断
是否为空。

(4)shared_ptr 和 unique_ptr 都得构造函数都使⽤explicit 修饰,防⽌普通指针隐式类型转换
成智能指针对象。

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);
// 拷⻉时,管理权限转移,被拷⻉对象ap1悬空
auto_ptr<Date> ap2(ap1);
// 空指针访问,ap1对象已经悬空
//ap1->_year++;
unique_ptr<Date> up1(new Date);
// 不⽀持拷⻉
//unique_ptr<Date> up2(up1);
// ⽀持移动,但是移动后up1也悬空,所以使⽤移动要谨慎
unique_ptr<Date> up3(move(up1));
shared_ptr<Date> sp1(new Date);
// ⽀持拷⻉
shared_ptr<Date> sp2(sp1);
shared_ptr<Date> sp3(sp2);
cout << sp1.use_count() << endl;
sp1->_year++;
cout << sp1->_year << endl;
cout << sp2->_year << endl;
cout << sp3->_year << endl;
// ⽀持移动,但是移动后sp1也悬空,所以使⽤移动要谨慎
shared_ptr<Date> sp4(move(sp1));
return 0;
}
template<class T>
void DeleteArrayFunc(T* ptr)
{
delete[] ptr;
}
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> up1(new Date[10]);
// shared_ptr<Date> sp1(new Date[10]);
// 解决⽅案1
// 因为new[]经常使⽤,所以unique_ptr和shared_ptr
// 实现了⼀个特化版本,这个特化版本析构时⽤的delete[]
unique_ptr<Date[]> up1(new Date[5]);
shared_ptr<Date[]> sp1(new Date[5]);
// 解决⽅案2
// 仿函数对象做删除器
//unique_ptr<Date, DeleteArray<Date>> up2(new Date[5], DeleteArray<Date>
());
// unique_ptr和shared_ptr⽀持删除器的⽅式有所不同
// unique_ptr是在类模板参数⽀持的,shared_ptr是构造函数参数⽀持的
// 这⾥没有使⽤相同的⽅式还是挺坑的
// 使⽤仿函数unique_ptr可以不在构造函数传递,因为仿函数类型构造的对象直接就可以调⽤
// 但是下⾯的函数指针和lambda的类型不可以
unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);
shared_ptr<Date> sp2(new Date[5], DeleteArray<Date>());
/ 函数指针做删除器
unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);
shared_ptr<Date> sp3(new Date[5], DeleteArrayFunc<Date>);
// lambda表达式做删除器
auto delArrOBJ = [](Date* ptr) {delete[] ptr; };
unique_ptr<Date, decltype(delArrOBJ)> up4(new Date[5], delArrOBJ);
shared_ptr<Date> sp4(new Date[5], delArrOBJ);
// 实现其他资源管理的删除器
shared_ptr<FILE> sp5(fopen("Test.cpp", "r"), Fclose());
shared_ptr<FILE> sp6(fopen("Test.cpp", "r"), [](FILE* ptr) {
cout << "fclose:" << ptr << endl;
fclose(ptr);
});
return 0;
}
int main()
{
shared_ptr<Date> sp1(new Date(2024, 9, 11));
shared_ptr<Date> sp2 = make_shared<Date>(2024, 9, 11);
auto sp3 = make_shared<Date>(2024, 9, 11);
shared_ptr<Date> sp4;
// if (sp1.operator bool())
if (sp1)
cout << "sp1 is not nullptr" << endl;
if (!sp4)
cout << "sp1 is nullptr" << endl;
// 报错
shared_ptr<Date> sp5 = new Date(2024, 9, 11);
unique_ptr<Date> sp6 = new Date(2024, 9, 11);
return 0;
}

2.智能指针的原理 

(1)下⾯我们模拟实现了auto_ptr和unique_ptr的核⼼功能,这两个智能指针的实现⽐较简单,⼤家了解⼀下原理即可。auto_ptr的思路是拷⻉时转移资源管理权给被拷⻉对象,这种思路是不被认可的,也不建议使⽤。unique_ptr的思路是不⽀持拷贝。

namespace test
{
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{}
auto_ptr(auto_ptr<T>& sp)
:_ptr(sp._ptr)
{
// 管理权转移
sp._ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
// 检测是否为⾃⼰给⾃⼰赋值
if (this != &ap)
{
// 释放当前对象中资源
if (_ptr)
delete _ptr;
// 转移ap中资源到当前对象中
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
~auto_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
// 像指针⼀样使⽤
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
template<class T>
class unique_ptr
{
public:
explicit unique_ptr(T* ptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
// 像指针⼀样使⽤
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
unique_ptr(unique_ptr<T>&& sp)
:_ptr(sp._ptr)
{
sp._ptr = nullptr;
}
unique_ptr<T>& operator=(unique_ptr<T>&& sp)
{
delete _ptr;
_ptr = sp._ptr;
sp._ptr = nullptr;
}
private:
T* _ptr;
};
}int main()
{
test::auto_ptr<Date> ap1(new Date);
// 拷⻉时,管理权限转移,被拷⻉对象ap1悬空
test::auto_ptr<Date> ap2(ap1);
// 空指针访问,ap1对象已经悬空
//ap1->_year++;
test::unique_ptr<Date> up1(new Date);
// 不⽀持拷⻉
//unique_ptr<Date> up2(up1);
// ⽀持移动,但是移动后up1也悬空,所以使⽤移动要谨慎
test::unique_ptr<Date> up3(move(up1));
return 0;
}

(2) ⼤家重点要看看shared_ptr是如何设计的,尤其是引⽤计数的设计,主要这⾥⼀份资源就需要⼀个引⽤计数,我们知道一个静态成员属于该类的所有的对象,这就意味着,该类每实例化一个对象就会让这个静态成员加1,而我们的目的是让一份资源由多个对象共享,只有共享某份资源的对象才能让对应的引用计数加1,所以引用计数采用静态成员的方式是⽆法实现的,要使⽤堆上动态开辟的⽅式,构造智能指针对象时来⼀份资源,就要new⼀个引⽤计数出来。多个shared_ptr指向资源时就++引⽤计数,shared_ptr对象析构时就--引⽤计数,引⽤计数减到0时代表当前析构的shared_ptr是最后⼀个管理资源的对象,则析构资源。

 2.1 shared_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;}
};

我们在日期类的析构函数中加一行打印,方便我们观察我们申请的资源是否正常释放。

先使用库中的shared_ptr测试一下:

shared_ptr<Date> sp1(new Date);
// ⽀持拷⻉
shared_ptr<Date> sp2(sp1);
shared_ptr<Date> sp3(sp2);test::shared_ptr<Date>  sp4(new Date);

创建了一个对象sp1,再用两个对象来共享sp1中的资源,接着由创建了sp4对象,它也申请了自己的资源,所以,在这里虽然由、有四个对象,但是只申请了两份资源,所以只有两次析构,我们运行程序测试运行一下:

我们的推测没有错,程序结束时只析构了两次,下面我们就来自己实现一个shared_ptr。

2.1 1 模拟实现shared_ptr的构造函数

使用一个模板类型的指针来接收我们要共享的资源,然后使用int类型的指针作为引用计数:

T* _ptr;
int* _pcount;

   使用T类型的参数来接收我们要共享的类型,如果没有传参,就默认为空 ,设置一个为nullptr的缺省参数,引用计数默认为1:

shared_ptr(T* ptr=nullptr):_ptr(ptr),_pcount(new int(1))
{}

2.1 2 模拟实现shared_ptr的拷贝构造函数 

   拷贝构造函数接收一个shared_ptr类型的对象,将我们接收到的对象的T类型的指针通过浅拷贝的方法拷贝给我们新创建的对象,再把相应的引用计数复制给新对象,让引用计数加1;

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

我们来测试一下:

可以看到构造函数和拷贝构造函数的模拟实现成功了, 这意味着shared_ptr完成了吗?

我们来看看给另一对象赋值的情况能不能正常使用,将sp4给sp1:

我们发现此程序只析构了一次,但是我们申请了两份资源, 说明我们目前的程序还有问题,还需要实现一个拷贝赋值函数。在赋值之前,我们要先将自己的资源析构掉,如果有其他对象共享了它的资源,则只需让它的引用计数减一,再执行赋值操作,让引用计数加一,值得注意的是,我们要小心自己给自己赋值的情况,如果一个对象自己给自己赋值,自己是最后一个拥有资源的对像,在判断引用计数减一之后为0,析构完了,再给自己赋值,程序就会崩溃,所以这种情况也要处理好:

shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{//防止自己给自己赋值的错误出现if (_ptr != sp._ptr){// 接收资源之前先释放掉原来的资源// 如果不知一个智能指针管理这块资源//就让引用计数减一if (--(*_pcount) == 0){delete _ptr;delete _pcount;}//一切正常,就正常赋值_pcount = sp._pcount;_ptr = sp._ptr;++(*_pcount);}return *this;}

 我们再测试一下:

这次就没有问题了。

2.1 3 析构函数 

   析构函数的逻辑与拷贝赋值的上半部分的逻辑一样,先减一再判断引用计数是否为0,如果是就使用delete释放申请的资源。

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

3. shared_ptr和weak_ptr

3.1 shared_ptr循环引⽤问题

•shared_ptr⼤多数情况下管理资源⾮常合适,⽀持RAII,也⽀持拷⻉。但是在循环引⽤的场景下会
导致资源没得到释放内存泄漏,所以我们要认识循环引⽤的场景和资源没释放的原因,并且学会使
⽤weak_ptr解决这种问题。
• 如下图所述场景,n1和n2析构后,管理两个节点的引⽤计数减到1

1. 右边的节点什么时候释放呢,左边节点中的_next管着呢,_next析构后,右边的节点就释放了。
2. _next什么时候析构呢,_next是左边节点的的成员,左边节点释放,_next就析构了。

3. 左边节点什么时候释放呢,左边节点由右边节点中的_prev管着呢,_prev析构后,左边的节点就释放了。
4. _prev什么时候析构呢,_prev是右边节点的成员,右边节点释放,_prev就析构了。 

• ⾄此逻辑上成功形成回旋镖似的循环引⽤,谁都不会释放就形成了循环引⽤,导致内存泄漏
• 把ListNode结构体中的_next和_prev改成weak_ptr,weak_ptr绑定到shared_ptr时不会增加它的
引⽤计数,_next和_prev不参与资源释放管理逻辑,就成功打破了循环引⽤,解决了这⾥的问题

我们来演示一下这里的问题:有一个双链表,它的两个结点分别是两个ListNode对象,被两个shared_ptr管理着,如果要链接,只能使用shared_ptr来链接:

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

而不是ListNode结点:

ListNode* _next;
ListNode* _prev;

如果使用这种方式来链接,就会出现shared_ptr类型链接LIstNode类型的情况,造成类型不匹配的问题,那么我们只能都只使用shared_ptr类型来链接:

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

定义两个对象:

shared_ptr<ListNode> n1(new ListNode);
shared_ptr<ListNode> n2(new ListNode);

不将它们链接:

 现在可以正常析构,将它们链接:

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

再运行一下:

3.2 weak_ptr

• weak_ptr不⽀持RAII,也不⽀持访问资源,所以我们看⽂档发现weak_ptr构造时不⽀持绑定到资
源,只⽀持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引⽤计数,那么就可以
解决上述的循环引⽤问题。

• weak_ptr也没有重载operator*和operator->等,因为他不参与资源管理,那么如果他绑定的
shared_ptr已经释放了资源,那么他去访问资源就是很危险的。weak_ptr⽀持expired检查指向的
资源是否过期,use_count也可获取shared_ptr的引⽤计数,weak_ptr想访问资源时,可以调⽤
lock返回⼀个管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是⼀个空对象,如
果资源没有释放,则通过返回的shared_ptr访问资源是安全的。

 没有正常析构,这就是循环引用,是什么原因导致的我们在上面已经讲清楚了,遇到上面的极端场景我们可以使用weak_ptr来管理两个结点:

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

 将链表的结点交给weak_ptr来管理,再运行一下:

现在这两个结点正常释放了,引用计数也始终是1 。

4.shared_ptr的线程安全问题 

• shared_ptr的引⽤计数对象在堆上,如果多个shared_ptr对象在多个线程中,进⾏shared_ptr的拷
⻉析构时会访问修改引⽤计数,就会存在线程安全问题,所以shared_ptr引⽤计数是需要加锁或者
原⼦操作保证线程安全的。
• shared_ptr指向的对象也是有线程安全的问题的,但是这个对象的线程安全问题不归shared_ptr
管,它也管不了,应该有外层使⽤shared_ptr的⼈进⾏线程安全的控制。
• 下⾯的程序会崩溃或者A资源没释放,bit::shared_ptr引⽤计数从int*改成atomic<int>*就可以保证
引⽤计数的线程安全问题,或者使⽤互斥锁加锁也可以。

struct AA
{
int _a1 = 0;
int _a2 = 0;
~AA()
{
cout << "~AA()" << endl;
}
};
int main()
{
bit::shared_ptr<AA> p(new AA);
const size_t n = 100000;
mutex mtx;
auto func = [&]()
{
for (size_t i = 0; i < n; ++i)
{
// 这⾥智能指针拷⻉会++计数
bit::shared_ptr<AA> copy(p);
{
unique_lock<mutex> lk(mtx);
copy->_a1++;
copy->_a2++;
}
}
};
thread t1(func);
thread t2(func);
t1.join();
t2.join();
cout << p->_a1 << endl;
cout << p->_a2 << endl;
cout << p.use_count() << endl;
return 0;
}

5.C++11和boost中智能指针的关系 

• Boost库是为C++语⾔标准库提供扩展的⼀些C++程序库的总称,Boost社区建⽴的初衷之⼀就是为C++的标准化⼯作提供可供参考的实现,Boost社区的发起⼈Dawes本⼈就是C++标准委员会的成员之⼀。在Boost库的开发中,Boost社区也在这个⽅向上取得了丰硕的成果,C++11及之后的新语法和库有很多都是从Boost中来的。
• C++ 98 中产⽣了第⼀个智能指针auto_ptr。
• C++ boost给出了更实⽤的scoped_ptr/scoped_array和shared_ptr/shared_array和weak_ptr等.
• C++ TR1,引⼊了shared_ptr等,不过注意的是TR1并不是标准版。
• C++ 11,引⼊了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的
scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

上述类容了解一下即可。

6.内存泄漏

6.1什么是内存泄漏,内存泄漏的危害

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使⽤的内存,⼀般是忘记释放或者发⽣异常释放程序未能执⾏导致的。内存泄漏并不是指内存在物理上的消失,⽽是应⽤程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因⽽造成了内存的浪费。


内存泄漏的危害:普通程序运⾏⼀会就结束了出现内存泄漏问题也不⼤,进程正常结束,⻚表的映射关系解除,物理内存也可以释放。⻓期运⾏的程序出现内存泄漏,影响很⼤,如操作系统、后台服务、⻓时间运⾏的客⼾端等等,不断出现内存泄漏会导致可⽤内存不断变少,各种功能响应越来越慢,最终卡死。

int main()
{
// 申请⼀个1G未释放,这个程序多次运⾏也没啥危害
// 因为程序⻢上就结束,进程结束各种资源也就回收了
char* ptr = new char[1024 * 1024 * 1024];
cout << (void*)ptr << endl;
return 0;
}

6.2如何避免内存泄漏

• ⼯程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下⼀条智能指针来管理才有保证。
• 尽量使⽤智能指针来管理资源,如果⾃⼰场景⽐较特殊,采⽤RAII思想⾃⼰造个轮⼦管理。
• 定期使⽤内存泄漏工具检测,尤其是每次项⽬快上线前,不过有些⼯具不够靠谱,或者是收费。
• 总结⼀下:内存泄漏⾮常常⻅,解决⽅案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。 

本章完。

相关文章:

c++解决常见内存泄漏问题——智能指针的使用及其原理

目录 前言&#xff1a; 1. 智能指针的使用及其原理 1. 1 智能指针的使用场景分析 1.2 RAII和智能指针的设计思路 1.3 C标准库智能指针的使用 1.3 1 auto_ptr 1.3 2 unique_ptr 1.3 3 shared_ptr(重&#xff09; 1.3 4 weak_ptr 1.3 5 模拟实现删除器 2.智能指针的原…...

算法竞赛之离散化技巧 python

目录 离散化实战演练总结 离散化 不改变数据相对大小的情况下&#xff0c;对数据进行相应的下标映射&#xff0c;即离散化。 例如&#xff1a;【100,200,300,400,500】&#xff0c;离散化后为【1,2,3,4,5】 什么时候可以离散化&#xff1a;当数据只与它们之间的相对大小有关&a…...

1.CSS的三大特性

css有三个非常重要的三个特性&#xff1a;层叠性、继承性、优先级 1.1 层叠性 想通选择器给设置想听的样式&#xff0c;此时一个样式就会覆盖&#xff08;层叠&#xff09;另一个冲突的样式。层叠性主要是解决样式冲突的问题。 <!DOCTYPE html> <html lang"en&…...

由于请求的竞态问题,前端仔喜提了一个bug

在平常的开发过程中&#xff0c;你可能会遇到这样一个bug。 测试&#xff1a;我在测一个输入框搜索的功能时&#xff0c;告诉你通过输入框输入的内容&#xff0c;和最终通过输入内容搜索出来的结果对不上。 前端&#xff1a;我是通过调用后端接口拿到的数据&#xff0c;这明显…...

HTML `<head>` 元素详解

在 HTML 文档中&#xff0c;<head> 元素是一个非常重要的部分&#xff0c;它包含了文档的元数据&#xff08;metadata&#xff09;和其他与文档相关的信息。虽然 <head> 中的内容不会直接显示在网页上&#xff0c;但它对网页的行为、样式和搜索引擎优化&#xff08…...

基于RAG构建Text2SQL的实战教程

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于大模型算法的研究与应用。曾担任百度千帆大模型比赛、BPAA算法大赛评委,编写微软OpenAI考试认证指导手册。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。授权多项发明专利。对机器学…...

GPT-4对话模型在客服中的应用与前景:开启智能客服新时代

GPT-4对话模型在客服中的应用与前景:开启智能客服新时代 随着人工智能技术的迅猛发展,基于深度学习的对话模型在各个领域中得到了广泛应用。其中,GPT-4对话模型在客服系统中的应用尤为引人注目。本文将探讨GPT-4在客服中的应用与未来发展前景,并结合具体代码示例进行说明。…...

我想通过python语言,学习数据结构和算法该如何入手?

学习数据结构和算法是编程中的重要基础&#xff0c;Python 是一个非常适合入门的语言。以下是学习数据结构和算法的步骤和建议&#xff1a; 1. 掌握 Python 基础 确保你对 Python 的基本语法、数据类型、控制结构&#xff08;如循环、条件语句&#xff09;、函数等有扎实的理…...

Java多线程的面试面试题及答案解析

什么是进程&#xff1f;什么是线程&#xff1f;有什么区别&#xff1f; 进程是系统资源分配的基本单位&#xff0c;拥有独立的地址空间。线程是 CPU 调度和分派的基本单位&#xff0c;是比进程更小的独立执行的单位&#xff0c;共享所在进程的内存空间等资源。一个进程可以包含…...

python flask中使用or查询和and查询,还有同时使用or、and的情况

在 Flask 中处理数据库查询时&#xff0c;通常会结合使用 ORM 工具&#xff0c;例如 SQLAlchemy。以下是 or 查询、and 查询以及两者同时使用的示例。 文章目录 基础准备1. 使用 or_ 查询2. 使用 and_ 查询3. 同时使用 or_ 和 and_4. 更加复杂的嵌套查询 基础准备 假设有一个…...

C# 解析视频流播放全解析

在多媒体技术日益发达的今天&#xff0c;视频流播放已经成为众多应用中不可或缺的功能。对于开发者而言&#xff0c;掌握如何使用编程语言来解析和播放视频流是一项重要的技能。本文将深入探讨如何使用 C# 来实现视频流的解析与播放。 一、视频流播放原理简介 视频流是将视频…...

关于为什么java中nextInt()和nextLine()不能混用 | nextInt()和nextInt()之类的可以一起用

键盘录入的区别&#xff1a; 第一套体系&#xff1a;遇到空格、制表符、回车都结束&#xff0c;并且都不接收 nextInt()、nextDouble()、next() 遇到空格、制表符、回车就结束&#xff0c;只接收其之前的数据&#xff0c;空格以及空格之后的数据都在缓冲区内&#xff0c;如果…...

计算机图形学:实验一 OpenGL基本绘制

1.OpenGL的环境配置&#xff1a; 集成开发环境Visual Studio Community 2019的安装&#xff1a; 在Windows一栏选择使用C的桌面开发&#xff1b;再转到“单个组件”界面&#xff0c;在“编译器、生成工具和运行时”一栏选择用于“Windows的C CMake工具”&#xff1b;然后转到…...

Node.js 到底是什么

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境&#xff0c;它允许开发者使用 JavaScript 编写服务器端代码。 一、主要特点 1. 事件驱动和非阻塞 I/O 模型 Node.js 采用事件驱动架构&#xff0c;通过回调函数处理 I/O 操作&#xff0c;这使得它在处理大量并发请…...

2022年全国职业院校技能大赛网络系统管理赛项模块A:网络构建(样题5)

目录 任务描述 任务清单 (一)基础配置 (二)有线网络配置 (三)无线网络配置 (四)出口网络配置 附录1:拓扑图 附录2:地址规划表 任务描述 随着业务的发展,现在要对海琼银行进行全网改造,为其它区域的网络提供高效的保障服务。同时,海琼银行还针对各个分支行、网点的…...

智慧脚下生根,智能井盖监测终端引领城市安全新革命

在繁忙的都市生活中&#xff0c;我们往往只关注地面的繁华与喧嚣&#xff0c;却忽略了隐藏在地面之下的基础设施——井盖。这些看似不起眼的井盖&#xff0c;实则承担着排水、通讯、电力等重要功能&#xff0c;是城市安全运转的重要一环。然而&#xff0c;传统的井盖管理面临着…...

ES6 简单练习笔记--变量申明

一、ES5 变量定义 1.在全局作用域中 this 其实就是window对象 <script>console.log(window this) </script>输出结果: true 2.在全局作用域中用var定义一个变量其实就相当于在window上定义了一个属性 例如: var name "孙悟空" 其实就相当于执行了 win…...

MsfVenom木马制作及使用

msfvenom基本用法 1、功能介绍 msfvenom的功能&#xff1a;常用于生成木马&#xff0c;在目标机器执行&#xff0c;在本地机器kali中上线&#xff0c;与反弹shell类似。MsfVenom可以生成两种类型的攻击载荷&#xff1a; &#xff08;1&#xff09;Payload&#xff1a;Payloa…...

ChromeOS 132 版本更新

ChromeOS 132 版本更新 1. 企业定制化 Chrome Web Store 管理员现在可以使用新设置定制 Chrome Web Store 以适应他们管理的用户&#xff0c;包括以下功能&#xff1a; 添加公司标志添加首页横幅和自定义公告策划扩展集合实施基于类别的控制 这些设置可以通过管理员控制台进…...

MySQL(表空间)

​开始前先打开此图配合食用 MySQL表空间| ProcessOn免费在线作图,在线流程图,在线思维导图 InnoDB 空间文件中的页面管理 后面也会持续更新&#xff0c;学到新东西会在其中补充。 建议按顺序食用&#xff0c;欢迎批评或者交流&#xff01; 缺什么东西欢迎评论&#xff01;我都…...

智能化加速标准和协议的更新并推动验证IP(VIP)在芯片设计中的更广泛应用

作者&#xff1a;Karthik Gopal, SmartDV Technologies亚洲区总经理 智权半导体科技&#xff08;厦门&#xff09;有限公司总经理 随着AI技术向边缘和端侧设备广泛渗透&#xff0c;芯片设计师不仅需要考虑在其设计中引入加速器&#xff0c;也在考虑采用速度更快和带宽更高的总…...

Chrome远程桌面无法连接怎么解决?

Chrome远程桌面连接已停止工作 Chrome远程桌面是一款极为便捷的浏览器插件&#xff0c;能够帮助用户将自己的计算机连接到其他设备&#xff0c;无论是手机、平板电脑还是其他电脑。然而&#xff0c;在实际使用中&#xff0c;许多用户可能会面临各种各样的问题&#xff0c;比如…...

springcloud alibaba 五大组件

Spring Cloud Alibaba是Spring Cloud的一个子项目&#xff0c;致力于为构建分布式应用提供一站式解决方案。它基于阿里巴巴的底层Java开源框架&#xff0c;主要包含以下五大核心组件&#xff1a; 1. Nacos&#xff08;服务注册与配置中心&#xff09; 功能&#xff1a;Nacos提…...

es 3期 第25节-运用Rollup减少数据存储

#### 1.Elasticsearch是数据库&#xff0c;不是普通的Java应用程序&#xff0c;传统数据库需要的硬件资源同样需要&#xff0c;提升性能最有效的就是升级硬件。 #### 2.Elasticsearch是文档型数据库&#xff0c;不是关系型数据库&#xff0c;不具备严格的ACID事务特性&#xff…...

理解深度学习pytorch框架中的线性层

文章目录 1. 数学角度&#xff1a; y W x b \displaystyle y W\,x b yWxb示例 2. 编程实现角度&#xff1a; y x W T b \displaystyle y x\,W^T b yxWTb3. 常见错误与易混点解析4. 小结参考链接 在神经网络或机器学习的线性层&#xff08;Linear Layer / Fully Connect…...

“上门按摩” 小程序开发项目:基于 SOP 的全流程管理

在竞争激烈的生活服务市场,“上门按摩” 服务需求日益增长。为满足这一需求,我们启动了 O2O 模式的 “上门按摩” 小程序开发项目,该项目涵盖客户端、系统管理端、技师端。以下将通过各类 SOP 对项目进行全面管理,确保项目顺利推进。 一、项目启动 SOP:5W2H 分析法 What(…...

【xcode 16.2】升级xcode后mac端flutter版的sentry报错

sentry_flutter 7.11.0 报错 3 errors in SentryCrashMonitor_CPPException with the errors No type named terminate_handler in namespace std (line 60) and No member named set_terminate in namespace std 替换sentry_flutter版本为&#xff1a; 8.3.0 从而保证oc的…...

Unity自学之旅05

Unity自学之旅05 Unity学习之旅⑤&#x1f4dd; AI基础与敌人行为&#x1f94a; AI导航理论知识&#xff08;基础&#xff09;开始实践 &#x1f383; 敌人游戏机制追踪玩家攻击玩家子弹碰撞完善游戏失败条件 &#x1f917; 总结归纳 Unity学习之旅⑤ &#x1f4dd; AI基础与敌…...

LINUX下设置分离状态(Detached State)和未设置分离状态的主要区别在于线程资源的管理方式和线程的生命周期。以下是两种状态的对比:

1. 设置分离状态&#xff08;Detached State&#xff09; 资源管理&#xff1a; 线程终止时&#xff0c;系统会自动释放与线程相关的所有资源&#xff08;如线程栈、线程控制块&#xff09;。不需要其他线程显式回收&#xff08;pthread_join&#xff09;。 线程生命周期&…...

软考信安26~大数据安全需求分析与安全保护工程

1、大数据安全威胁与需求分析 1.1、大数据相关概念发展 大数据是指非传统的数据处理工具的数据集,具有海量的数据规模、快速的数据流转、多样的数据类型和价值密度低等特征。 大数据的种类和来源非常多,包括结构化、半结构化和非结构化数据。 1.2、大数据安全威胁分析 (…...

Alibaba Spring Cloud 一 核心组件、特性

Alibaba Spring Cloud 是 Alibaba 基于 Spring Cloud 的分布式微服务解决方案&#xff0c;提供了一套高性能、高可靠的微服务开发和运维工具。它扩展了 Spring Cloud 的功能&#xff0c;并优化了许多在生产环境中的实践场景&#xff0c;例如服务发现、配置管理、熔断限流等。 …...

通过脚本申请免费SSL证书(泛解析SSL证书)

参考来源 1.https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E 2.https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode 3.https://github.com/acmesh-official/acme.sh/wiki/dnsapi 安装 acme.sh 配置账号 配置默认CA 安装依赖 # Cento…...

基于相机内参推导的透视投影矩阵

基于相机内参推导透视投影矩阵&#xff08;splatam&#xff09;&#xff1a; M c a m [ 2 ⋅ f x w 0.0 ( w − 2 ⋅ c x ) w 0.0 0.0 2 ⋅ f y h ( h − 2 ⋅ c y ) h 0.0 0 0 f a r n e a r n e a r − f a r 2 f a r ⋅ n e a r n e a r − f a r 0.0 0.0 − 1.0 0.0 ] M_…...

代码随想录算法训练营day34

代码随想录算法训练营 —day34 文章目录 代码随想录算法训练营前言一、62.不同路径动态规划动态规划空间优化 二、63. 不同路径 II动态规划动态规划优化空间版 三、343. 整数拆分动态规划贪心算法 96.不同的二叉搜索树总结 前言 今天是算法营的第34天&#xff0c;希望自己能够…...

Orgill EDI需求分析

Orgill 是一家位于美国的家族企业&#xff0c;主要为五金零售商、建材供应商及相关行业提供全面的分销服务和支持&#xff0c;覆盖范围遍及全球。 EDI需求分析 EDI全称Electronic Data Interchange&#xff0c;中文名称是电子数据交换&#xff0c;也被称为“无纸化贸易”。EDI…...

好用的js工具类

格式化相关 // 数字每三位增加一个逗号 function toThousands(num) {if (num) {return num.toString().replace(/\d/, function(n) {// 先提取整数部分return n.replace(/(\d)(?(\d{3})$)/g, function($1) {return $1 ,})})} else {return 0} }//输出10,000 toThousands(10…...

C++ —— 基于范围的 for 循环

C —— 基于范围的 for 循环 语法push_back() 与 emplace_back() 的区别**emplace_back()** 示例代码如下&#xff1a;**push_back()** 示例代码如下&#xff1a; 容器中的元素是结构体和类 语法 C11中引入了基于范围的for循环&#xff0c;语法如下&#xff1a; for (迭代的变…...

15-spring整合mybatis方式一

spring整合mybatis 方式一【重要】 步骤: 1.导入相关jar包 junit mybatis mysql数据库 spring相关的 aop织入 mybatis-spring 【new】 junit junit 4.12 mysql mysql-connector-java 8.0.23 org.mybatis mybatis 3.5.2 org.springframework spring-webmvc 5…...

数据结构:二叉树—面试题(一)

目录 1、相同的树 2、另一棵树的子树 3、翻转二叉树 4、平衡二叉树 5、对称二叉树 6、二叉树遍历 7、二叉树的分层遍历 1、相同的树 习题链接https://leetcode.cn/problems/same-tree/description/https://leetcode.cn/problems/same-tree/description/ 描述&#xff1a…...

GPU算力平台|在GPU算力平台部署可图大模型Kolors的应用实战教程

文章目录 一、GPU算力服务平台GPU算力服务平台的概述 二、平台账号注册流程可图大模型Kolors的应用实战教程可图大模型的介绍可图大模型的应用场景可图大模型Kolors的部署步骤 一、GPU算力服务平台 GPU算力服务平台的概述 蓝耘GPU算力平台专为高性能计算设计&#xff0c;广泛…...

Linux探秘坊-------4.进度条小程序

1.缓冲区 #include <stdio.h> int main() {printf("hello bite!");sleep(2);return 0; }执行此代码后&#xff0c;会 先停顿两秒&#xff0c;再打印出hello bite&#xff0c;但是明明打印在sleep前面&#xff0c;为什么会后打印呢&#xff1f; 因为&#xff…...

Ansys Motor-CAD:IPM 电机实验室 - 扭矩速度曲线

各位电动机迷们&#xff0c;大家好&#xff1a; 在本博客中&#xff0c;我讨论了如何使用 Ansys Motor-CAD 通过 LAB 模块获取扭矩速度曲线。使用每安培最大扭矩电机控制策略&#xff0c;并涵盖恒定扭矩区域和恒定功率、磁通减弱区域。分析了高转子速度如何影响功率输出。 模型…...

关于事件捕获和事件冒泡的理解

我一直对事件捕获和事件冒泡是挺困惑的&#xff0c;好像理解了&#xff0c;但又感觉哪里不对。这篇文章打算深入探讨一些细节性的问题&#xff0c;更好的理解事件捕获和事件冒泡。 当我们点击的时候&#xff0c;浏览器的默认行为是怎么样的&#xff1f; 搞清楚这个非常的重要…...

如何使用HASH创建低交互式蜜罐系统

关于HASH HASH是一个用于创建和启动低交互蜜罐的框架&#xff0c;可以帮助广大研究人员轻松创建HTTP无关的低交互式软件蜜罐。 HASH 的主要理念是易于配置&#xff0c;能够灵活地模拟在 HTTP/HTTPs 上运行的任何软件。尽可能减少占用空间&#xff0c;避免被检测为蜜罐。 功能…...

vue3+vite+ts安装wangeditor富文本编辑器

需求: 实现粘贴,上传图片时本地渲染但并不实现上传功能,工具栏移除不需要的工具 安装方法看官网 安装 | wangEditor 封装子组件 wangEditor.vue <template><div><div style"border: 1px solid #ccc; margin-top: 10px"><Toolbar:editor&qu…...

PostIn教程-安装配置

PostIn是一款国产开源免费的接口管理工具&#xff0c;包含项目管理、接口调试、接口文档设计、接口数据MOCK等模块&#xff0c;支持常见的HTTP协议、websocket协议等&#xff0c;支持免登陆本地接口调试&#xff0c;同时可以对项目进行灵活的成员权限、消息通知管理等。 1、服务…...

SpringBoot读取配置优先级顺序是什么?

Spring Boot外部化配置详解 目录 引言Spring Boot外部化配置概述配置加载优先级配置加载顺序详解实际案例总结 引言 Spring Boot因其“开箱即用”的特性&#xff0c;极大地简化了Java应用的开发和部署过程。它通过外部化配置机制&#xff0c;允许开发者根据不同的环境&#x…...

群晖docker获取私有化镜像http: server gave HTTP response to HTTPS client].

群晖docker获取私有化镜像提示http: server gave HTTP response to HTTPS clien 问题描述 层级时间用户事件Information2023/07/08 12:47:45cxlogeAdd image from xx.xx.31.240:1923/go-gitea/gitea:1.19.3Error2023/07/08 12:47:48cxlogeFailed to pull image [Get "http…...

MySQL8【学习笔记】

第一章前提须知 1.1 需要学什么 Dbeaver 的基本使用SQL 语句&#xff1a;最重要的就是查询&#xff08;在实战的时候&#xff0c;你会发现我们做的绝大部分工作就是 “查询”&#xff09;MySQL 存储过程&#xff08;利用数据库底层提供的语言&#xff0c;去进行业务逻辑的封装…...

汇编实验·子程序设计

一、实验目的: 1.掌握汇编中子程序编写方法 2.掌握程序传递参数的基本方法,返回值的方法。 3.掌握理解子程序(函数)调用的过程 二、实验内容 1.编写汇编语言子程序,实现C表达式SUM=X+Y的功能,具体要求: 1)函数的参数传递采用寄存器实现 2)函数的参数传递采用堆栈…...