—内存泄漏—
内存泄漏是C++程序中最常见也是经常出现的问题,必须重视
内存泄漏 —- 没有即时释放不再使用的内存空间,导致一直占用内存空间,并且不能使用
如果程序结束,对于现代的操作系统来说,泄露的内存会被操作系统自动释放,叫内存自动回收。
如果程序存在内存泄漏,但恰好运行的操作系统可以帮你自动释放,那么短时间运行没问题。但是要考虑移植到另一个没有内存自动回收功能的操作系统的情况。
但是对于C++后台服务端的守护进程来说,如果存在内存泄漏,日积月累会造成严重的问题,程序会崩溃,操作系统的性能和稳定性也会受到影响。
因此 预防内存泄漏 以及 代码写完后内存泄漏的检测 是十分有必要的。
1. 出现内存泄漏的原因
1.开发人员经验不足、意识不到位或一时疏忽导致;没有将new和delete或者 malloc与free 成对使用。
2.不熟悉一些接口。可能有些接口需要手动释放内存。
3.创建线程后,没有及时回收已经退出的线程
2. 解决内存泄漏的方法
从简单到复杂的程序对应着不同的解决方案。
2.1 养成良好的编码习惯
写代码时,在函数中看到有局部指针,就要警惕内存泄漏问题,养成进一步排查的习惯。
2.2 使用智能指针代替裸指针
头文件 #include
int main()
{
shared_ptr<int> sp1 = make_shared<int>(100); // 共享指针
shared_ptr<int> p1(new int(1));
unique_ptr<int> uptr(new int(10)); //绑定动态对象 //独占指针
//std::unique_ptr<int> uptr2 = uptr; //不能赋值
//std::unique_ptr<int> uptr2(uptr); //不能拷贝
unique_ptr<int> uptr2 = std::move(uptr); //转换所有权
uptr = nullptr;
uptr.release(); //释放所有权
return 0;
}
2.3 重载 operator new()函数
稍微复杂的单文件程序可以重写malloc和free函数或者重写new和delete关键字中的operator new 和operator delete,一般来说都是重载 operator new。将申请内存或者释放内存的信息打印在屏幕上或者文件中。
自己编写了一个内存泄漏的的小demo,仅限简单的单线程的线程判断。
思路:用双向链表作为保存申请内存的数据结构,运用头插法进行内存数据管理。
知识要点:宏定义;双向链表的增删;重载operator new/delete;单例模式;
缺点:不支持多线程
memleak.h
#ifndef MEM_LEAK_
#define MEM_LEAK_
#include <iostream>
using namespace std;
#include <string>
void* operator new(size_t size, const char* file, size_t line);
void* operator new[](size_t size, const char* file, size_t line);
void operator delete(void* ptr);
void operator delete[](void* ptr);
#ifndef OVERLOAD_NEW_
#define new new(__FILE__,__LINE__)
#endif // !OVERLOAD_NEW_
//静态全局 在整个程序中只需存在一次。用来检测内存泄漏
class memleak
{
public:
static size_t callcount; //静态变量, 在类外进行初始化
memleak() { ++callcount; }
~memleak() { if (0 == --callcount) _memleak(); }
private:
void _memleak();
};
static memleak memLeak;
#endif // !MEM_LEAK_
memleak.cpp
#define OVERLOAD_NEW_
#define _CRT_SECURE_NO_WARNINGS
#include "memleak.h"
size_t memleak::callcount = 0; //静态变量, 在类外进行初始化
static size_t allocSize = 0; //用来记录泄漏大小
//链表数据结构 --双向链表
struct LinkNode {
LinkNode* _prev;
LinkNode* _next;
size_t _size; //大小
size_t _line; //行数
char* _file; //文件
bool _isArray; //是否为数组
};
//初始化头结点
LinkNode linkHead = { &linkHead,&linkHead,0,0,NULL,false };
void* allocMem(size_t size, const char* file, size_t line, bool isArray)
{
//计算总大小
size_t totalSize = size + sizeof(LinkNode);
//开辟空间
LinkNode* newMem = (LinkNode*)malloc(totalSize);
//连在数据结构链表上 头插
newMem->_prev = &linkHead;
newMem->_next = linkHead._next;
linkHead._next->_prev = newMem;
linkHead._next = newMem;
//外加初始化数据
newMem->_size = size; //实际开辟大小
newMem->_line = line;
//如果存在文件 开辟文件空间
if (NULL != newMem->_file)
{
newMem->_file = (char*)malloc(strlen(file) + 1);
strcpy(newMem->_file, file);
}
else
newMem->_file = NULL;
newMem->_isArray = isArray;
allocSize += size;
return (char*)newMem + sizeof(linkHead);
}
void releaseMem(void* ptr,bool isArray)
{
LinkNode* newMem = (LinkNode*)( (char*)ptr - sizeof(LinkNode) );
if (newMem->_isArray != isArray)
{
cout << "delete or delete[] error !" << endl;
return;
}
//链表删除操作
newMem->_next->_prev = newMem->_prev;
newMem->_prev->_next = newMem->_next;
allocSize -= newMem->_size;
if (NULL != newMem->_file)
free(newMem->_file);
free(newMem);
return;
}
//重载
void* operator new(size_t size, const char* file, size_t line )
{
return allocMem(size, file, line, false);
}
void* operator new[](size_t size, const char* file, size_t line)
{
return allocMem(size, file, line, true);
}
void operator delete(void* ptr)
{
return releaseMem(ptr,false);
}
void operator delete[](void* ptr)
{
return releaseMem(ptr, true);
}
//泄漏检测
void memleak::_memleak()
{
//如果没有发生泄漏
if (0 == allocSize)
{
cout << "恭喜你,内存没有发生泄漏" << endl;
system("pause");
return;
}
LinkNode* ptr = linkHead._next;
size_t count = 0;
while ((NULL != ptr) && (&linkHead != ptr))
{
if (ptr->_isArray == true)
cout << "new[]泄漏,";
else
cout << "new泄漏,";
if (NULL != ptr->_file)
cout << "位于第 " << ptr->_line << " 行,泄漏大小为 " << ptr->_size << "k" << endl;
else
cout << "无文件信息" << endl;
++count;
ptr = ptr->_next;
}
cout << "一共有 " << count << " 处发生内存泄漏,泄漏总大小为" << allocSize << "k" << endl;
system("pause");
return;
}
main.cpp
测试案例
#include <iostream>
using namespace std;
#include "memleak.h"
#include <vector>
int main()
{
vector<int>* vec = new vector<int>(5);
int* a = new int ;
int* b = new int[10];
//delete vec;
delete a;
delete[] b;
return 0;
}
2.4 valgrind内存泄漏工具
比较隐蔽或者代码量很大的程序可以借助工具valgrind(linux平台)
memcheck模块
valgrind --leak-check=full ./测试代码
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 351134995@qq.com