上一篇文章学习了malloc系列的三个函数的使用。众所周知malloc的使用很容易导致内存泄漏。本文的目的就是使用C语言来实现内存泄漏检测模块,来帮忙自动检测我们写的程序中是否出现内存泄露。

1 内存泄露检测模块的实现原理

想要实现内存泄露检测模块,必须在内存申请的时候将申请的内存位置,在代码的哪个位置写代码申请的,以及在哪个源文件中申请的这些信息都记录下来,然后如果申请的内存没有被释放,那就最终将这些没有释放的内存的信息打印出来,我们才能够定位到内存泄露的地方。

可以采用数组记录申请的内存的信息,数组的每个元素存的是申请过的内存的信息。因为信息比较多,所以数组的元素采用结构体的形式。

采用数组的话,申请内存时遍历数组的元素,将申请的内存信息记录到数组中,释放内存的时候遍历数组查找要释放的内存信息对应哪个元素。最后还需要遍历数组查看是否数组中还残留有没有释放的内存由的话,就将***存信息打印出来,方便查找。

1.1 各个函数模块的设计

先给出各个小部分函数的设计思想。最后给出整个代码的整合,会让你豁然开朗。可以结合后面的整个代码来看。

  1. 首先定义内存信息的结构体,这个结构体信息可以让我们知道申请的内存的地址,申请的代码在哪个源文件以及哪一行,以及申请的内存的字节大小。如下:
typedef struct{
    void* pointer; //指针
    int size;      /大小
    const char* file;   //文件名
    int line;     //行号
}MemoryInfo;
  1. 然后定义存放申请到的内存信息的结构体数组。该结构体时用于我们存放申请到的内存信息,以及在最后查找是否有内存没有释放用的:
#define SIZE 256

static MemoryInfo M_array[SIZE];
  1. 申请内存的函数设计:标准库函数malloc无法检测内存泄漏,所以我们对malloc进行封装。在申请内存的时候,将申请到的内存信息记录到M_array数组中。参数n是代表申请多少字节内存。file代表当前在哪个源文件中使用了内存申请的函数。参数line是代表在该源文件的哪一行进行了内存申请的动作。
void* mallocEX(size_t n, const char* file, int line){
    
    void* ret = malloc(n);
    
    if(ret!=NULL){
        int i = 0;
        
        for(i=0; i<SIZE; i++){
            if(M_array[i].pointer==NULL){
                M_array[i].pointer = ret;
                M_array[i].size = n;
                M_array[i].file = file;
                M_array[i].line = line;
                
                break;
            }
            
        }
    }
    return ret;
}

可以看到该函数中将申请到的内存信息都记录到了M_array数组中。方便后续的查询信息。

  1. 释放内存函数设计:释放内存的函数中,必须先遍历数组M_array ,查找要释放的内存在数组中的记录索引,在释放内存的时候,同时将数组中存放该内存的信息置空,代表该内存已经被释放:
void freeEX(void* p){
    if(p!=NULL){
        int i = 0;
        
        for(i=0; i<SIZE; i++){
            if(M_array[i].pointer == p){
                M_array[i].pointer = NULL;
                M_array[i].size = 0;
                M_array[i].file = NULL;
                M_array[i].line = 0;
                
                free(p);
                break;
            }            
        }
    }
}
  1. 查询未释放的内存信息:在程序的结束,即将退出之前,需要查询当前的程序中是否有未释放的内存,如果有将该信息打印出来方便程序员定位修改。直接遍历数组M_array,只要有元素非空,说明就有内存没有被释放:
void PRINT_LEAK_INFO(){
    int i = 0;
    printf("Potential Memory Leak Info: \n");
    
    for(i=0; i<SIZE; i++){
        if(M_array[i].pointer != NULL){
            printf("Address: %p, size:%d, Location: %s:%d\n", M_array[i].pointer, M_array[i].size, M_array[i].file, M_array[i].line);
        }
    }
}

1.2 模块整合

上面的几个函数的设计,可以实现内存泄漏检测。将整个程序整合为下面的三个代码:

  • 38-1-lyy.c
#include <stdio.h>
#include "lyy_mleak.h"

void f(){
    MALLOC(100); //申请内存,没有释放 
}

int main(){

    int* p = (int*)MALLOC(3*sizeof(int));
    
    f();     //这里发生内存泄漏
    
    p[0] = 0;
    p[1] = 1;
    p[2] = 2;
    
    FREE(p);
    
    PRINT_LEAK_INFO();
    
    return 0;
}
  • lyy_mleak.h

#ifndef _LYY_MLEAK_H
#define _LYY_MLEAK_H

#include <malloc.h>

#define MALLOC(n) mallocEX(n, __FILE__, __LINE__)
#define FREE(p) freeEX(p)

void* mallocEX(size_t n, const char* file, const int line);
void freeEX(void* p);
void PRINT_LEAK_INFO();

#endif
  • lyy_mleak.c
#include "lyy_mleak.h"

#define SIZE 256

typedef struct{
    void* pointer;
    int size;
    const char* file;
    int line;
}MemoryInfo;

static MemoryInfo M_array[SIZE];

void* mallocEX(size_t n, const char* file, int line){
    
    void* ret = malloc(n);
    
    if(ret!=NULL){
        int i = 0;
        
        for(i=0; i<SIZE; i++){
            if(M_array[i].pointer==NULL){
                M_array[i].pointer = ret;
                M_array[i].size = n;
                M_array[i].file = file;
                M_array[i].line = line;
                
                break;
            }
            
        }
    }
    return ret;
}

void freeEX(void* p){
    if(p!=NULL){
        int i = 0;
        
        for(i=0; i<SIZE; i++){
            if(M_array[i].pointer == p){
                M_array[i].pointer = NULL;
                M_array[i].size = 0;
                M_array[i].file = NULL;
                M_array[i].line = 0;
                
                free(p);
                break;
            }            
        }
    }
}

void PRINT_LEAK_INFO(){
    int i = 0;
    printf("Potential Memory Leak Info: \n");
    
    for(i=0; i<SIZE; i++){
        if(M_array[i].pointer != NULL){
            printf("Address: %p, size:%d, Location: %s:%d\n", M_array[i].pointer, M_array[i].size, M_array[i].file, M_array[i].line);
        }
    }
}
  • 编译运行上述代码:gcc 38-1-lyy.c lyy_mleak.c -o test.out
  • ./test.out

可以看到,运行程序后,自动的为我们检测是否有未释放的内存,并且将未释放的内存信息打印出来供我们定位修改。只需要将函数f() 修改为以下即可:

void f(){
    int* p = MALLOC(100); //申请内存,没有释放 
	......
	FREE(p);
}

这样修改过后,就不会出现内存泄漏了。

2 总结

  • malloc和free一定要成对出现