网站app开发哪家好,男科医院哪家好一些,深圳个人网站设计,在线直播网站开发目录 1. RAII和智能指针的设计思路
2. C标准库智能指针的使用
2.1 auto_ptr
2.2 unique_ptr
2.3 简单模拟实现auto_ptr和unique_ptr的核心功能
2.4 shared_ptr
2.4.1 make_shared 2.5 weak_ptr
2.6 shared_ptr的缺陷#xff1a;循环引用问题 3. shared_ptr 和 unique_…目录 1. RAII和智能指针的设计思路
2. C标准库智能指针的使用
2.1 auto_ptr
2.2 unique_ptr
2.3 简单模拟实现auto_ptr和unique_ptr的核心功能
2.4 shared_ptr
2.4.1 make_shared 2.5 weak_ptr
2.6 shared_ptr的缺陷循环引用问题 3. shared_ptr 和 unique_ptr 的explicit 修饰
4. shared_ptr和weak_ptr的基础模拟实现
5. shared_ptr的线程安全问题
6. 怎样才能使用delete[]
方法1
方法2
1. 仿函数对象做删除器
2. 函数指针做删除器
3. lambda表达式做删除器
4. 实现其他资源管理的删除器
7. C11和boost中智能指针的关系 8. 内存泄漏
8.1 什么是内存泄漏内存泄漏的危害
8.2 如何检测内存泄漏
8.3 如何避免内存泄漏 1. RAII和智能指针的设计思路 1. RAII是⼀种管理资源的类的设计思想本质是⼀种利⽤对象⽣命周期来管理获取到的动态资源避免资源泄漏 这⾥的资源可以是内存、⽂件指针、⽹络连接、互斥锁等等 RAII在获取资源时把资源委托给⼀个对象接着控制对资源的访问资源在对象的⽣命周期内始终保持有效最后在对象析构的时候释放资源这样保障了资源的正常释放避免资源泄漏问题 2. 智能指针类除了满⾜RAII的设计思路还要⽅便资源的访问所以智能指针类还会像迭代器类⼀样重载 operator*/operator-/operator[] 等运算符⽅便访问资源 智能指针就是帮我们管理动态分配的内存的它会帮助我们自动释放new出来的内存从而避免内存泄漏 templateclass T
class SmartPtr
{
public:// RAIISmartPtr(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出来的数组以后程序简单多了SmartPtrint sp1 new int[10];SmartPtrint 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;
} 2. C标准库智能指针的使用 C98 提供了 auto_ptr 模板的解决方案C11 增加unique_ptr、shared_ptr 和weak_ptr C标准库中的智能指针都在memory这个头⽂件下⾯我们包含memory就可以是使⽤了智能指针有好⼏种除了weak_ptr他们都符合RAII和像指针⼀样访问的⾏为原理上⽽⾔主要是解决智能指针拷⻉时的思路不同 2.1 auto_ptr auto_ptr - C Referencehttps://legacy.cplusplus.com/reference/memory/auto_ptr/ auto_ptr是C98时设计出来的智能指针他的特点是拷⻉时把被拷⻉对象的资源的管理权转移给拷⻉对象这是⼀个⾮常糟糕的设计因为他会导致被拷贝对象悬空访问报错的问题C11设计出新的智能指针后强烈建议不要使⽤auto_ptr 其他C11出来之前很多公司也是明令禁⽌使⽤这个智能指针的 用 法: auto_ptr类型 变量名(new 类型) 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_ptrDate ap1(new Date);// 拷⻉时管理权限转移被拷⻉对象ap1悬空auto_ptrDate ap2(ap1);// 空指针访问ap1对象已经悬空//ap1-_year;cout sp1-_year endl;return 0;
} 2.2 unique_ptr unique_ptr是C11设计出来的智能指针他的名字翻译出来是唯⼀指针他的特点的不⽀持拷⻉只⽀持移动 如果不需要拷⻉的场景就⾮常建议使⽤他 unique_ptr - C Referencehttps://legacy.cplusplus.com/reference/memory/unique_ptr/ unique_ptr的特性 1. 两个指针不能指向同一个资源 2. 不能进行左值unique_ptr复制构造和左值复制赋值操作但可以临时右值赋值构造和赋值 3. 保存指向某个对象的指针当它本身离开作用域时会自动释放它指向的对象 4. 指针保存在容器里是安全的 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_ptrDate up1(new Date);// 不⽀持拷⻉//unique_ptrDate up2(up1);// ⽀持移动但是移动后up1也悬空所以使⽤移动要谨慎unique_ptrDate up3(move(up1));cout sp2-_year endl;return 0;
} unique_ptr是不支持拷贝的只支持移动不然会报错它的原理是将拷贝构造和赋值重载直接delete掉然后提供移动构造 移动构造这种方式也算是管理权转移因为如果是右值的话就可以转移资源深拷贝也是转移资源 unique_ptr(const unique_ptrT sp) delete;
unique_ptrT operator(const unique_ptrT sp) delete;unique_ptr(unique_ptrT sp):_ptr(sp._ptr)
{sp._ptr nullptr;
}unique_ptrT operator(unique_ptrT sp)
{delete _ptr;_ptr sp._ptr;sp._ptr nullptr;
} 2.3 简单模拟实现auto_ptr和unique_ptr的核心功能 auto_ptr的思路是拷贝时转移资源管理权给被拷⻉对象这种思路是不被认可的也不建议使用 unique_ptr的思路是不支持拷贝 //auto_ptr
namespace bit
{templateclass Tclass auto_ptr{public:auto_ptr(T* ptr):_ptr(ptr){}auto_ptr(auto_ptrT sp):_ptr(sp._ptr){// 管理权转移sp._ptr nullptr;}auto_ptrT operator(auto_ptrT 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;};//unique_ptrtemplateclass Tclass 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_ptrTsp) delete;unique_ptrT operator(const unique_ptrTsp) delete;unique_ptr(unique_ptrT sp):_ptr(sp._ptr){sp._ptr nullptr;}unique_ptrT operator(unique_ptrT sp){delete _ptr;_ptr sp._ptr;sp._ptr nullptr;}private:T* _ptr;};int main(){bit::auto_ptrDate ap1(new Date);// 拷⻉时管理权限转移被拷⻉对象ap1悬空bit::auto_ptrDate ap2(ap1);// 空指针访问ap1对象已经悬空//ap1-_year;bit::unique_ptrDate up1(new Date);// 不⽀持拷⻉//unique_ptrDate up2(up1);// ⽀持移动但是移动后up1也悬空所以使⽤移动要谨慎bit::unique_ptrDate up3(move(up1));return 0;} 2.4 shared_ptr shared_ptr是C11设计出来的智能指针他的名字翻译出来是共享指针他的特点是⽀持拷⻉也⽀持移动 如果需要拷⻉的场景就需要使⽤他了 底层是⽤引⽤计数的⽅式实现的 shared_ptr - C Referencehttps://legacy.cplusplus.com/reference/memory/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;}
};int main()
{shared_ptrDate sp1(new Date);// ⽀持拷⻉shared_ptrDate sp2(sp1);shared_ptrDate sp3(sp2);cout sp3-_year endl;// ⽀持移动但是移动后sp1也悬空所以使⽤移动要谨慎shared_ptrDate sp4(move(sp1));return 0;
} 2.4.1 make_shared
template class T, class... Args shared_ptrT make_shared(Args... args) shared_ptr 除了⽀持⽤指向资源的指针构造还⽀持 make_shared ⽤初始化资源对象的值 直接构造 int main()
{//这两个的结果是相同的std::shared_ptrDate sp1(new Date(2024, 9, 11));shared_ptrDate sp2 make_sharedDate(2024, 9, 11);auto sp3 make_sharedDate(2024, 9, 11);return 0;
} 2.5 weak_ptr weak_ptr是C11设计出来的智能指针他的名字翻译出来是弱辅助指针他完全不同于上⾯的智能指针他不⽀持RAII也就意味着不能⽤它直接管理资源 weak_ptr的产⽣本质是要解决shared_ptr的⼀个循环引用导致内存泄漏的问题 weak_ptr - C Referencehttps://legacy.cplusplus.com/reference/memory/weak_ptr/ //weak_ptr
templateclass T
class weak_ptr
{
public:weak_ptr(){}weak_ptr(const shared_ptrT sp):_ptr(sp.get()){}weak_ptrT operator(const shared_ptrT sp){_ptr sp.get();return *this;}
private:T* _ptr nullptr;
};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 lock在资源还没有释放之前再产生一个shared_ptr去管理资源 如果资源已经被释放返回的shared_ptr是⼀个空对象如果资源没有释放则通过返回的shared_ptr访问资源是安全的 int main()
{std::shared_ptrstring sp1(new string(111111));std::shared_ptrstring sp2(sp1);std::weak_ptrstring wp sp1;cout wp.expired() endl;cout wp.use_count() endl;// sp1和sp2都指向了其他资源则weak_ptr就过期了sp1 make_sharedstring(222222);cout wp.expired() endl;cout wp.use_count() endl;sp2 make_sharedstring(333333);cout wp.expired() endl;cout wp.use_count() endl;wp sp1;//std::shared_ptrstring sp3 wp.lock();auto sp3 wp.lock();cout wp.expired() endl;cout wp.use_count() endl;*sp3 ###;cout *sp1 endl;return 0;
} 2.6 shared_ptr的缺陷循环引用问题 shared_ptr⼤多数情况下管理资源⾮常合适⽀持RAII也支持拷贝 但是在循环引用的场景下会导致资源没得到释放内存泄漏所以我们要认识循环引⽤的场景和资源没释放的原因并且学会使用weak_ptr解决这种问题 如下图所述场景n1和n2析构后管理两个节点的引用计数减到1 1. 右边的节点被左边节点中的_next管着当_next析构后右边的节点就释放了 2. _next是左边节点的的成员当左边节点释放_next就析构了 3. 左边节点由右边节点中的_prev管着呢当_prev析构后左边的节点就释放了 4. _prev是右边节点的成员当右边节点释放_prev就析构了 至此逻辑上成功形成回旋镖似的循环引用谁都不会释放就形成了循环引用导致内存泄漏 解决循环引用的方法 把ListNode结构体中的_next和_prev改成weak_ptrweak_ptr绑定到shared_ptr时不会增加它的引用计数_next和_prev不参与资源释放管理逻辑就成功打破了循环引用解决了这⾥的问题 struct ListNode
{int _data;std::shared_ptrListNode _next;std::shared_ptrListNode _prev;// 这里改成weak_ptr当n1-_next n2;绑定shared_ptr时// 不增加n2的引⽤计数不参与资源释放的管理就不会形成循环引⽤了/*std::weak_ptrListNode _next;std::weak_ptrListNode _prev;*/~ListNode(){cout ~ListNode() endl;}
};int main()
{// 循环引⽤ -- 内存泄露std::shared_ptrListNode n1(new ListNode);std::shared_ptrListNode 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// weak_ptr是专⻔绑定shared_ptr不增加他的引⽤计数作为⼀些场景的辅助管理//std::weak_ptrListNode wp(new ListNode);return 0;
} 3. shared_ptr 和 unique_ptr 的explicit 修饰 shared_ptr 和 unique_ptr 的构造函数都使用explicit 修饰防⽌普通指针隐式类型转换 成智能指针对象 expired判断当前weak_ptr智能指针是否还有托管的对象有则返回false无则返回true 如果返回true等价于 use_count() 0也就是已经没有托管的对象了也有可能是还有析构函数进行释放内存但此对象的析构已经发生 void kiana() {// expired判断当前智能指针是否还有托管的对象有则返回false无则返回trueif (!gw.expired()) {std::cout gw is valid\n; // 有效的还有托管的指针}else {std::cout gw is expired\n; // 过期的没有托管的指针}
}int main() {{auto sp std::make_sharedint(42);gw sp;f();}// 当{ }体中的指针生命周期结束后再来判断其是否还有托管的指针kiana();return 0;
} 4. shared_ptr和weak_ptr的基础模拟实现 shared_ptr的设计是重中之重尤其是引⽤计数的设计 引用计数的本质是一块资源有多少个智能指针对象管理 因为⼀份资源就需要⼀个引⽤计数所以引⽤计数不能使用静态成员的⽅式实现因为静态的成员变量是属于整个类的所有对象所以要使用堆上动态开辟的⽅式构造智能指针对象时来⼀份资源就要new⼀个引⽤计数出来 多个shared_ptr指向资源时就引⽤计数两个对象里面同时有两个指针指向资源同时有另外两个指针指向引用计数shared_ptr对象析构时就--引⽤计数引⽤计数减到0时代表当前析构的shared_ptr是最后⼀个管理资源的对象则析构资源 我们这⾥实现的shared_ptr和weak_ptr都是以最简洁的⽅式实现的只能满⾜基本的功能这⾥的weak_ptr lock等功能是⽆法实现的想要实现就要把shared_ptr和weak_ptr⼀起改了把引⽤计数拿出来放到⼀个单独类型shared_ptr和weak_ptr都要存储指向这个类的对象才能实现 //shared_ptr
templateclass T
class shared_ptr
{
public:explicit shared_ptr(T* ptr nullptr): _ptr(ptr), _pcount(new int(1)){}templateclass Dshared_ptr(T* ptr, D del): _ptr(ptr), _pcount(new int(1)), _del(del){}shared_ptr(const shared_ptrT sp):_ptr(sp._ptr), _pcount(sp._pcount), _del(sp._del){(*_pcount);}void release(){if (--(*_pcount) 0){// 最后⼀个管理的对象释放资源_del(_ptr);delete _pcount;_ptr nullptr;_pcount nullptr;}}shared_ptrT operator(const shared_ptrT sp){if (_ptr ! sp._ptr){release();_ptr sp._ptr;_pcount sp._pcount;(*_pcount);_del sp._del;}return *this;}~shared_ptr(){release();}T* get() const{return _ptr;}int use_count() const{return *_pcount;}T operator*(){return *_ptr;}T* operator-(){return _ptr;}
private:T* _ptr;int* _pcount;//atomicint* _pcount;functionvoid(T*) _del [](T* ptr) {delete ptr; };
};//weak_ptr
templateclass T
class weak_ptr
{
public:weak_ptr(){}weak_ptr(const shared_ptrT sp):_ptr(sp.get()){}weak_ptrT operator(const shared_ptrT sp){_ptr sp.get();return *this;}
private:T* _ptr nullptr;
};int main()
{bit::shared_ptrDate sp1(new Date);// ⽀持拷⻉bit::shared_ptrDate sp2(sp1);bit::shared_ptrDate sp3(sp2);cout sp1.use_count() endl;sp1-_year;cout sp1-_year endl;cout sp2-_year endl;cout sp3-_year endl;return 0;
} 5. shared_ptr的线程安全问题 1. shared_ptr的引⽤计数对象在堆上如果多个shared_ptr对象在多个线程中进⾏shared_ptr的拷⻉析构时会访问修改引⽤计数就会存在线程安全问题所以shared_ptr引⽤计数是需要加锁或者原⼦操作保证线程安全的 2. shared_ptr指向的对象也是有线程安全的问题的但是这个对象的线程安全问题不归shared_ptr管它也管不了应该有外层使⽤shared_ptr的⼈进⾏线程安全的控制 3. 下⾯的程序会崩溃或者A资源没释放bit::shared_ptr引⽤计数从int*改成atomicint*就可以保证引⽤计数的线程安全问题或者使⽤互斥锁加锁也可以 struct AA
{int _a1 0;int _a2 0;~AA(){cout ~AA() endl;}
};int main()
{bit::shared_ptrAA p(new AA);const size_t n 100000;mutex mtx;auto func [](){for (size_t i 0; i n; i){// 这⾥智能指针拷⻉会计数bit::shared_ptrAA copy(p);{unique_lockmutex 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;
} 6. 怎样才能使用delete[] 智能指针析构时默认是进⾏delete释放资源这也就意味着如果不是new出来的资源交给智能指针管理析构时就会崩溃 // 这样实现程序会崩溃
unique_ptrDate up1(new Date[10]);
shared_ptrDate sp1(new Date[10]); 方法1 临时的解决方法因为new[]经常使⽤所以unique_ptr和shared_ptr实现了⼀个特化版本这个特化版本析构时⽤的delete[] unique_ptrDate[] up1(new Date[5]);shared_ptrDate[] sp1(new Date[5]); 就可以管理new []的资源 // 解决⽅案2
// 因为new[]经常使⽤所以unique_ptr和shared_ptr
// 实现了⼀个特化版本这个特化版本析构时⽤的delete[]
unique_ptrDate[] up1(new Date[5]);
shared_ptrDate[] sp1(new Date[5]); 方法2 通用方法 当不是new[]出来的而是fopen之类的解决方法 shared_ptrFILE sp5(fopen(Test.cpp, r), Fclose()); 智能指针⽀持在构造时给⼀个删除器所谓删除器本质就是⼀个可调⽤对象这个可调⽤对象中实现你想要的释放资源的⽅式当构造智能指针时给了定制的删除器在智能指针析构时就会调⽤删除器去释放资源 这个删除器可以是仿函数对象函数指针lambda表达式包装器 1. 仿函数对象做删除器 unique_ptrDate, DeleteArrayDate up2(new Date[5], DeleteArrayDate
()); class Fclose
{
public:void operator()(FILE* ptr){cout fclose: ptr endl;fclose(ptr);}
};unique_ptrDate, DeleteArrayDate up2(new Date[5]);
shared_ptrDate sp2(new Date[5], DeleteArrayDate());2. 函数指针做删除器 //函数指针做删除器
unique_ptrDate, void(*)(Date*) up3(new Date[5], DeleteArrayFuncDate);
shared_ptrDate sp3(new Date[5], DeleteArrayFuncDate); 3. lambda表达式做删除器 // lambda表达式做删除器
auto delArrOBJ [](Date* ptr) {delete[] ptr; };unique_ptrDate, decltype(delArrOBJ) up4(new Date[5], delArrOBJ);shared_ptrDate sp4(new Date[5], delArrOBJ); 4. 实现其他资源管理的删除器 // 实现其他资源管理的删除器
shared_ptrFILE sp5(fopen(Test.cpp, r), Fclose());shared_ptrFILE sp6(fopen(Test.cpp, r), [](FILE* ptr) {cout fclose: ptr endl;fclose(ptr);}); 1. unique_ptr和shared_ptr⽀持删除器的⽅式有所不同 unique_ptr是在类模板参数⽀持的shared_ptr是构造函数参数⽀持的 2. 使⽤仿函数unique_ptr可以不在构造函数传递因为仿函数类型构造的对象直接就可以调⽤但是下⾯的函数指针和lambda的类型不可以 如果unique_ptr想使用删除器最好就用仿函数来解决问题shared_ptr都可以使用 7. C11和boost中智能指针的关系 1. Boost库是为C语⾔标准库提供扩展的⼀些C程序库的总称Boost社区建⽴的初衷之⼀就是为C的标准化⼯作提供可供参考的实现Boost社区的发起⼈Dawes本⼈就是C标准委员会的成员之⼀ 在Boost库的开发中Boost社区也在这个⽅向上取得了丰硕的成果C11及之后的新语法 和库有很多都是从Boost中来的 2. C 98 中产⽣了第⼀个智能指针auto_ptr 3. C boost给出了更实⽤的scoped_ptr/scoped_array和shared_ptr/shared_array和weak_ptr等 4. C TR1引⼊了shared_ptr等不过注意的是TR1并不是标准版 5. C 11引⼊了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的 8. 内存泄漏
8.1 什么是内存泄漏内存泄漏的危害 什么是内存泄漏内存泄漏指因为疏忽或错误造成程序未能释放已经不再使⽤的内存⼀般是忘记释放或者发⽣异常释放程序未能执⾏导致的。内存泄漏并不是指内存在物理上的消失⽽是应⽤程序分配某段内存后因为设计错误失去了对该段内存的控制因⽽造成了内存的浪费 内存泄漏的危害普通程序运⾏⼀会就结束了出现内存泄漏问题也不⼤进程正常结束⻚表的映射关系解除物理内存也可以释放。⻓期运⾏的程序出现内存泄漏影响很⼤如操作系统、后台服务、⻓时间运⾏的客户端等等不断出现内存泄漏会导致可⽤内存不断变少各种功能响应越来越慢最终卡死 int main()
{// 申请⼀个1G未释放这个程序多次运⾏也没啥危害// 因为程序⻢上就结束进程结束各种资源也就回收了char* ptr new char[1024 * 1024 * 1024];cout (void*)ptr endl;return 0;
} 8.2 如何检测内存泄漏 linux下内存泄漏检测linux下⼏款内存泄漏检测⼯具 Linux下几款C程序中的内存泄露检查工具_c内存泄露工具分析-CSDN博客https://blog.csdn.net/gatieme/article/details/51959654windows下使⽤第三⽅⼯具 windows下的内存泄露检测工具VLD使用_windows内存泄漏检测工具-CSDN博客https://blog.csdn.net/lonely1047/article/details/120038929 8.3 如何避免内存泄漏 1. ⼯程前期良好的设计规范养成良好的编码规范申请的内存空间记着匹配的去释放。ps这个是理想状态。但是如果碰上异常时就算注意释放了还是可能会出问题。需要下⼀条智能指针来管理才有保证 2. 尽量使⽤智能指针来管理资源如果⾃⼰场景⽐较特殊采⽤RAII思想⾃⼰造个轮⼦管理 3. 定期使⽤内存泄漏⼯具检测尤其是每次项⽬快上线前不过有些⼯具不够靠谱或者是收费 总结⼀下内存泄漏⾮常常⻅解决⽅案分为两种1、事前预防型。如智能指针等。2、事后查错型如泄漏检测⼯具 无论何时的~