一、内存泄漏是什么
简单来说你 new 或者 malloc 一块内存后忘记 delete 或者 free 了,或者把地址丢失了导致无法进行释放。
以产生的方式来分类,内存泄漏可以分为四类:
-
常发性内存泄漏:发生内存泄漏的代码会被多次执行到,每次被执行时都会导致一块内存泄漏。
-
偶发性内存泄漏:发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
-
一次性内存泄漏:发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅有一块内存发生泄漏。
-
隐式内存泄漏:程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。
二、如何检查
内存泄漏检查对于经常在 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() 函数,无需改动原有代码。
缺点:
- 对于工程量大的代码来说可能会比较不方便,因为头文件众多,需要添加的地方也很多。
- 显示申请动态空间的代码位置太过深入,还是无法能够十分快速的找出哪里申请的空间没有释放。