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

深入探究 C++ 编程中的资源泄漏问题

目录

1、GDI对象泄漏

1.1、何为GDI资源泄漏?

1.2、使用GDIView工具排查GDI对象泄漏

1.3、有时可能需要结合其他方法去排查

1.4、如何保证没有GDI对象泄漏?

2、进程句柄泄漏

2.1、何为进程句柄泄漏?

2.2、创建线程时的线程句柄泄漏 

3、内存泄漏

3.1、在多态中没有将父类的析构函数声明为virtual函数,导致没有执行到子类的析构函数

3.2、使用智能指针shared_ptr发生循环引用问题,导致内存泄漏

3.3、第三方注入库有内存泄漏,导致进程有内存泄漏

3.4、内存泄漏的危害

3.5、内存泄漏的排查

4、最后


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html       在C++程序开发维护过程中,时常会遇到资源泄漏问题,比如GDI对象泄漏、进程线程句柄泄漏以及内存泄漏问题。今天我们就来深入探讨一下这几类资源泄漏以及排查这些泄露的办法。

1、GDI对象泄漏

        在Windows平台上,做UI客户端编程,很多时候都是使用系统GDI对象进行窗口的绘制,常见的GDI对象有Pen(用来绘制线条的画笔)、Brush(用来填充颜色的画刷)、Bitmap(用来处理图片的位图)、Font(用来设置文字大小的字体)、Region(区域)、DC(设备上下文)等。

1.1、何为GDI资源泄漏?

       对于Pen、Brush、Bitmap和Region等,在使用前我们需要调用创建这些对象的接口把对象创建出来,比如CreatePen/CreatePenIndirect、CreateSolidBrush/CreateBrushIndirect、CreateFont/CreateFontIndirect、CreateCompatibleBitmap等API接口,然后在使用完这些对象后需要调用DeleteObject将对象释放掉。对于DC对象,则一般调用GetDC去获取窗口的DC对象,然后在不使用时需要调用ReleaseDC将DC释放掉。如果不释放这些对象,则会导致GDI对象泄漏。

在Windows程序中,一个进程的GDI对象总数是有上限的,默认情况下上限值为10000个。可以从如下的注册表中可以看到,这个值是系统设置的默认值,一般情况下不用修改,即使修改,也不能改成很大的值。

       如果发生GDI对象泄漏的代码段,频繁地执行,程序在持续运行一段时间后,进程的GDI对象总数接近或达到10000个上限。当接近上限时,就会出现GDI绘图函数内部发生错误,返回失败,导致窗口绘制异常。紧接着可能就会产生崩溃闪退。

1.2、使用GDIView工具排查GDI对象泄漏

       GDI对象持续泄漏,对程序可能是致命的,一旦接近或达到上限,就会导致程序发声崩溃闪退。GDI对象泄漏问题,排查起来相对容易一些,先用GDIView工具先看一下是哪类GDI对象有泄漏

然后有针对性的查看操作这类GDI对象的代码,然后逐步缩小排查的范围。

       如果出现窗口绘制或显示异常,或者程序无故闪退,可以到任务管理器中查看进程的GDI对象总数的值:(默认情况下不显示GDI对象列,右键点击标题栏,在弹出窗口中勾选GDI对象选项即可显示)

如果总数接近10000个,肯定是GDI对象泄漏导致的。可以重新启动程序,然后再任务管理器中持续观察进程的GDI对象总数。

1.3、有时可能需要结合其他方法去排查

       有时也要结合其他方法来辅助定位,比如可以使用历史版本比对法,看看是从哪天开始出现泄漏。然后查看前一天svn或git上的代码提交记录,或者底层模块库发布记录,这样就能有效的缩小问题的排查范围。有次项目中出的问题,就出在底层的WebRTC开源库中。当时排查了UI层的代码没有找到泄漏点,所以怀疑可能是底层模块有问题。

       当时找到了问题的复现办法,然后使用历史版本比对法,确定了从哪一天开始出现泄漏。然后查看了svn上的代码提交记录以及底层库的发布记录, 发现出问题前一天底层开源组件组发布了新版本的WebRTC开源库,在这个版本中开源组件组为了处理一个bug,添加了一段代码,于是找开源组件的同事排查一下他们提交的代码,看看是否存在GDI泄漏。一小段时间后,他们给出结论,说他们新加的代码没问题,应该是其他模块引发的。但根据历史版本比对法的对比,问题应该就出在WebRTC开源库中,但开源组件组始终觉得他们的代码没问题。

       于是我到开源组件组那边查看svn上他们的代码修改记录,看到他们新增的一段代码果然有问题,如下所示:

#if defined (WEBRTC_WIN)//修正程序开启DWM导致的鼠标位置问题int desktop_horzers = GetDeviceCaps( GetDC(nullptr) DESKTOPHORZRES); // 问题就出在这个GetDC上int horzers = GetDeviceCaps(GetDC(nullptr),HORZRES);float scale_rate=(float)desktop_horzers/(float)horzers;relative_position.set( relative_ position.x()*scale_rate, relative_ position.y()*scale_rate );
#endif

这段代码中,他们调用GetDC接口获取窗口的DC对象,在使用完DC对象后,没有调用ReleaseDC将DC对象释放掉,所以导致了DC对象的泄漏。修改后的代码如下:

#if defined (WEBRTC_WIN)//修正程序开启DWM导致的鼠标位置问题HDC hDC = ::GetDC(nullptr);int desktop_horzers = GetDeviceCaps( hDC, DESKTOPHORZRES);int horzers = GetDeviceCaps(hDC,HORZRES);float scale_rate=(float)desktop_horzers/(float)horzers;relative_position.set( relative_ position.x()*scale_rate, relative_ position.y()*scale_rate );::ReleaseDC(nullptr, hDC);
#endif

至于开源组件的同事没找到问题,可能是他们对UI编程不熟悉导致的。

      另一个GDI对象泄漏排查实例,可以参见我之前写的文章:

使用GDIView工具排查GDI对象泄漏导致程序UI界面绘制异常的问题https://blog.csdn.net/chenlycly/article/details/128625868icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/128625868

1.4、如何保证没有GDI对象泄漏?

       要保证不出现GDI对象泄漏,在GDI对象使用完成后要将之删除或释放掉,如果不删除或释放,则会导致GDI泄漏。比如使用CreateXXXXXX创建的GDI对象,使用完后,要用DeleteObject释放;调用LoadXXXXXX函数去加载图片资源,使用完后,也要用DeleteObject释放;调用CreateXXXDC创建的DC对象,使用完后,要用DeleteDC去释放;调用GetDC获取到的DC对象,使用完后,要用ReleaseDC释放。

       调用不用的接口去创建或获取GDI对象,释放时也要调用对应的释放接口,不能混淆!在这里给大家大概的罗列一下:

创建或获取GDI对象删除或释放GDI对象
CreatePen/CreatePenIndirect(pen画笔对象)、CreateSolidBrush/CreateBrushIndirect(brush画刷对象)、CreateFont/CreateFontIndirect(Font字体对象)、CreateCompatibleBitmap(BItmap位图对象)对于Create出来的对象,要调用DeleteObject释放
CreateDC/CreateCompatibleDC(创建DC对象)调用DeleteDC释放
GetDC(获取DC对象)调用ReleaseDC释放
LoadBitmap(加载Bitmap位图)调用DeleteObject释放
LoadImage(加载图片资源)

如果加载的是Bitmap位图,则调用DeleteObject释放;

如果加载的是Cursor光标,则调用DestroyCursor释放;

如果加载的是Icon图标,则调用DestroyIcon释放。

对于上面提到的创建GDI对象的API函数,在释放时该调用哪个接口,直接到MSDN上查看API接口的Remarks部分就会找到对应的说明。比如创建兼容位图的API函数CreateCompatibleBItmap,在Remaks部分的说明如下:

 再比如加载图片的API函数LoadImage,其在Remarks部分的说明如下:

在调用Windows系统API函数遇到问题时,需要到微软MSDN帮助页面中查看API函数的详细说明(可能会给出调用函数时的注意事项,或者调用函数的示例代码等),在说明中可能会找到相关的原因!会使用MSDN,是一个Windows开发人员最基本的要求!

2、进程句柄泄漏

       进程句柄包括文件句柄(打开文件时产生的句柄)、注册表句柄(打开注册表节点时产生的句柄)、事件句柄信号量句柄线程句柄(创建线程时产生的句柄)、进程句柄(创建子进程时产生的句柄)等。

2.1、何为进程句柄泄漏?

       这些句柄在使用完成后需要及时释放,如果不释放,则会造成句柄泄漏一般调用CloseHandle去释放句柄,比如进程句柄、线程句柄、事件句柄、文件句柄等。当然也有部分句柄需要对应的接口去释放,比如注册表句柄需要调用RegCloseKey去关闭。

       在Winows系统中,进程句柄数也是有上限的,默认也是10000个,也有对应的注册表项。当进程的句柄数接近或达到10000个上限时,就会导致后续产生句柄的操作会执行失败,比如调用CreateThread去创建新的线程会失败。关于进程GDI对象上限值的注册表设置路径为:

计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows

不仅进程的GDI对象有上限,进程的句柄数(比如线程句柄、事件句柄等句柄)也是有上限的,默认也是10000个。注册表对应的节点配置如下:

这两个配置项的说明如下:

1)GDIProcessHandleQuota项:设置GDI句柄数量,默认值为2710(16进制)/10000(10进制),该值的允许范围为 256 ~ 16384 ,将其调整为大于默认的10000的值。如果您的系统配置了2G或更多内容,不妨将其设置为允许的最大值 16384(10进制)。

2)USERProcessHandleQuota项:设置用户句柄数量,默认值同样为2710(16进制)/10000(10进制),该值的允许范围为 200 ~ 18000 ,将其调整为更多的数值。同样地,对于具有2GB或更多物理内存的系统,不妨将用户句柄数直接设置为上限 18000(10进制)。

2.2、创建线程时的线程句柄泄漏 

       以前我们在项目中就遇到这样的问题,有的业务子系统是通过https和平台服务器交互的,客户端每执行一个https操作时都会创建一个线程去执行,但创建线程后没有调用CloseHanlde将线程句柄关闭掉,导致线程句柄发生泄漏当多次执行https操作导致线程句柄过多,导致后续再去创建线程创建失败了可以到Process Explorer中查看进程都占用哪些具体的句柄:

这个地方需要注意一下,调用CloaseHandle将线程句柄释放掉:

HANDLE hThread = ::CreateThread( NULL, NULL, ProcessProc, this, NULL, NULL );
if ( hThread != NULL )
{CloseHandle( hThread );
}

调用CloseHandle将句柄释放掉,并不表示将线程结束掉,线程是否结束是要看线程函数的,线程函数退出了,则线程就结束了。

        线程结束了,不会自动关闭线程句柄。对于线程函数,还有一个细节,发起线程创建的CreateThread函数返回了,不代表线程的代码已经执行到线程函数中了。这点我们在项目中遇到过这类的场景。当时的问题场景是,线程函数中访问了一个指针变量,将该指针变量的值初始化为NULL的操作放在CreateThread函数调用之后,如下所示:

// 1、指针变量定义
CVideoDec* g_pVideoDec;// 2、创建线程
HANDLE hThread = ::CreateThread( NULL, NULL, ProcessProc, this, NULL, NULL );
if ( hThread != NULL )
{CloseHandle( hThread );
}
g_pVideoDec = NULL; // 对指针变量进行初始化// 3、线程函数,函数中访问了指针变量g_pVideoDec
DWORD WINAPI ProcessCachedMsgThreadProc( LPVOID lpParameter )
{// 线程函数中访问到了该指针变量g_pVideoDec->StartDec();return 1;
}

当然这种做法是不规范的,后来发现程序会时不时崩溃在线程函数中,使用Windbg分析下来得知是线程中访问了未初始化的变量,但这个问题不是必现的。这个不必现,就和CreateThread函数返回后线程是否执行到线程函数中有关。

       有时,CreateThread返回时还没执行到线程函数中,紧接着就去初始化指针变量的值,是不会崩溃的。但如果CreateThread返回时已经执行到线程函数中,就会访问未初始化的指针变量,Release下未初始化的内存是个随机值,即指针变量的值为随机值,所以一般都会引发异常。

3、内存泄漏

       内存泄漏是C++程序使用动态申请的内存时容易出现的一类典型内存问题。动态申请内存的方式有多种,比如使用new(要用delete去释放),比如使用malloc(要用free去释放),再比如调用系统API函数HeapCreate或者HeapAlloc(要用HeapFree去释放),还有可以调用API函数VirtualAlloc(要用VirtualFree去释放),当然还有其他的API函数。动态申请的内存没有释放,则会导致内存泄漏。

       之所以会导致内存泄漏,可能是忘记释放,也可能是写了释放内存的代码,但因为种种原因没有执行到内存释放的代码,后面这类情况有一定的隐蔽性。下面我们重点说一下后面的这类情况。       

3.1、在多态中没有将父类的析构函数声明为virtual函数,导致没有执行到子类的析构函数

       比如如下的多态代码:

class CBase
{
public:CBase();~CBase();  // 没有将父类的析构函数设置为虚函数     
} class CDerived : public class CBase
{
public:CDerived();~CDerived();       
} // 将new出来的子类对象赋值给父类指针,就是多态
CBase* pBase = new CDerived;
// ...  // 中间代码省略
delete pBase;

上述代码,因为没有将父类的析构函数~CBase设置为虚函数,导致执行到delete pBase;时没有调用子类的析构函数,导致子类的部分内存没有释放,从而引发内存泄漏。特别是新人比较容易犯这类错误,之前在帮新人排查问题时遇到过,这个场景下的内存泄漏具有一定的隐蔽性。

如果不是析构函数,是其他的成员函数,如果父类的接口没有声明为virtual,多态就不会生效,会导致子类重写的成员函数不会被执行到,子类重写的成员函数中可能包含了重要的业务代码,这样就会导致重要的业务代码没有执行到,导致业务出现异常。

之前我们这边的新人将代码移植到国产化机器上时就遇到过,新人忘记在父类的接口前添加virtual声明,导致子类的接扣执行不到,导致业务出现异常,当时他查了很久没找出问题,后来找我去排查,找到这个原因,所以对这个问题印象很深!

       所以,我们在定义C++类时,如果该类可能会被继承,一般都要将父类的析构函数设置为虚函数,防止出现上述多态场景下子类的析构函数执行不到导致内存泄漏的问题。当然,设置虚函数有一定的副作用,如果一个类中包含虚函数,则类中会自动添加一个虚函数表指针,此外虚函数调用时也涉及到二次寻址问题(效率上略有影响)。

3.2、使用智能指针shared_ptr发生循环引用问题,导致内存泄漏

       使用shared_ptr可能会出现循环引用问题,这使用shared_ptr智能指针的一个典型问题(也是一个关于shared_ptr智能指针的面试题),场景是两个类中都包含了指向对方的shared_ptr对象,这样会导致new出来的两个类没有走析构,引发内存泄漏问题。

       循环引用问题的示意图如下:

相关代码如下:

#include <iostream>
#include<memory>using namespace std;class B;
class A{public:shared_ptr<B> bptr;~A(){cout<<"~A()"<<endl;}
}class B
{public:shared_ptr<A> aptr;~B( ){cout<<"~B()"<<endl;}
}int main() {shared_ptr<A> pa(new A()); // 引用加1shared_ptr<B> pb(new B()); // 引用加1pa->bptr = pb; // 引用加1pa->aptr = pa; // 引用加1return 0;
}

执行到上述return 0这句代码时,指向A和B两个对象的引用计数都是2。当退出main函数时,先析构shared_ptr<B> pb对象,B对象的引用计数减1,B对象的引用计数还为1,所以不会delete B对象,不会进入B对象析构函数,所以B类中的shared_ptr<A> aptr成员不会析构,所以此时A对象的引用计数还是2。当析构shared_ptr<A> pa时,A的引用计数减1,A对象的引用计数变为1,所以不会析构A对象。所以上述代码会导致A和B两个new出的对象都没释放,导致内存泄漏。

       为了解决上述问题,引入了weak_ptr,可以将类中包含的shared_ptr成员换成weak_ptr,如下:

相关代码如下:

#include <iostream>
#include<nemory>using namespace std;class B;
class A{public:weak_ptr<B> bptr;  // 使用weak_ptr替代shared_ptr~A(){cout<<"~A()"<<endl;}
}class B
{public:weak_ptr<A> aptr; // 使用weak_ptr替代shared_ptr~B( ){cout<<"~B()"<<endl;}
}int main() {shared_ptr<A> pa(new A());shared_ptr<B> pb(new B());pa->bptr = pb;pa->aptr = pa;return 0;
}

3.3、第三方注入库有内存泄漏,导致进程有内存泄漏

       第三库注入到我们程序进程中有两个典型的场景,一种是输入法模块的注入,一种是第三方安全软件的注入。输入法要支持所有进程的文字输入,正式通过远程注入到所有进程的模块去感知用户的输入的。第三方安全软件,为了监控软件的数据操作,一般也是需要远程注入到进程中的。

       之前项目中就遇到过第三方安全软件的注入模块有内存泄漏,导致进程内存耗尽,引发程序闪退。对于这类问题,可能其他软件运行不会触发内存泄漏,只有我们的软件才会触发内存泄漏,这个需要拿出足够的证据证明是第三方安全软件的注入模块引起的内存泄漏,否则客户会认为这是我们软件的问题,因为其他软件都没问题,客户可能会不承认这与第三方安全软件有关。当时的问题原因是,第三方安全软件处理UDP数据监控的代码有内存泄漏,因为我们的软件有大量的音视频数据收发,走的是UDP,所以触发了第三方安全软件注入模块的内存泄漏。在给出足够的证据后,客户找到第三方安全软件提供商,然后安全厂商才修复了这个bug。

3.4、内存泄漏的危害

        如果发生内存泄漏的代码,不会频繁地的执行,只是偶尔的执行一下,不会引起太大的问题。但如果有内存泄漏的代码,被频繁地执行,则会频繁地泄漏(内存不释放),最终可能会导致进程的内存耗尽,引发Out of memory(内存耗尽)的崩溃。

       进程启动时,系统会给进程分配指定大小的虚拟内存。以32位程序为例,系统会分配4GB的虚拟内存,其中用户态虚拟内存2GB,内核态虚拟内存2GB,一般内存泄漏的代码都在用户态,所以内存持续泄漏会导致用户态虚拟内存被用尽,引发Out of memory的崩溃。当然,对于64位程序,会分配足够大的虚拟内存。但用户的电脑可能很多天不关机,软件一直在持续的运行,如果有持续的内存泄漏,总有内存用尽的那一天。

       我们可以通过Windows自带的任务管理器:

去持续观察目标进程的内存变化情况,如果内存持续增长不回落,则可能存在内存泄漏。

       此外,Windows自带的任务管理器看不到进程的总的虚拟内存占用,可以使用Process Explorer工具查看进程占用的总虚拟内存,该工具显示的是用户态的虚拟内存占用:

我们一般只需要关注用户态的虚拟内存,因为业务代码占用的是用户态的虚拟内存。 

我们的程序是32位的,系统给进程分配了4GB的虚拟内存,其中用户态虚拟内存占2GB,内核态虚拟内存占2GB,从上图中看,当前程序进程的用户态虚拟内存占用达到1.7GB,已经快接近2GB的上限了,可能再运行一会,2GB用户态的内存就要耗尽了,程序就会闪退!

       注意,Process Explorer工具默认是不显示Virtual Size虚拟内存列,需要右键点击进程列表的标题栏,点击“Select Columns”,在弹出的窗口中点击“Process Memory”标签页,然后将“Virtual Size”选项:

3.5、内存泄漏的排查

       内存泄漏问题的排查,相对比较麻烦,但可以使用一些工具去分析。

3.5.1、Windows平台上内存泄漏的排查

       在Windows平台上,可以使用Windbg(使用!heap命令)、umdh.exe(该工具位于Windbg的安装目录中)、DebugDiagVMMAP以及Visual C++专用的Visual Leak Detector等工具。对于Visual Leak Detector工具,需要将相关的库编译到模块中。其他几个工具,则可以直接使用。

       关于如何使用umdh.exe工具去检测内存泄漏问题,可以参见我之前写的文章:

使用Windbg定位Windows C++程序中的内存泄漏https://blog.csdn.net/chenlycly/article/details/121295720icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/121295720       关于如何使用Visual Leak Detector工具去排查,可以参见我之前写的文章:
使用Visual Leak Detector排查内存泄漏问题icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/133041372      内存泄漏问题的定位,也不太容易,上述工具可能只能检测某些场景下的内存泄漏,如果在使用一个工具检测不出来时,可以使用其他工具试试。

       此外,从Visual Studio 2019的16.9版本开始,Visual Studio引入了google的强大内存监测工具AddressSanitizer(AddressSanitizer原先只在Linux系统中被支持,继承在gcc中),就像gcc那样提供编译选项上的支持:

在安装高版本的Visual Studio时,可以将“C++ AddressSanitizer”安装选项勾选上,这样Visual Studio中就支持AddressSanitizer了。

AddressSanitizer(简称ASan)是google提供的一款面向C/C++语言的内存错误问题检查工具,它可以检测出堆溢出(Heap buffer overflow)、栈溢出(Stack buffer overflow)、全局变量越界(Global buffer overflow)、已释放内存使用(Use after free )、初始化顺序(Initialization order bugs)、内存泄漏(Use after free )等多个内存问题。

AddressSanitizer项目地址:https://github.com/google/sanitizers/wiki/AddressSanitizer

参考文档页面:AddressSanitizerAlgorithm · google/sanitizers Wiki · GitHub

       如果要使用AddressSanitizer内存检测工具,必须要使用Visual Studio 2019的16.9及以上的版本。此外,AddressSanitizer不能像Windbg那样独立运行,直接附加到目标进程上去分析,需要使用AddressSanitizer相关编译选项重新编译代码才行。 

       对于如何在Visual Studio中使用AddressSanitizer内存分析工具,可以看一下微软官方文章的详细说明:

在Visual Studio中集成AddressSanitizerhttps://docs.microsoft.com/zh-cn/cpp/sanitizers/asan?view=msvc-170icon-default.png?t=N7T8https://docs.microsoft.com/zh-cn/cpp/sanitizers/asan?view=msvc-170

3.5.2、Linux平台上内存泄漏的排查 

       在Linux平台上,常用的内存检测工具有Valgrind和AddressSanitizer,这两个工具各有优势。

       Valgrind工具可以直接监测目标进程,不需要重新编译代码,用起来比较方便。但Valgrind在监测内存时比较消耗内存,同时会严重拖慢程序的运行速度,这对于需要实时响应的服务器来讲,是个很大的问题。

       AddressSanitizer是google出品的内存检测工具,gcc4.8及以上版本才内置了AddressSanitizer,通过编译选项去使用该工具(需要重新编译代码),该工具会占用更少的内存,不会明显拖慢程序的运行速度。不过要使用该工具,需要将gcc4.8及以上的版本才行。

       关于AddressSanitizer的优势以及如何使用AddressSanitizer,可以查看我之前写的文章:

为什么选择C/C++内存检测工具AddressSanitizer?如何使用AddressSanitizer?icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/132863447

4、最后

        上面详细讲解了GDI对象泄漏、进程句柄资源泄漏和内存泄漏三大类问题,希望能给大家提供一定的借鉴和参考。

相关文章:

深入探究 C++ 编程中的资源泄漏问题

目录 1、GDI对象泄漏 1.1、何为GDI资源泄漏&#xff1f; 1.2、使用GDIView工具排查GDI对象泄漏 1.3、有时可能需要结合其他方法去排查 1.4、如何保证没有GDI对象泄漏&#xff1f; 2、进程句柄泄漏 2.1、何为进程句柄泄漏&#xff1f; 2.2、创建线程时的线程句柄泄漏 …...

BLE协议栈1-物理层PHY

从应届生开始做ble开发也差不读四个月的时间了&#xff0c;一直在在做上层的应用&#xff0c;对蓝牙协议栈没有过多的时间去了解&#xff0c;对整体的大方向概念一直是模糊的状态&#xff0c;在开发时也因此遇到了许多问题&#xff0c;趁有空去收集了一下资料来完成了本次专栏&…...

光伏储能直流系统MATLAB仿真(PV光伏阵列+Boost DCDC变换器+负载+双向DCDC变换器+锂离子电池系统)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

C++三大特性——继承(上篇)

文章目录 目录 一、继承的概念及定义 1.1继承的概念 1.2 继承定义 1.2.1定义格式 1.2.2继承关系和访问限定符 1.2.3继承基类成员访问方式的变化 二、基类和派生类对象赋值转换 三、继承中的作用域 四、派生类的默认成员函数 一、继承的概念及定义 1.1继承的概念 继承(inherita…...

docker系列(9) - docker-compose

文章目录 9. compose编排9.1 介绍9.2 安装9.3 compose常用命令9.4 实战Springboot部署9.4.1 准备组件配置文件9.4.1.1 redis的配置文件9.4.1.2 MySQL的配置文件9.4.1.3 SpringBoot打包文件 9.4.2 准备docker-compose.yml9.4.3 启动服务9.4.4 测试验证 9.5 实战ElasticsearchKib…...

Vue中如何进行日历展示与操作

在Vue中创建交互式日历应用 在Web开发中&#xff0c;创建一个交互式的日历应用是一项常见的任务。Vue.js作为一个流行的JavaScript框架&#xff0c;提供了许多便捷的工具和组件来简化日历的开发。本文将介绍如何使用Vue来创建一个简单但功能强大的日历应用&#xff0c;包括展示…...

SpringBoot 返回图片、Excel、音视频等流数据几种处理方式

方式一:直接针对响应对象(response)实现 @RestController @Slf4j @Api(tags = SwaggerConfig.TAG_IMAGE) @RequestMapping(SwaggerConfig.TAG_IMAGE) public class ImageController {@GetMapping(value = "/getImage")@ApiOperation("获取图片-以ImageIO流形…...

【Vue面试题一】、说说你对 Vue 的理解

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;有使用过vue吗&#xff…...

vue3 axios

npm install axios import axios from axios // 创建axios实例 const request axios.create({baseURL: ,// 所有的请求地址前缀部分(没有后端请求不用写)timeout: 80000, // 请求超时时间(毫秒)withCredentials: true,// 异步请求携带cookie// headers: {// 设置后端需要的传…...

划片机:半导体生产的必备设备

划片机是半导体加工行业中的重要设备&#xff0c;主要用于将晶圆切割成晶片颗粒&#xff0c;为后道工序粘片做好准备。随着国内半导体生产能力的提高&#xff0c;划片机市场的需求也在逐渐增加。 在市场定位上&#xff0c;划片机可以应用于半导体芯片和其他微电子器件的制造过程…...

电路维修——双端队列BFS

达达是来自异世界的魔女&#xff0c;她在漫无目的地四处漂流的时候&#xff0c;遇到了善良的少女翰翰&#xff0c;从而被收留在地球上。 翰翰的家里有一辆飞行车。有一天飞行车的电路板突然出现了故障&#xff0c;导致无法启动。电路板的整体结构是一个 R 行 C 列的网格&#…...

乌班图22.04 kubeadm简单搭建k8s集群

1. 我遇到的问题 任何部署类问题实际上对于萌新来说都不算简单&#xff0c;因为没有经验&#xff0c;这里我简单将部署的步骤和想法给大家讲述一下 2. 简单安装步骤 准备 3台标准安装的乌班图server22.04&#xff08;采用vm虚拟机安装&#xff0c;ip为192.168.50.3&#xff0…...

vue3富文本编辑器的二次封装开发-Tinymce

欢迎点击领取 -《前端面试题进阶指南》&#xff1a;前端登顶之巅-最全面的前端知识点梳理总结 *分享一个使用比较久的&#x1fa9c; 简介 1、安装&#xff1a;pnpm add tinymce / pnpm add tinymce/tinymce-vue > Vue3 tinymce tinymce/tinymce-vue 2、功能实现图片上传…...

typescript 类型声明文件

typescript 类型声明文件概述 在今天几乎所有的JavaScript应用都会引入许多第三方库来完成任务需求。这些第三方库不管是否是用TS编写的&#xff0c;最终都要编译成JS代码&#xff0c;才能发布给开发者使用。6我们知道是TS提供了类型&#xff0c;才有了代码提示和类型保护等机…...

Hadoop伪分布式环境搭建

什么是Hadoop伪分布式集群&#xff1f; Hadoop 伪分布式集群是一种在单个节点上模拟分布式环境的配置&#xff0c;用于学习、开发和测试 Hadoop 的功能和特性。它提供了一个简化的方式来体验和熟悉 Hadoop 的各个组件&#xff0c;而无需配置和管理一个真正的多节点集群。 在 Ha…...

javaee ssm框架项目添加分页控件

搭建ssm框架项目 参考上一篇博文 添加分页控件 引入依赖 <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schema…...

2023年中国非晶纳米晶竞争格局、产业链及行业产量分析[图]

非晶合金又称“液态金属、金属玻璃”&#xff0c;是一种新型软磁合金材料&#xff0c;主要包含铁、硅、硼等元素。其主要制品非晶合金薄带的制造工艺是采用急速冷却技术将合金熔液以每秒106℃的速度急速冷却&#xff0c;形成厚度约0.03mm的非晶合金薄带&#xff0c;物理状态表现…...

在业务开发中遇到的树形结构(部门、区域、职位),递归处理。

文章目录 概要对象结构示例完整示例小结 概要 本文主要记录在树形结构中会遇到的问题&#xff0c; 使用部门结构讲解&#xff0c;main方法进行演示。 1、获取部门树结构 2、根据部门id获取所有下级 3、根据部门id获取上级部门 4、根据部门id获取类似面包屑&#xff08;总公司…...

张量-算术操作函数

tf.add(x,y,name None)求和函数 示例代码如下: import tensorflow.compat.v1 as tf tf.disable_v2_behavior()x 1 y 2a tf.add(x,y)with tf.Session() as sess:print(sess.run(a)) tf.subtract(x,y,name None)减法函数 示例代码如下: import tensorflow.compat.v1 as …...

虚拟展厅有什么重要意义,了解虚拟展厅在宣传中的应用

引言&#xff1a; 随着科技的不断进步&#xff0c;虚拟展厅已经逐渐成为展览行业的重要一环。虚拟展厅是一种数字化平台&#xff0c;为观众提供了与传统展览完全不同的体验。 一&#xff0e;虚拟展厅的定义 虚拟展厅是一个通过互联网和虚拟现实技术创建的数字展示空间&#x…...

华为OD机试真题-补种未成活胡杨(Java/C++/Go/Python)

华为OD机试真题-补种未成活胡杨(Java/C++/Go/Python) 题目描述 近些年来,我国防沙治沙取得显著成果。某沙漠新种植N棵胡杨(编号1-N),排成一排。 一个月后,有M棵胡杨未能成活。现可补种胡杨K棵,请问如何补种(只能补种,不能新种),可以得到最多的连续胡杨树? 输入…...

Java卷上天,可以转行干什么?

小刚是某名企里的一位有5年经验的高级Java开发工程师&#xff0c;每天沉重的的工作让他疲惫不堪&#xff0c;让他萌生出想换工作的心理&#xff0c;但是转行其他工作他又不清楚该找什么样的工作 因为JAVA 这几年的更新实在是太太太……快了&#xff0c;JAVA 8 都还没用多久&am…...

Pyside6 安装和简单界面开发

Pyside6 安装和简单界面开发 Pyside6介绍Pysied6开发环境搭建Python安装Pysied6安装 Pyside6界面开发简单界面设计界面设计界面编译 编写界面初始化代码软件打包 Pyside6介绍 对于Python的GUI开发来说&#xff0c;Python自带的可视化编程模块的功能较弱&#xff0c;PySide是跨…...

python读取vivo手机截图,将满屏图片文件移动别的路径

问题之初 python读取vivo手机截图&#xff0c; 将满屏图片文件移动别的路径好多这样的图片&#xff0c;占用手机大量的内存&#xff0c;食之无味弃之可惜&#xff01;那么会复制粘贴&#x1f440;代码的我们我们今天就把这些图片筛选清理掉。 这段代码 原有逻辑的基础上&…...

【一周安全资讯1007】多项信息安全国家标准10月1日起实施;GitLab发布紧急安全补丁修复高危漏洞

要闻速览 1.以下信息安全国家标准10月1日起实施 2.GitLab发布紧急安全补丁修复高危漏洞 3.主流显卡全中招&#xff01;GPU.zip侧信道攻击可泄漏敏感数据 4.MOVEit漏洞导致美国900所院校学生信息发生大规模泄露 5.法国太空和国防供应商Exail遭黑客攻击&#xff0c;泄露大量敏感…...

2023年09月个人工作生活总结

本文为 2023 年 9 月工作生活总结。 研发编码 Alpine 容器 某工程部署于alpine镜像&#xff0c;当初看上是因为其体积小&#xff0c;其它微服务&#xff0c;在250MB左右&#xff0c;但那个工程只用50MB。最近发现时间戳转换不正确。对于同一时间字符串转时间戳函数&#xff0…...

现货白银图表分析的依据

现货白银的行情图表分析其实与股票的差不多&#xff0c;投资者可以结合均线、k线的变化&#xff0c;来分析实时的行情走势。当走势图的均线呈多头排列&#xff0c;即短期、中期、长期均线依次从上到下排列并向右上方运行&#xff0c;且白银价格沿各均线向右上方拉升&#xff0c…...

python多线程与多进程

多线程与多进程 一, 什么是进程, 什么是线程? ​ 进程: 运行中的程序. 每次我们执行一个程序, 咱们的操作系统对自动的为这个程序准备一些必要的资源(例如, 分配内存, 创建一个能够执行的线程. ) ​ 线程: 程序内, 可以直接被CPU调度的执行过程. 是操作系统能够进行运算调度…...

62从零开始学Java之时间相关的类都有哪些?

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦 千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者 前言 我们在开发时&#xff0c;除了数字、数学这样的常用API之外&#xff0c;还有日期时间类&#xff0c;更…...

2023年山东安全员c证考试题库及答案解析来了!

...

做足球经理头像的网站/近期热点新闻

单例模式 允许自由创建每个类没有实际意义&#xff0c;还有可能造成系统性能下降 优势&#xff1a;减少创建java实例带来的系统开销 便于系统跟踪某个实例的生命周期&#xff0c;实例状态等 2 工厂模式&#xff1a; 工厂模式又分简单工厂模式&#xff0c;抽象工厂模式 使用简单…...

网站最好服务器/常州网站seo

官网http://commons.apache.org/index.html 参考&#xff1a;http://langgufu.iteye.com/blog/1913579 以及 http://www.cnblogs.com/younggun/p/3247261.html /**Apache Commons包含了很多开源的工具&#xff0c;用于解决平时编程经常会遇到的问题&#xff0c;减少重复劳动。一…...

任务网站建设/搜索引擎调词工具哪个好

B2B是商家/企业与商家/企业的网络交易&#xff0c;例如阿里巴巴、会搜商务网。 B2C是商家企业与消费者的网络交易&#xff0c;比如淘宝商城。 C2C是个体户与消费者的交易&#xff0c;比如淘宝。 b2b&#xff0c;b2c&#xff0c;c2c全面解释&#xff1a; b2b&#xff1a; B2B&am…...

课程设计代做网站php/各大网站的网址

目录 文档和教程 教程 文档 论文&#xff1a; PPT: 开发社区 Linux社区 RDMA社区 其他知识 玩家 硬件厂商和用户 参考和学习资料和途径 文档和教程 教程 链接&#xff1a;https://www.jianshu.com/p/22bbb8f029e6 视频教程&#xff1a;腾讯视频 英伟达网络的个人频…...

淘宝客wordpress想/广州网络运营课程培训班

图的存在&#xff0c;让数据变得形象化。无论多么复杂的东西&#xff0c;都是简单的组合。 1 import matplotlib.pyplot as plt 2 import numpy as np 3 dict {A: 40, B: 70, C: 30, D: 85} 4 for i, key in enumerate(dict):#Circulate both index and value(Here is key) 5…...

长城集团建设有限公司网站/b站推出的短视频app哪个好

django之Cookie与Session-66 一.Cookie与Session由来 因为Http协议的特性,每一次来自用户浏览器的请求都是无状态且独立的,通俗地说,就是无法保存用户状态,后台服务器根本就不知道当前请求和以前及以后请求是否来自同一用户,对于静态网站,这可能不是一个问题,但是对于动态网站来…...