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++的模板特性,写出的代码效果会更好。

 

posted @ 2021-03-28 20:21  寒霜未降  阅读(85)  评论(0编辑  收藏  举报