C语言|简易的内存跟踪实现

前言:

由于懒得去找内存泄漏的地方,于是在被Segment Fault折磨了16分钟后我整出了一个简易的内存跟踪实现。

正文:

不要分配 0 字节的内存,这会导致内存跟踪出错!
内存跟踪是全局的。
这个实现不是线程安全的,如果要保证线程安全,请在适当的地方加锁。

引入必要的库

#include <stdlib.h>
#include <string.h>

结构体和全局变量

记录跟踪的指针的结构体

这是一个结构体,里面存放着一个记录分配的指针和其指向的内存(块)的大小。

// Record Pointer.
typedef struct _PTR_RECODE {
    void* ptr;
    unsigned long size;
} PTR_RECODE;

(private)全局的跟踪数据

定义两个全局变量

// Record Array
INTERFACE_TYPE PTR_RECODE* recoder = 0x0;
INTERFACE_TYPE unsigned long recode_num = 0;

recoder (拼写错了,应该是recorder),可以看作跟踪的指针的列表。
recode_num,即当前recorder最多能够跟踪的指针数量(即分配的PTR_RECODE的数量)。
(不是当前跟踪的指针数量)

这里INTERFACE_TYPE是我在debug时使用的一个宏,即static

内存跟踪的一些函数

初始化内存跟踪

在使用前预先分配一小点的内存。

// Init Record Array. Default allocate 4 recorder.
void InitMemFollow(void){
    recode_num = 4;
    recoder = calloc(recode_num, sizeof(PTR_RECODE));
}

统计内存总分配大小

统计跟踪的内存总大小。

// Get Used Memory.
unsigned long GetMemFollowUsed(void){
    unsigned long mem_used = 0;
    for(unsigned long i = 0;i < recode_num; ++i){
        mem_used += recoder[i].size;
    }
    return mem_used;
}

释放所有内存并返回内存泄漏信息

在程序结束时释放所有内存,并且返回泄漏的内存总量和指针数量。
这里通过一个结构体把返回值包装一下。

// Leak Pointer Num And Leaked Memory Size
typedef struct _LEAK_INFO {
    unsigned long leak_ptr_num;
    unsigned long total_leaked_memory;
} LEAK_INFO;

// Delete Record Array and Return The Leaked Memory.
LEAK_INFO DelMemoryFollow(void){
    unsigned long mem_used = GetMemFollowUsed();
    LEAK_INFO li = {};
    // Free Leaked Memory.
    if(mem_used){
        for(unsigned long i = 0;i < recode_num; ++i){
            if(recoder[i].ptr) {
                ++li.leak_ptr_num;
                free(recoder[i].ptr);
            }
        }
    }
    free(recoder);
    li.total_leaked_memory = mem_used;
    return li;
}

如果还需要跟踪则需再次调用InitMemFollow函数。

(private)增加可跟踪指针的数量

在需要的时候增加可跟踪指针的数量。
这是一个私有方法。
当失败时,它不会做任何事情。这意味着必须检查recode_num来判断其执行的成功与否。

开辟的内存大小在这里设置为大约原先的1.25倍。
(为什么说大约呢,因为如果原先分配的跟踪数量除以4小于1时,新分配的就跟原先的一样了,故还要加上1。)
当需要跟踪新的指针且没有空的PTR_RECODE时,便开辟一块更大的内存,将原先的跟踪数据搬过去。(我尝试用realloc,但是总有问题,不知道怎么回事,求解。)

#if !defined MEM_FOLLOW_RESIZE_NUM
#define MEM_FOLLOW_RESIZE_NUM 4
#endif

// When no empty recorder in the array, reallocate them.
// 「If Failed, It Will Do Nothing.」
INTERFACE_TYPE void _MoreRecorder(void){
    unsigned long more_memory = recode_num / MEM_FOLLOW_RESIZE_NUM + 1;
    void* t_ptr = calloc(recode_num + more_memory, sizeof(PTR_RECODE));
    if(t_ptr) {
        // Copy Data
        memcpy(t_ptr, recoder, recode_num * sizeof(PTR_RECODE));
        // Save Size.
        recode_num += more_memory;
        // Move Pointer
        free(recoder);
        recoder = t_ptr;
    }
}

接下来是内存的分配,再分配和释放了。

内存分配,重新分配和释放

分配内存

分为两部分。

  • 第一部分是寻找空的跟踪记录。
  • 第二部分是当没有空的跟踪记录时扩大跟踪记录的内存大小。
    失败返回NULL
// Allocate Memory Which Will Be Recorded.
void* mfalloc(unsigned long size){
    // Find an empty recorder.
    for(unsigned long i = 0;i < recode_num;++i){
        if(recoder[i].size == 0){
            void* ret = calloc(size, 1);
            recoder[i].size = size;
            recoder[i].ptr = ret;
            return ret;
        }
    }
    // Failed.
    // Reallocate Them.
    unsigned long old_size = recode_num;
    _MoreRecorder();
    if(old_size < recode_num){ // Success to reallocate.
        for(unsigned long i = old_size;i < recode_num;++i){
            if(recoder[i].size == 0){
                void* ret = calloc(size, 1);
                recoder[i].size = size;
                recoder[i].ptr = ret;
                return ret;
            }
        }
    }
    // Failed.
    return 0x0;
}

释放内存

这个简单,找到对应的记录删除并且释放内存就行了。
重复释放内存时不会做任何事情

// Free Memory Which Is Recorded
void mffree(void* ptr){
    for(unsigned long i = 0;i < recode_num;++i){
        if(recoder[i].ptr == ptr){
            free(ptr);
            // Clear Recorder.
            recoder[i].ptr = 0x0;
            recoder[i].size = 0;
            return;
        }
    }
}

重新分配内存

当待重新分配内存的指针为NULL或不在跟踪记录中时,其会直接分配内存。

// Reallocate Memory.
// If the pointer is NULL or not in Record Array, It will allocate a new memory.
// 「DO NOT SET THE SIZE TO ZERO, OR THE MemoryFollow WILL GO WRONG.」
void* mfrealloc(void* ptr, unsigned long size){
    // Allocate Memory.
    void* t_ptr = mfalloc(size);
    // Find The Recorder.
    if(t_ptr){
        for(unsigned long i = 0;i < recode_num; ++i){
            if(recoder[i].ptr == ptr){
                memcpy(t_ptr, recoder[i].ptr, size < recoder[i].size ? size : recoder[i].size);
                mffree(recoder[i].ptr);
            }
        }
    }
    return t_ptr;
}

这里我依旧没有使用realloc,原因同上。

杂项

获取跟踪记录所占的内存大小

// Get Record Array Size
unsigned long GetRecordSize(void){
    return recode_num * sizeof(PTR_RECODE);
}

测试

测试代码

测试代码如下。(请自行修改头文件地址

一共5个指针(a, b, c, d, e)

  1. 首先给a, b, c分配内存。(测试mfalloc)
  2. 然后释放b的内存。(测试mffree)
  3. b, c, d, e分配内存。(测试_MoreRecorder
  4. 释放b, c, d, e
  5. 重新给a分配内存。(测试mfrealloc
  6. 最后结束程序。(a没有被释放)
#include <stdio.h>

#include "../core/memfollow.h" // 请自行修改头文件地址

int main(void){
    InitMemFollow();

    printf("%lu Used.\n", GetMemFollowUsed());

    void* a = mfalloc(10);

    void* b = mfalloc(20);

    void* c = mfalloc(30);

    printf("%lu Used.(alloc a,b,c)\n", GetMemFollowUsed());

    mffree(b);

    printf("%lu Used.(free b)\n", GetMemFollowUsed());

    b = mfalloc(40);

    void* d = mfalloc(50);

    void* e = mfalloc(60);

    printf("%lu Used.(alloc b,d,e)\n", GetMemFollowUsed());

    mffree(b);

    mffree(c);

    mffree(d);

    mffree(e);

    printf("%lu Used.(free b,c,d,e)\n", GetMemFollowUsed());

    a = mfrealloc(a, 20);

    printf("%lu Used.(realloc a)\n", GetMemFollowUsed());

    printf("Now MF Used %lu Byte(s) Memory\n", GetRecordSize());
    LEAK_INFO leaked = DelMemoryFollow();
    printf("Leak %lu Byte(s) Memory.(%lu Pointer(s))\n", leaked.total_leaked_memory, leaked.leak_ptr_num);
    return 0;
}

测试结果

测试结果如下:
结果
截屏
测试环境:Windows10, MinGW-w64 GCC 11.4.0


代码贴这里了

/*
MIT License

Copyright (c) 2024 Qiong-Mengzi

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/

/*
    @author: Qiong-Mengzi
    跟踪内存查看内存泄漏的(然并卵)
    请忽略奇怪的拼写错误
*/

//#define MF_DEBUG

#ifdef MF_DEBUG
#define INTERFACE_TYPE extern
#else
#define INTERFACE_TYPE static
#endif

#ifndef __MEMORY_FOLLOWING__
#define __MEMORY_FOLLOWING__

#include <stdlib.h>
#include <string.h>

#if !defined MEM_FOLLOW_RESIZE_NUM
#define MEM_FOLLOW_RESIZE_NUM 4
#endif

// Record Pointer.
typedef struct _PTR_RECODE {
    void* ptr;
    unsigned long size;
} PTR_RECODE;

// Leak Pointer Num And Leaked Memory Size
typedef struct _LEAK_INFO {
    unsigned long leak_ptr_num;
    unsigned long total_leaked_memory;
} LEAK_INFO;

// Record Array
INTERFACE_TYPE PTR_RECODE* recoder = 0x0;
INTERFACE_TYPE unsigned long recode_num = 0;

// Init Record Array. Default allocate 4 recorder.
void InitMemFollow(void){
    recode_num = 4;
    recoder = calloc(recode_num, sizeof(PTR_RECODE));
}

// Get Used Memory.
unsigned long GetMemFollowUsed(void){
    unsigned long mem_used = 0;
    for(unsigned long i = 0;i < recode_num; ++i){
        mem_used += recoder[i].size;
    }
    return mem_used;
}

// When no empty recorder in the array, reallocate them.
// 「If Failed, It Will Do Nothing.」
INTERFACE_TYPE void _MoreRecorder(void){
    unsigned long more_memory = recode_num / MEM_FOLLOW_RESIZE_NUM + 1;
    void* t_ptr = calloc(recode_num + more_memory, sizeof(PTR_RECODE));
    if(t_ptr) {
        // Copy Data
        memcpy(t_ptr, recoder, recode_num * sizeof(PTR_RECODE));
        // Save Size.
        recode_num += more_memory;
        // Move Pointer
        free(recoder);
        recoder = t_ptr;
    }
}

// Delete Record Array and Return The Leaked Memory.
LEAK_INFO DelMemoryFollow(void){
    unsigned long mem_used = GetMemFollowUsed();
    LEAK_INFO li = {};
    // Free Leaked Memory.
    if(mem_used){
        for(unsigned long i = 0;i < recode_num; ++i){
            if(recoder[i].ptr) {
                ++li.leak_ptr_num;
                free(recoder[i].ptr);
            }
        }
    }
    free(recoder);
    li.total_leaked_memory = mem_used;
    return li;
}

// Get Record Array Size
unsigned long GetRecordSize(void){
    return recode_num * sizeof(PTR_RECODE);
}

// Allocate Memory Which Will Be Recorded.
void* mfalloc(unsigned long size){
    // Find an empty recorder.
    for(unsigned long i = 0;i < recode_num;++i){
        if(recoder[i].size == 0){
            void* ret = calloc(size, 1);
            recoder[i].size = size;
            recoder[i].ptr = ret;
            return ret;
        }
    }
    // Failed.
    // Reallocate Them.
    unsigned long old_size = recode_num;
    _MoreRecorder();
    if(old_size < recode_num){ // Success to reallocate.
        for(unsigned long i = old_size;i < recode_num;++i){
            if(recoder[i].size == 0){
                void* ret = calloc(size, 1);
                recoder[i].size = size;
                recoder[i].ptr = ret;
                return ret;
            }
        }
    }
    // Failed.
    return 0x0;
}

// Free Memory Which Is Recorded
void mffree(void* ptr){
    for(unsigned long i = 0;i < recode_num;++i){
        if(recoder[i].ptr == ptr){
            free(ptr);
            // Clear Recorder.
            recoder[i].ptr = 0x0;
            recoder[i].size = 0;
            return;
        }
    }
}

// Reallocate Memory.
// If the pointer is NULL or not in Record Array, It will allocate a new memory.
// 「DO NOT SET THE SIZE TO ZERO, OR THE MemoryFollow WILL GO WRONG.」
void* mfrealloc(void* ptr, unsigned long size){
    // Allocate Memory.
    void* t_ptr = mfalloc(size);
    // Find The Recorder.
    if(t_ptr){
        for(unsigned long i = 0;i < recode_num; ++i){
            if(recoder[i].ptr == ptr){
                memcpy(t_ptr, recoder[i].ptr, size < recoder[i].size ? size : recoder[i].size);
                mffree(recoder[i].ptr);
            }
        }
    }
    return t_ptr;
}

#endif

代码附属于我的一个项目。待项目的框架形成后会将项目地址放出来的。

代码还没有优化得很好(比如运行久了只能重置跟踪记录来释放内存),仍有许多待完善之处。

EOF

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值