如何正確使用動態內存?

原文:https://blog.csdn.net/jiangjunjie_2005/article

一.  常見錯誤與預防

1.   分配後忘記釋放內存

void func(void)
{
    p = malloc(len);
    do_something(p);
    return;  /*錯誤!退出程序時沒有釋放內存*/
}

預防:  編寫代碼時 malloc() 和 free() 保證成對出現,避免忘記資源回收。

int func(void)
{
    p = malloc(len);
    if (condition)
        return -1;  /*錯誤!退出程序時沒有釋放內存*/
    free(p);
    return 0;
}

預防: 一旦使用動態內存分配,請仔細檢查程序的退出分支是否已經釋放該動態內存。

2.   釋放內存調用錯誤指針

void func(void)
{
    p = malloc(len);
    val = *p++;  /*錯誤!動態內存句柄不可移動*/
    free(p);
}

預防: 千萬不要修改動態內存句柄!可以另外賦值給其他指針變量,再對該動態內存進行訪問操作。

3.   分配內存不夠導致溢出

void func(void)
{
    len = strlen(str);
    p = malloc(len);
    strcpy(p, str);  /*錯誤!str的’\0’寫到動態內存外*/
}

預防:  分配內存前仔細思考長度是否足夠,千萬注意字符串拷貝佔用內存比字符串長度大 1。

二.  自動查錯機制

儘管在開發過程中堅守原則和謹慎編程甚至嚴格測試,然而內存泄露的錯誤還是難以杜絕,如何讓系統自動查出內存泄露的錯誤呢?

一種比較好的方法是建立日誌塊,即每次分配內存時記錄該內存塊的指針和大小,釋放時再去除該日誌塊,如果有內存泄露就會有對應的日誌塊記錄這些內存沒有釋放,這樣就可以提醒程序員進行查錯。

有了上述日誌塊操作函數,再來實現動態內存分配與釋放函數就很容易了。只有當處於 DEBUG 版本和打開內存調試 DMEM_DBG 時才進行日誌登錄,否則 MallocExt() 和 FreeExt() 函數與 malloc() 和 free() 是等價的,這樣保證了系統處於發佈版本時的性能。

(代碼已經過嚴格測試,但這不是盈利的商業代碼,即沒有版權。但如果因代碼錯誤帶來的任何損失作者具有免責權利)

代碼部分:

首先定義日誌塊結構體:

/* Log of dynamic memory usage */
typedef struct _dmem_log
{
    struct _dmem_log *p_stNext; /* Point to next log */
    const void *p_vDMem; /* Point to allocated memory by this pointer */
    INT32S iSize; /* Size of the allocated memory */
} DMEM_LOG;

然後爲該結構體開闢內存:

static DMEM_LOG *s_pstFreeLog; /* Point to free log pool by this pointer */
static INT8U s_byNumUsedLog;
static DMEM_LOG *s_pstHeadLog; /* Point to used log chain by this pointer */

/* Pool of dynamic memory log */
#define NUM_DMEM_LOG 20
static DMEM_LOG s_astDMemLog[NUM_DMEM_LOG];

下面是內存日誌塊的操作函數:初始化、插入日誌和移除日誌:

/**********************************************************                                                             *                    Initialize DMem Log
* Description : Initialize log of dynamic memory
* Arguments  : void
* Returns      : void
* Notes        :
**********************************************************/
static void InitDMemLog(void)
{
    INT16S    nCnt;
    /* Initialize pool of log */
    for (nCnt = 0; nCnt < NUM_DMEM_LOG; ++nCnt)
    {
        /* Point to next one */
        s_astDMemLog[nCnt].p_stNext = &s_astDMemLog[nCnt + 1];
    }
    s_astDMemLog[NUM_DMEM_LOG - 1].p_stNext = NULL;
    s_pstFreeLog = &s_astDMemLog[0]; /* Point to the 1th log */
    return;
}

/**********************************************************                                                             *                       Log DMem
* Description : Join an allocated memory into log pool
* Arguments  : const void *p_vAddr    point to address of this allocated memory by this pointer
*             INT32S iSize    size of this allocated memory
* Returns      : void
* Notes        :
**********************************************************/
static void LogDMem(const void *p_vAddr, INT32S iSize)
{
    ASSERT(p_vAddr && iSize > 0);
    DMEM_LOG *p_stLog;
    #if OS_CRITICAL_METHOD == 3
    OS_CPU_SR  cpu_sr;
    #endif
    
    /* Get a log from free pool */
    OS_ENTER_CRITICAL(); /* Avoid race condition on s_pstFreeLog */
    if (!s_pstFreeLog)
    {
        OS_EXIT_CRITICAL();
        PRINTF("Allocate DMemLog failed.\r\n");       
        return;
    }
    p_stLog = s_pstFreeLog;
    s_pstFreeLog = s_pstFreeLog->p_stNext;
    OS_EXIT_CRITICAL();
    
    /* Don't need to protect this log that is free one currently */
    p_stLog->p_vDMem = p_vAddr;
    p_stLog->iSize = iSize;

    /* Put this log into used chain */
    OS_ENTER_CRITICAL(); /* Avoid race condition */
    p_stLog->p_stNext = s_pstHeadLog;
    s_pstHeadLog = p_stLog;
    ++s_byNumUsedLog;
    OS_EXIT_CRITICAL();
    
    return;
}

/**********************************************************                                                             *                       Unlog DMem
* Description : Remove an allocated memory from log pool
* Arguments  : const void *p_vAddr point to address of this allocated memory by this pointer
* Returns      : void
* Notes        :
**********************************************************/
static void UnlogDMem(const void *p_vAddr)
{
    ASSERT(p_vAddr);
    DMEM_LOG    *p_stLog, *p_stPrev;
    #if OS_CRITICAL_METHOD == 3
    OS_CPU_SR  cpu_sr;
    #endif

    /* Search the log */
    OS_ENTER_CRITICAL(); /*Avoid race condition */
    p_stLog = p_stPrev = s_pstHeadLog;
    while (p_stLog)
    {
        if (p_vAddr == p_stLog->p_vDMem)
        {
         break; /* Have found */
        }          

        p_stPrev = p_stLog;        
        p_stLog = p_stLog->p_stNext;    /* Move to next one */
    }
    
    if (!p_stLog)
    {
        OS_EXIT_CRITICAL();
        PRINTF("Search Log failed.\r\n");         
        return;
    }

    /* Remove from used pool */
    if (p_stLog == s_pstHeadLog)
    {
     s_pstHeadLog = s_pstHeadLog->p_stNext;
    }
    else
    {
     p_stPrev->p_stNext = p_stLog->p_stNext;
    }
    --s_byNumUsedLog;
    OS_EXIT_CRITICAL();

    /* Don't need to protect this log that is free one currently */
    p_stLog->p_vDMem = NULL;
    p_stLog->iSize = 0;

    /* Add into free pool */
    OS_ENTER_CRITICAL(); /* Avoid race condition */
    p_stLog->p_stNext = s_pstFreeLog;
    s_pstFreeLog = p_stLog;
    OS_EXIT_CRITICAL();

    return;
}

帶日誌記錄功能的內存分配 MallocExt() 和內存釋放 FreeExt() 函數:

/*********************************************************                                                    
*                      Malloc Extension
* Description : Malloc a block of memory and log it if need
* Arguments : INT32S iSize    size of desired allocate memory
* Returns: void *NULL= failed, otherwise=pointer of allocated memory
* Notes        :
**********************************************************/
void *MallocExt(INT32S iSize)
{
    ASSERT(iSize > 0);
    void *p_vAddr;

    p_vAddr = malloc(iSize);
    if (!p_vAddr)
    {
     PRINTF("malloc failed at %s line %d.\r\n", __FILE__, __LINE__);
    }
    else
    {
        #if (DMEM_DBG && DBG_VER)
        memset(p_vAddr, 0xA3, iSize); /* Fill gargage for debug */
        LogDMem(p_vAddr, iSize);    /* Log memory for debug */
        #endif
    }

    return p_vAddr;     
}

/**********************************************************
*                      Free Extension
* Description : Free a block of memory and unlog it if need
* Arguments  : void * p_vMem point to the memory by this pointer
* Returns      : void
* Notes        :
**********************************************************/
void FreeExt(void *p_vMem)
{
    ASSERT(p_vMem);

    free(p_vMem);  
    #if (DMEM_DBG && DBG_VER)
    UnlogDMem(p_vMem);    /* Remove memory from log */
    #endif
    return;
}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/rNy70kODxD1srGZvr5z7GQ