【C/C++】内存泄漏检测(不借助工具)

一、内存泄漏是什么

        简单来说你 new 或者 malloc 一块内存后忘记 delete 或者 free 了,或者把地址丢失了导致无法进行释放。

以产生的方式来分类,内存泄漏可以分为四类:

  1. 常发性内存泄漏:发生内存泄漏的代码会被多次执行到,每次被执行时都会导致一块内存泄漏。

  2. 偶发性内存泄漏:发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。

  3. 一次性内存泄漏:发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅有一块内存发生泄漏。

  4. 隐式内存泄漏:程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。

二、如何检查

        内存泄漏检查对于经常在 Windows 系统或者 Linux 系统下编程的人员来说会相对来说比较容易,有各种各样的工具能够用来检查内存泄漏,但对于嵌入式工程师来说就非常麻烦了,因为也没有什么工具能够检查。这里提供一个不需要依赖工具,只需要添加几个代码文件即可检查出哪里发生了内存泄漏的代码。

1、my_malloc.c

#include "my_malloc.h"

static MY_ITEM g_record[SIZE];//如果存在多线程的情况一定要注意互斥操作!!!

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

void my_free(void* p)
{
    if (p != NULL)
    {
        int i = 0;
        for (i = 0; i < SIZE; i++)
        {
            if (g_record[i].pointer == p)
            {
                g_record[i].pointer = NULL;
                g_record[i].size = 0;
                g_record[i].file = NULL;
                g_record[i].line = 0;

                free(p);
                break;
            }
        }
    }
}

void print_dynamic_memory_info(void)
{
    int i = 0;
    printf("动态内存申请尚未释放的如下:\n");
    printf("%-20s\t%-10s\t%s\n", "地址", "地址大小", "申请位置");

    for (i = 0; i < SIZE; i++)
    {
        if (g_record[i].pointer != NULL)
        {
            printf("%-20p\t%-10d\t%s:%d 行\n",
                g_record[i].pointer, g_record[i].size, g_record[i].file, g_record[i].line);
        }
    }
}

2、my_malloc.h

#ifndef __MY_MALLOC_H__
#define __MY_MALLOC_H__

#include <stdio.h>
#include <malloc.h>

#define SIZE        256

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

void*	my_malloc(size_t n, const char* file, const int line);
void	my_free(void* p);
void    print_dynamic_memory_info(void);

#endif // !__MY_MALLOC_H__

3、my_malloc_inf.h

#ifndef __MY_MALLOC_INF_H__
#define __MY_MALLOC_INF_H__

#include "my_malloc.h"

#define malloc(n)	my_malloc(n, __FILE__, __LINE__)
#define free(p)		my_free(p)

//void* my_malloc(size_t n, const char* file, const int line);
//void my_free(void* p);

#endif // !__MY_MALLOC_INF_H__

三、如何使用

        只需将 #include "my_malloc_inf.h" 放到有动态申请内存的代码中即可(这同时也是它的不足之处)

        比如在著名开源项目 cJSON.h 中添加进 #include "my_malloc_inf.h" 。

#include <stdio.h>
#include "my_malloc_inf.h"
#include "cJSON.h"

void main()
{
    char* str = (char*)malloc(sizeof(char) * 6);
    cJSON* json = cJSON_CreateNull();
    print_dynamic_memory_info();
    cJSON_Delete(json);
    print_dynamic_memory_info();
}

四、优缺点

优点:

  • 无需借助工具,对嵌入式工程师来说非常友好。
  • 在运行的时候甚至可以将地址丢失的动态空间进行释放。
  • 正常使用 malloc() 函数与 free() 函数,无需改动原有代码。

缺点:

  • 对于工程量大的代码来说可能会比较不方便,因为头文件众多,需要添加的地方也很多。
  • 显示申请动态空间的代码位置太过深入,还是无法能够十分快速的找出哪里申请的空间没有释放。

猜你喜欢

转载自blog.csdn.net/weixin_48896613/article/details/127398274