【内存泄露/C++】基于重载new运算符的内存泄露检测工具

来源:互联网 发布:色系shipin软件下载 编辑:程序博客网 时间:2024/06/05 06:54

···检测内存泄露的方法有很多,这里使用的是重载系统的new运算符和delete运算符。从而达到,在new的时候记录申请内存的大小,和代码所属文件和行号,后两个信息主要是为了调试。在程序结束之前,访问保存的这些信息,判断内存是否泄漏。
···这里使用的存储结构是哈希表,结构如下:

typedef struct MemNode//记录new时保存的信息{    int size;    char* file;    int line;}MemNode;#define BUCKET_NUM 3typedef struct hash_node{    MemNode* bucket[BUCKET_NUM];//桶结点    hash_node* link;}hash_node;

···我们要检测到内存泄露,肯定要在程序结束的时候判断。这里指的程序结束,不是main函数的最后一句,而是所有的对象析构完的时候。我们知道,对象析构的顺序是先声明的最后释放,我们利用这个特性,可以声明一个全局的对象,在他的析构函数里判断内存泄露:

class VLD{public:    VLD()    {        creatHashtable(ht); //初始化哈希表        hash_table_size = 0;    }    ~VLD()    {        check_vld();//检测    }};

···重载new运算符的函数原型为:

void* operator new(size_t sz ,char* filename, int line);

···这里重载的是operator new 另外new操作符还有两种重载方式,

    void* operator new(size_t sz ,char* filename, int line){       void* result;    hash_table_size++;    int totle_size = sz + sizeof(MemNode);    MemNode* s = (MemNode*)malloc(totle_size);    s->file = filename;    s->line = line;    s->size = sz;    result = s + 1;    int index = Hash(result);    return result;}

···很容易就写出了重载的new函数,但是,我们并没有把存储信息的结点放入哈希表中,放在哈希表是为了delete的时候快速找到,当然,用链表存储也是可以的,只不过效率略低。
这里写图片描述

···上图是存储信息的数据结构示意图,和一般的哈希表不一样的是,哈希表data域放的是MemNode结构体指针,每个结点可以放BUCKET_NUM 个MemNode结构体指针,这样,完整的new函数如下:

void* operator new(size_t sz ,char* filename, int line){    void* result;    hash_table_size++;    int totle_size = sz + sizeof(MemNode);    MemNode* s = (MemNode*)malloc(totle_size);    s->file = filename;    s->line = line;    s->size = sz;    result = s + 1;    int index = Hash(result);//为生成的Memnode寻找插入点    for (int l = 0; l < BUCKET_NUM; l++)        if (ht[index].bucket[l] == NULL)        {            ht[index].bucket[l] = s;            return result;        }    hash_node* prev = ht + index;    while (prev->link != NULL)    {        for (int l = 0; l < BUCKET_NUM; l++)            if (prev->link->bucket[l] == NULL)            {                prev->link->bucket[l] = s;                return result;            }        prev = prev->link;    }    hash_node* t = (hash_node*)malloc(sizeof(hash_node));    inithashnode(t);    t->bucket[0] = s;    prev->link = t;    return result;}

···代码略长,如果是用链表存储的,代码可以少很多,但是查找的时候就没hash表快了。用哈希表存,主要是考虑到链表查找性能不高。

···重载的delete函数如下:

void operator delete(void* p){    int index = Hash(p);    hash_node* s = ht + index;    while (s != NULL)    {        for (int l = 0; l < BUCKET_NUM; l++)            if (s->bucket[l] + 1 == p)            {                free(s->bucket[l]);                s->bucket[l] = NULL;                hash_table_size--;                return;            }        s = s->link;    }}

···如果这时候我们要使用自己的重载函数,要解决的第一点就是,所属文件和行号怎么传递,如果在new的时候这样写:

int *p = new(__FILE__, __LINE__) int(1);

···这未必太繁琐了,怎样很好的解决呢,这里我们参考C标准中_DEBUG中的写法,定义一个宏:

    #define new new(__FILE__,__LINE__)

···这样你写的new int(1),就会自动转为new(FILE,LINE) int(1),继而转到我们的函数。这样做的缺陷很大,会在文章的最后面写出。

···我们再给出检查内存泄露的代码:

void check_vld(){    if (hash_table_size == 0)    {        cout << "No memory leaks detected" << endl;        OutputDebugStringA("No memory leaks detected\n");        return;    }    cout << "                          WARNING : Memory leak" << endl;    OutputDebugStringA("                          WARNING : Memory leak\n");    cout << "-----------------------------------------------------------------------------------------" << endl;    OutputDebugStringA("---------------------------------------------------------------------------------------- - \n");    int count = 0, allsize = 0;    char str[100] = { 0 };    for (int i = 0; i < P; i++)    {        hash_node* s = ht + i;        while (s)        {            for (int l = 0; l < BUCKET_NUM; l++)            {                if (s->bucket[l] != NULL)                {                    count++;                    allsize += s->bucket[l]->size;                                          sprintf_s(str, "FILE:%s\n", s->bucket[l]->file);                    OutputDebugStringA(str);                    sprintf_s(str, "line:%d\n", s->bucket[l]->line);                    OutputDebugStringA(str);                    sprintf_s(str, "At:%p  Size:%d\n", s->bucket[l] + 1, s->bucket[l]->size);                    OutputDebugStringA(str);                    OutputDebugStringA("-----------------------------------------------------------------------------------------\n");#ifdef COUT2APP                    cout << "FILE:" << s->bucket[l]->file << endl;                    cout << "LINE:" << s->bucket[l]->line << endl;                    cout << "At:" << s->bucket[l] + 1 << "   Size:" << s->bucket[l]->size << endl;                    cout << "-----------------------------------------------------------------------------------------" << endl;    #endif                }            }            s = s->link;        }    }    sprintf_s(str, "Count: %d, Total %d \n", count, allsize);    OutputDebugStringA(str);}

···不要觉得他很长,,,其实全是在控制输入的格式。核心就是判断我们的hash_table是否空,不空则遍历里面的所有元素,说白了,和平时写的show函数异曲同工。

····这样可以算完了吗?当然,还差一步。我们来看这个例子:

void main() {    int* p = new int[20];    delete p;}

这里写图片描述

····在调试窗口显示无内存泄露,这个很好理解,由于int是内置类型,缺少析构函数,就算不用delete【】这样的写法也没关系。···

····但是:

void* operator new[](size_t sz, char* filename, int line){    return operator new(sz, filename, line);}void operator delete[](void* p){    operator delete(p);}

加上之后,用delete【】 释放:
这里写图片描述

···至此,所有功能函数全部写完。
···其实,很明显的一个缺点,就是把new定义为了宏
···这样做的坏处就是,引入了自己的内存检测头文件,就只能使用最简单的new操作符了,连定位 new 都无法编译通过。

最后,附上完整代码:

#include <corecrt_malloc.h>#include <iostream>#include<Windows.h>#include<stdio.h>#ifndef _VLD_H_#define _VLD_H_using namespace std;#define BUCKET_NUM 3#define P 13//1 4 8 12 16 20 24typedef struct MemNode{    int size;    char* file;    int line;}MemNode;typedef struct hash_node{    MemNode* bucket[BUCKET_NUM];    hash_node* link;}hash_node;typedef hash_node hash_table[P];static hash_table ht;static int hash_table_size;void creatHashtable(hash_table& ht){    for (int i = 0; i < P; i++)    {        for (int l = 0; l < BUCKET_NUM; l++)            ht[i].bucket[l] = NULL;        ht[i].link = NULL;    }}void inithashnode(hash_node*& t){    t->link = NULL;    for (int l = 0; l < BUCKET_NUM; l++)        t->bucket[l] = NULL;}int Hash(void* point){    return (int)point % P;}void* operator new(size_t sz ,char* filename, int line){    void* result;    hash_table_size++;    int totle_size = sz + sizeof(MemNode);    MemNode* s = (MemNode*)malloc(totle_size);    s->file = filename;    s->line = line;    s->size = sz;    result = s + 1;    int index = Hash(result);    for (int l = 0; l < BUCKET_NUM; l++)        if (ht[index].bucket[l] == NULL)        {            ht[index].bucket[l] = s;            return result;        }    hash_node* prev = ht + index;    while (prev->link != NULL)    {        for (int l = 0; l < BUCKET_NUM; l++)            if (prev->link->bucket[l] == NULL)            {                prev->link->bucket[l] = s;                return result;            }        prev = prev->link;    }    hash_node* t = (hash_node*)malloc(sizeof(hash_node));    inithashnode(t);    t->bucket[0] = s;    prev->link = t;    return result;}void operator delete(void* p){    int index = Hash(p);    hash_node* s = ht + index;    while (s != NULL)    {        for (int l = 0; l < BUCKET_NUM; l++)            if (s->bucket[l] + 1 == p)            {                free(s->bucket[l]);                s->bucket[l] = NULL;                hash_table_size--;                return;            }        s = s->link;    }}void* operator new[](size_t sz, char* filename, int line){    return operator new(sz, filename, line);}void operator delete[](void* p){    operator delete(p);}void check_vld(){    if (hash_table_size == 0)    {        cout << "No memory leaks detected" << endl;        OutputDebugStringA("No memory leaks detected\n");        return;    }    cout << "                          WARNING : Memory leak" << endl;    OutputDebugStringA("                          WARNING : Memory leak\n");    cout << "-----------------------------------------------------------------------------------------" << endl;    OutputDebugStringA("---------------------------------------------------------------------------------------- - \n");    int count = 0, allsize = 0;    char str[100] = { 0 };    for (int i = 0; i < P; i++)    {        hash_node* s = ht + i;        while (s)        {            for (int l = 0; l < BUCKET_NUM; l++)            {                if (s->bucket[l] != NULL)                {                    count++;                    allsize += s->bucket[l]->size;                                          sprintf_s(str, "FILE:%s\n", s->bucket[l]->file);                    OutputDebugStringA(str);                    sprintf_s(str, "line:%d\n", s->bucket[l]->line);                    OutputDebugStringA(str);                    sprintf_s(str, "At:%p  Size:%d\n", s->bucket[l] + 1, s->bucket[l]->size);                    OutputDebugStringA(str);                    OutputDebugStringA("-----------------------------------------------------------------------------------------\n");#ifdef COUT2APP                    cout << "FILE:" << s->bucket[l]->file << endl;                    cout << "LINE:" << s->bucket[l]->line << endl;                    cout << "At:" << s->bucket[l] + 1 << "   Size:" << s->bucket[l]->size << endl;                    cout << "-----------------------------------------------------------------------------------------" << endl;    #endif                }            }            s = s->link;        }    }    sprintf_s(str, "Count: %d, Total %d \n", count, allsize);    OutputDebugStringA(str);}class VLD{public:    VLD()    {        creatHashtable(ht);        hash_table_size = 0;    }    ~VLD()    {        check_vld();    }};#ifdef _DEBUG    VLD vldtest;    #define new new(__FILE__,__LINE__)    #define COUT2APP#endif#endif
1 0
原创粉丝点击