用C写程序的时候,管理内存是一件比较麻烦的事情,稍有不慎就容易导致内存泄露。因此,一种好的内存管理方式还是十分有必要的,尤其是代码量比较大的时候,更显其重要性。
首先要管理内存,就得有一种有效的机制检测内存情况,该方面好像叫“打桩”,也不知道名字对不对。c语音内存的申请和释放主要使用malloc和free这两个函数。我们可以对malloc和free再次进行封装,比如我们将malloc封装为Malloc,对free封装为Free,在编程过程中使用Malloc和Free函数,而不使用原生的malloc和free函数。并创建一个全局结构变量,用于记录申请过和释放过的内存内存地址,然后进行对比,就能有效的检测到内存泄露点了。并通过printf将对应的内存泄露点位置打印出来,这样就能在每次运行后及时的检测内存情况,便于在编码过程中,及时的解决内存泄露问题。同时也能有效的监测是否存在重复释放等一系列情况,之前没有分享具体实现,今天分享出来,不然感觉写得文章很空。
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include "BDA_PortTable.h"
//=========================================================
//--------以下为内存泄漏检测
//=========================================================
#define TML_MAX 1024
static struct tagBDA_MEM_ITEM
{
void * p; //内存指针
const char * name; //内存分配点名字
int line; //行号
long size;
}sg_memItems[TML_MAX] = {{0}};
/*
* 初始化内存检测
*/
void BDA_MemCheckInit(void)
{
#if BDA_IS_MEMORY_CHECK == BDA_MEMORY_CHECK_OPEN
BDA_memset(sg_memItems, 0, sizeof(sg_memItems));
#endif
}
/*
* 退出内存检测
*/
void BDA_MemCheckExit(void)
{
#if BDA_IS_MEMORY_CHECK == BDA_MEMORY_CHECK_OPEN
int i = 0;
LOGW("\n###########################################################\n");
LOGW("###########################################################\n");
LOGW("检查内存泄漏情况\n");
for ( i = 0; i < TML_MAX; i++ )
{
if (sg_memItems[i].p)
{
LOGW("###########################################################\n");
LOGW("###########################################################\n");
LOGW("未释放的内存 %s[%d] = 0x%08X\n", sg_memItems[i].name, sg_memItems[i].line, sg_memItems[i].p);
LOGW("###########################################################\n");
}
}
#endif
}
//=========================================================
//--------以下为平台重载函数
//=========================================================
#if BDA_IS_MEMORY_CHECK == BDA_MEMORY_CHECK_CLOSE
void *BDA_malloc(int32_t size)
{
return malloc(size);
}
#else
void *BDA_CheckMalloc(int32_t size, const char * name, int32_t line)
{
int32_t i = 0;
char *ptr = (char *)malloc(size);
if ( ptr )
{
for ( i = 0; i < TML_MAX; i++ )
{
if (!sg_memItems[i].p)
{
sg_memItems[i].p = (void *)ptr;
sg_memItems[i].name = name;
sg_memItems[i].line = line;
sg_memItems[i].size = size;
break;
}
}
if (i == TML_MAX)
{
LOGW("内存记录条目已经用满\n");
}
}
return (void *)ptr;
}
#endif
#if BDA_IS_MEMORY_CHECK == BDA_MEMORY_CHECK_CLOSE
void BDA_free(void * p)
{
free(p);
}
#else
void BDA_CheckFree(void * ptr, const char * name, int line)
{
int32_t i = 0;
for ( i = 0; i < TML_MAX; i++ )
{
if (((unsigned long)sg_memItems[i].p) == ((unsigned long)ptr))
{
sg_memItems[i].p = 0;
sg_memItems[i].name = 0;
sg_memItems[i].line = 0;
sg_memItems[i].size = 0;
break;
}
}
if (i == TML_MAX)
{
LOGW("释放了未申请的内存 %s[%d] = 0x%08X\n", name, line, ptr);
}
free(ptr);
}
#endif
//这之下本来还封装了一些与操作系统相关的函数,由于与本文章没有什么关系,就不列举出来了,进行这样的封装,主要是为了跨平台移植的方便。
....
从代码中,各位应该能看到,关于内存管理的,主要涉及到四个函数:
BDA_MemCheckInit(void); //负责初始化内存管理结构
<pre name="code" class="cpp">void BDA_MemCheckExit(void); //负责在程序结束时,检测是否存在泄漏
<pre name="code" class="cpp">void *BDA_CheckMalloc(int32_t size, const char * name, int32_t line); //对malloc进行封装,用于记录内存申请点
<pre name="code" class="cpp">void BDA_CheckFree(void * ptr, const char * name, int line); //对free进行封装,用于释放内存并清除申请记录
为了使这几个函数的使用方法与原生函数一直,在.h文件中做一下声明:
void *BDA_CheckMalloc(int32_t size, const char * name, int32_t line);
void BDA_CheckFree(void * p, const char * name, int line);
#define BDA_malloc(x) BDA_CheckMalloc(x, __FILE__, __LINE__);
#define BDA_free(x) BDA_CheckFree(x, __FILE__, __LINE__);
到此,大功告成,在实际项目开发时,采用一下结构:
void main()
{
BDA_MemCheckInit(); //内存泄漏检测初始化
//需要编写的代码
BDA_MemCheckExit(); //内存泄漏检查退出
}
采用这样的结构,就可以轻松的找到内存的泄漏点了。
仅仅是能检测内存泄露情况是还不够的,良好的代码结构对于内存管理也是十分重要的。
当我们面对较大的程序设计时,我们应当参考面向对象的编程思想,将一系列的数据结构抽象成一个个的类,每个类进行自己的内存管理,相对于类的客户端代码只需要在合适的时候销毁对应对象即可,这里可以参考一下c++构造函数和析构函数的思想,无需关心对象的内部内存如何释放。采用这样的方式,逐层封装,逐层的进行管理,每一层级的类调用下它之下的对象的销毁函数接口,从而实现更清晰的内存管理。
如果代码进一步激增,估计还需要更完善的内存管理机制,目前还没有从事这样的项目开发,后期有机会,有经验了,再总结下来。