C编程实践——动态内存管理
1. 一维指针动态内存管理
1.1 同函数内存管理
在同一个函数内完成动态内存的申请和释放,常用的写法如下,
1 #define TEST_DATA_LEN 20 2 3 void Demo_Fun(void) 4 { 5 double *pTstPtr = NULL; 6 7 pTstPtr = (double*)malloc(sizeof(double) * TEST_DATA_LEN); 8 if (NULL == pTstPtr) 9 { 10 goto MEM_ERR; 11 } 12 13 /* Main function area */ 14 15 MEM_ERR: 16 if (NULL != pTstPtr) 17 { 18 free(pTstPtr); 19 pTstPtr = NULL; 20 } 21 }
在代码中加入goto语句是一个危险的操作,在一些公司的编码规范中会强制要求禁用goto。因此需要探索一种不使用goto的写法,本文给出的一种实现方式如下,
1 #define TEST_DATA_LEN 20 2 3 void Demo_FunNoGoto(void) 4 { 5 double* pTstPtr = NULL; 6 7 do { 8 pTstPtr = (double*)malloc(sizeof(double) * TEST_DATA_LEN); 9 if (NULL == pTstPtr) 10 { 11 break; 12 } 13 14 /* Main function area */ 15 16 } while (0); 17 18 if (NULL != pTstPtr) 19 { 20 free(pTstPtr); 21 pTstPtr = NULL; 22 } 23 }
在该实现中,需要借助do {} while(0); 带有的中断效果,当需要退出处理流时,使用break语句,就可打断操作。
将上述过程进行一定程度的封装,代码可以演变为,
1 #define TEST_DATA_LEN 20 2 3 #define MEM_ALLOC(type, size) (type*)malloc(sizeof(type)*size) 4 #define MEM_CHECK(ptr) if (NULL == ptr) { break; } 5 #define MEM_FREE(ptr) if (NULL != ptr) {\ 6 free(ptr);\ 7 ptr = NULL;\ 8 } 9 10 void Demo_FunNoGoto(void) 11 { 12 double* pTstPtr = NULL; 13 14 do { 15 pTstPtr = MEM_ALLOC(double, TEST_DATA_LEN); 16 MEM_CHECK(pTstPtr); 17 18 /* Main function area */ 19 20 } while (0); 21 22 MEM_FREE(pTstPtr); 23 }
经过演变后,分配、检查和释放可以放在头文件中,供所有文件使用。
1.2 不同函数内存管理
在实际使用中,确实会遇到在一个函数中分配内存,在另一个函数释放内存的情况。
对于嵌入式开发来说,这是一种比较危险的行为,很容易导致内存泄漏,且一些辅助工具不太容易检查这种情况。
针对这种情况,本文建议在同一文件中提供专门的一组函数实现内存的分配和释放,
1 u32 Demo_FunMemAlloc(double *pSrcPtr, u32 srcSize) 2 { 3 u32 ret = true; 4 5 pSrcPtr = NULL; 6 7 pSrcPtr = (double*)malloc(sizeof(double) * srcSize); 8 9 if (NULL == pSrcPtr) 10 { 11 ret = false; 12 } 13 14 if (false == ret) 15 { 16 if (NULL != pSrcPtr) 17 { 18 free(pSrcPtr); 19 pSrcPtr = NULL; 20 } 21 } 22 23 return ret; 24 } 25 26 void Demo_FunMemFree(double* pSrcPtr) 27 { 28 if (NULL != pSrcPtr) 29 { 30 free(pSrcPtr); 31 pSrcPtr = NULL; 32 } 33 }
在本文提供的方法中,内存的申请和释放有固定的接口,当检查代码时对申请和释放进行全局搜索,就可以比较容易的识别出申请和释放是否成对出现。
但需要明确指出的是,本文提出的方法仅能降低风险,并不能从根本上解决问题,根本的解决办法还是在于直接消除这种使用场景。
2. 二维指针动态内存管理
在某些使用场景下会涉及二维指针的动态内存管理问题,一种经典的实现方法如下,
1 void Demo_FunDimTwo(u32 rowSize, u32 colSize) 2 { 3 u32 i; 4 double** pRgPtr = NULL; 5 6 pRgPtr = (double**)malloc(sizeof(double*) * rowSize); 7 if (NULL == pRgPtr) 8 { 9 goto MEM_ERR; 10 } 11 12 for (i = 0; i < rowSize; i++) 13 { 14 pRgPtr[i] = (double*)malloc(sizeof(double) * colSize); 15 16 if (NULL == pRgPtr[i]) 17 { 18 goto MEM_ERR; 19 } 20 } 21 22 MEM_ERR: 23 if (NULL != pRgPtr) 24 { 25 for (i = 0; i < rowSize; i++) 26 { 27 if (NULL != pRgPtr[i]) 28 { 29 free(pRgPtr[i]); 30 pRgPtr[i] = NULL; 31 } 32 } 33 34 free(pRgPtr); 35 pRgPtr = NULL; 36 } 37 }
这种二维指针申请方法申请的内存块是不连续的,且反复多次申请。本文探索另一种申请方法,
1 void Demo_FunDimTwoNew(u32 rowSize, u32 colSize) 2 { 3 u32 i; 4 double* pPhyRgPtr = NULL; 5 double** pRgPtr = NULL; 6 7 do { 8 pPhyRgPtr = (double*)malloc(sizeof(double) * rowSize * colSize); 9 if (NULL == pPhyRgPtr) 10 { 11 break; 12 } 13 14 pRgPtr = (double**)malloc(sizeof(double*) * rowSize); 15 if (NULL == pRgPtr) 16 { 17 break; 18 } 19 20 for (i = 0; i < rowSize; i++) 21 { 22 pRgPtr[i] = &pPhyRgPtr[i * colSize]; 23 } 24 25 /* Main function area */ 26 } while (0); 27 28 if (NULL != pPhyRgPtr) 29 { 30 free(pPhyRgPtr); 31 pPhyRgPtr = NULL; 32 } 33 34 if (NULL != pRgPtr) 35 { 36 free(pRgPtr); 37 pRgPtr = NULL; 38 } 39 40 }
在该方法中,物理内存为一块连续的一维指针,然后将该一维指针切块,用二维指针管理。相比于第一种,申请动态内存的次数更少。
重写该函数,可以封装为,
1 #define MEM_ALLOC(type, size) (type*)malloc(sizeof(type)*size) 2 #define MEM_CHECK(ptr) if (NULL == ptr) { break; } 3 #define MEM_FREE(ptr) if (NULL != ptr) {\ 4 free(ptr);\ 5 ptr = NULL;\ 6 } 7 #define MEM_DIM2_ALLOC(type, data) do {\ 8 data.PhyPtr = NULL; \ 9 data.UserPtr = NULL;\ 10 data.PhyPtr = MEM_ALLOC(double, data.RowSize*data.ColSize);\ 11 MEM_CHECK(data.PhyPtr);\ 12 data.UserPtr = MEM_ALLOC(double*, data.RowSize);\ 13 MEM_CHECK(data.UserPtr);\ 14 Mem_##type##Dim2Proc(&data); 15 } while (0) 16 17 #define MEMEM_DIM2_FREE(data) do {\ 18 MEM_FREE(data.UserPtr);\ 19 MEM_FREE(data.PhyPtr);\ 20 } while(0) 21 22 typedef struct { 23 u32 RowSize; 24 u32 ColSize; 25 double* PhyPtr; 26 double** UserPtr; 27 }Demo_doubleDim2Type; 28 29 void Mem_doubleDim2Proc(Demo_doubleDim2Type* pSrcPtr) 30 { 31 u32 i = 0; 32 33 for (i = 0; i < pSrcPtr->RowSize; i++) 34 { 35 pSrcPtr->UserPtr[i] = &pSrcPtr->PhyPtr[i* pSrcPtr->ColSize]; 36 } 37 } 38 39 void Demo_FunDimTwoNew(u32 rowSize, u32 colSize) 40 { 41 Demo_doubleDim2Type tmpRgData; 42 43 do { 44 tmpRgData.RowSize = rowSize; 45 tmpRgData.ColSize = colSize; 46 MEM_DIM2_ALLOC(tmpRgData); 47 MEM_CHECK(tmpRgData.UserPtr); 48 49 /* Main function area */ 50 } while (0); 51 52 MEMEM_DIM2_FREE(tmpRgData); 53 }
封装后,代码可以重复使用。但这种写法的灵活性欠佳,如果能应用C++的模板特性,写出的代码效果会更好。