C/C++内存泄漏原因分析与应对方法

内存泄漏 一、内存泄漏的危害:
内存泄漏会导致当前应用程序消耗更多的内存,使得其他应用程序可用的内存更少了 。
如果有个进程可用的内存不够,就会触发Linux操作系统的直接/后台内存回收(即将一些内存页的数据写到磁盘里,那么该页也就可用了,脏页回写) 。虽然后台回收是异步的不阻塞当前进程,但是内存还是不够会触发直接内存回收,最后内存泄漏积累到一定程度,会直接触发OOM,该机制会杀掉那些实时占用内存大的进程 。
而且,即使没有OOM,无论是直接回收还是后台回收,都需要磁盘I/O而且需要多执行额外的回收线程,使系统性能下降 。
如果直接内存回收后,空闲的物理内存仍然无法满足此次物理内存的申请,那么内核就会放最后的大招了 ——触发 OOM (Out of )机制 。
还有资源泄漏:
比如没有关闭文件,程序提前或报错或忘记关闭,则可能导致想写入文件的数据没有真正落盘,从而丢失数据 。
二、内存泄漏举例:
1,在free()前就返回了,或者是报错并退出程序 。要在程序的所有路径上(if()的各个条件)都执行资源释放操作 。
2,在析构函数中未执行内存释放操作 。在构造函数中申请了堆内存或者打开了文件,在析构函数中忘了释放资源 。
3,基类的析构函数未声明为虚函数 。
析构函数如果不声明为虚函数,可能会导致多态对象在删除时无法正确调用派生类的析构函数(如果子类构造函数里()了内存,然后在析构函数里free()),从而导致内存泄漏 。
4,循环引用导致内存泄漏,用解决 。如下示例:
class Contro {private:double* p;public:Contro() {p = new double[10];}~Contro() {delete[] p;std::cout << "in ~Contro" << std::endl;}// 类内类class SubContro {public:SubContro() {p = new double[10];}~SubContro() {delete[] p;std::cout << "in ~SubContro" << std::endl;}std::shared_ptr controller_;};std::shared_ptr> sub_controller_;};int main() {auto contro = std::make_shared();auto sub_contro = std::make_shared();contro->sub_controller_ = sub_contro;sub_contro->controller_ = contro;// 打印引用计数std::cout << "contro use_count: " << contro.use_count() << std::endl;std::cout << "sub_contro use_count: " << sub_contro.use_count() << std::endl;return 0;}
发生循环引用,两个的引用计数输出都是2,所以main函数结束的时候,引用计数没有减为0,就不会调用二者的析构函数,导致资源泄漏 。
将类里的改成即可,后者不会增加引用计数,因此两个智能指针的引用计数都是1,然后main结束的时候,引用计数减少为0,然后执行析构函数,此时不会发生内存泄漏,输出如下:
contro use_count: 1sub_contro use_count: 2in ~Controin ~SubContro
三、避免内存泄漏的手段: 1. 静态代码检查工具 (1)对于大型项目,可以使用静态代码分析工具
像开源的有软件,集成了一些静态代码分析的工具
静态代码检查工具会从词法、语法、语义等多维度去对工程代码扫描分析,发现可能存在的问题,比如变量未定义、类型不匹配、变量作用域问题、数组下标越界、内存泄露等问题 。
既然是静态,那么就不是运行时 。但是是编译前还是编译后还是编译中?
其实都有,像商业软件“啄木鸟”是给源文件就行,然后它会在编译的过程中去检测语法,词法,以及最后生成的二进制 。
代码静态分析(SAST):可以简单的理解为在不执行程序的情况下,对源代码, 中间代码或者二进制代码进行分析的技术