内存泄漏


—内存泄漏—

​ 内存泄漏是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

×

喜欢就点赞,疼爱就打赏