C語言通用雙向循環鏈表操做函數集

說明

     相比Linux內核鏈表宿主結構可有多個鏈表結構的優勢,本函數集側重封裝性和易用性,而靈活性和效率有所下降。
     可基於該函數集方便地構造棧或隊列集。
     本函數集暫未考慮併發保護。html

 

一  概念

     鏈表是一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序經過鏈表中的指針連接次序實現。鏈表由一系列存儲結點組成,結點可在運行時動態生成。每一個結點均由兩部分組成,即存儲數據元素的數據域和存儲相鄰結點地址的指針域。當進行插入或刪除操做時,鏈表只需修改相關結點的指針域便可,所以相比線性表順序結構更加方便省時。node

     鏈表可分爲單鏈表(Singly Linked List)、雙向鏈表(Doubly Linked List)和循環鏈表(Circular List)等。一個單鏈表結點僅包含一個指向其直接後繼結點的指針,所以當須要操做某個結點的直接前驅結點時,必須從單鏈表表頭開始查找。算法

     雙向鏈表和循環鏈表均爲單鏈表的變體。一般建立雙向循環鏈表以綜合利用二者的優勢。安全

1.1 雙向鏈表

     雙向鏈表的每一個結點除含有數據域外,還有兩個指針域,分別指向直接前驅結點和直接後繼結點。所以,從雙向鏈表中的任一結點開始,都可方便地訪問其前驅結點和後繼結點。雙向鏈表的結點結構示意圖以下所示:數據結構

 

圖1 雙向鏈表的結點結構多線程

     其中,Data爲結點存儲的數據元素,prev指針指向該結點的前驅結點,next指針指向該結點的後繼結點。雙向鏈表一般含有一個表頭結點,亦稱哨兵結點(Sentinel Node),用於簡化插入和刪除等操做。帶頭結點的非空雙向鏈表以下圖所示:併發

 

圖2 帶頭結點的非空雙向鏈表函數

     圖中,表頭指針dhead指向表頭結點Head,該結點的前驅指針爲空;結點C爲表尾結點,其後繼指針爲空。除表頭結點和表尾結點外,對指向雙向鏈表任一結點的指針p,知足下面的關係:oop

p = p->prev->next = p->next->prev測試

     即當前結點前驅的後繼是自身,其後繼的前驅也是自身。

     鏈表有查找、插入和刪除三種基本操做。雙向鏈表也不例外。

     1) 查找操做

     在帶表頭的雙向鏈表中查找數據域爲一特定值的某個結點時,可從表頭結點開始向後依次匹配各結點數據域的值,若與特定值相同則返回指向該結點的指針,不然繼續日後遍歷直至表尾。

     2) 插入操做

     假設指針p和q指向雙向鏈表中的兩個先後相鄰結點,將某個新結點(指針爲s)插到p和q之間,其過程及C語言描述以下圖所示:

圖3 在雙向鏈表中插入結點的過程

     注意,結點前驅後繼指針的操做順序並不是惟一,但必須保證最後纔對p->next或q->prev賦值(操做➃),不然會「丟失」p的後繼結點或q的前驅結點。

     可見,若相鄰結點指針p、q均已知,則在p和q之間插入新結點s時,只需依次將s的前驅指針指向p,s的後繼指針指向q,p的後繼指針指向s,q的前驅指針指向s。即:

① s->prev = p;

② s->next = q;

③ p->next = s;

④ q->prev = s;

     雙向鏈表中p和q->prev指向同一結點,所以上述步驟等效於圖3中q「視角」的第二種插入順序。爲便於記憶,可想象孩子(s)前後去拉爸爸(p)和媽媽(q)的手,爸爸(p)媽媽(q)再前後拉住孩子(s)的手。

     3) 刪除操做

     刪除某個結點,其實就是插入某個結點的逆操做。仍是對於雙向循環鏈表,要在連續的三個結點s,p,q中刪除p結點,只需把s的右鏈域指針指向q,q的左鏈域指針指向s,並收回p結點便可。

     假設指針p、s和q指向雙向鏈表中的三個先後相鄰結點,刪除結點s的過程及C語言描述以下圖所示:

 

圖4 在雙向鏈表中刪除結點的過程

     可見,刪除時只需將p的後繼指針指向q,q的前驅指針指向p,並回收結點s便可。

1.2 循環鏈表

     將單鏈表尾結點的指針域指向第一個結點或表頭結點,即構成單向循環鏈表,簡稱循環鏈表。從循環鏈表中任一結點單向出發,都可找到鏈表中其餘結點。

     藉助表頭結點可統一空表和非空表的運算,所以循環鏈表中每每加入表頭結點。帶頭結點的循環鏈表以下圖所示:

 

圖5 帶頭結點的循環鏈表(頭指針)

     循環鏈表的操做算法與普通單鏈表基本相同,只是對錶尾的判斷有所改變。在循環鏈表chead中,判斷表尾結點p的條件是p->next == chead,即當結點的後繼指針指向表頭結點時,說明已到表尾。

     注意,建立循環鏈表時必須使其尾結點的後繼指針指向表頭結點,尤爲是在尾結點後插入新結點時。

     棄用頭指針而採用尾指針,可方便地找到循環鏈表的開始結點和終端結點。以下圖所示:

 

圖6 帶頭結點的循環鏈表(尾指針)

1.3 雙向循環鏈表

     雙向鏈表一般採用帶表頭結點的循環鏈表形式,即雙向循環鏈表。雙向循環鏈表在雙向鏈表的基礎上,將表頭結點的前驅指針指向尾結點,尾結點的後驅指針指向頭結點,首尾相連造成一個雙向環。雙向循環鏈表可方便地獲取當前結點的前驅結點,沒必要像單向循環鏈表那樣從頭開始遍歷;而其循環的特性又可方便地從任一結點出發單向遍歷整個鏈表,沒必要像雙向鏈表那樣根據方向而使用不一樣的指針域。

     帶頭結點的雙向循環鏈表以下圖所示:

 

圖7 帶頭結點的雙向循環鏈表

 

 

二  實現

     本節將採用C語言實現一個通用雙向循環鏈表的建立及操做函數集。

     文中「OMCI_」和「Omci」前綴爲代碼所在模塊名信息,使用接口時可按需修改這些前綴。

2.1 數據結構

     定義雙向循環鏈表單元結構示意以下:

 

圖8 雙向循環鏈表單元結構示意圖

     其中,根結點的pHead字段指向鏈表頭結點,pTail字段指向鏈表尾結點。頭結點的pPrev字段指向尾結點,尾結點的pNext字段指向頭結點。若鏈表爲空(僅含頭結點),則pHead和pTail字段均指向頭結點。

     鏈表結點定義以下:

typedef struct T_OMCI_LIST_NODE{
     struct T_OMCI_LIST_NODE  *pPrev;      /* 指向鏈表直接前驅結點的指針 */
     struct T_OMCI_LIST_NODE  *pNext;      /* 指向鏈表直接後繼結點的指針 */
     VOID                     *pvNodeData; /* 指向鏈表數據的指針。獲取具體數據時需顯式轉換該指針類型爲目標類型 */ }T_OMCI_LIST_NODE;

    相應地,鏈表定義以下:

typedef struct{
     T_OMCI_LIST_NODE   *pHead;          /* 指向鏈表頭結點的指針 */
     T_OMCI_LIST_NODE   *pTail;          /* 指向鏈表尾結點的指針 */
     INT32U             dwNodeNum;       /* 鏈表結點數目 */
     INT32U             dwNodeDataSize;  /* 鏈表結點保存的數據字節數 */
 }T_OMCI_LIST;

     爲支持不一樣的數據類型和數據結構(通用性),鏈表結點數據域定義爲VOID *pvNodeData指針。變量dwNodeDataSize指示數據域的數據寬度(字節數)。也可將數據寬度信息存儲於頭結點數據域內,從而沒必要定義變量dwNodeDataSize。經過遍歷鏈表並計數可得結點數目,故變量dwNodeNum也並不是必要。所以,dwNodeDataSize和dwNodeNum意在簡化邏輯,也是「空間換時間」思想的體現。

     除此以外,還定義如下狀態值,以使鏈表內部狀態透明化:

//鏈表函數返回狀態枚舉值
typedef enum{
    OMCI_LIST_OK    = (INT8U)0,
    OMCI_LIST_ERROR = (INT8U)1
}LIST_STATUS;

//鏈表結點空閒狀況枚舉值
typedef enum{
    OMCI_LIST_OCCUPIED = (INT8U)0,
    OMCI_LIST_EMPTY    = (INT8U)1,
    OMCI_LIST_NULL     = (INT8U)2
}LIST_OCCUPATION;

//BOOL型常量,適用於'Is'前綴函數
#define  OMCI_LIST_TRUE   (BOOL)1
#define  OMCI_LIST_FALSE  (BOOL)0

2.2 宏代碼

     爲確保安全性,鏈表操做中須要進行大量的指針校驗。所以,定義幾個校驗空指針的宏,以簡化代碼篇幅:

#define   FUNC_NAME    __FUNCTION__ //(__func__)

/* 指針校驗宏 */
//若無返回值則retVal置RETURN_VOID
#define RETURN_VOID
#define CHECK_SINGLE_POINTER(ptr1, retVal) do{\
    if(NULL == (ptr1)) 
    { \
        printf("[%s(%d)]Null Pointer: "#ptr1"!\n\r", FUNC_NAME, __LINE__); \
        return retVal; \
    } \
}while(0)
#define CHECK_DOUBLE_POINTER(ptr1, ptr2, retVal) do{\
    if((NULL == (ptr1)) || (NULL == (ptr2))) \
    { \
        printf("[%s(%d)]Null Pointer: "#ptr1"(%p), "#ptr2"(%p)!\n\r", FUNC_NAME, __LINE__, ptr1, ptr2); \
        return retVal; \
    } \
}while(0)
#define CHECK_TRIPLE_POINTER(ptr1, ptr2, ptr3, retVal) do{\
    if((NULL == (ptr1)) || (NULL == (ptr2)) || (NULL == (ptr3))) \
    { \
        printf("[%s(%d)]Null Pointer: "#ptr1"(%p), "#ptr2"(%p), "#ptr3"(%p)!\n\r", FUNC_NAME, __LINE__, ptr1, ptr2, ptr3); \
        return retVal; \
    } \
}while(0)

     若待檢查的指針中至少有一個指針爲空時,校驗宏打印全部待檢查的指針值並退出。但其實現使得下面的語句在pList爲空時崩潰(打印時試圖訪問pList->pHead等):

     CHECK_TRIPLE_POINTER(pList, pList->pHead, pList->pHead->pNext, OMCI_LIST_ERROR);

     所以必須使用下面的分級校驗以免多級指針前級爲NULL時訪問本級出錯:

     CHECK_SINGLE_POINTER(pList, OMCI_LIST_ERROR); 

     CHECK_SINGLE_POINTER(pList->pHead, OMCI_LIST_ERROR);

     CHECK_SINGLE_POINTER(pList->pHead->pNext, OMCI_LIST_ERROR);

     若不打印各指針的值,則邏輯或(||)的運算順序足矣保證CHECK_TRIPLE_POINTER寫法的安全性。

     注意,這些指針校驗宏大量應用於2.3節函數接口中,以保證其安全性。使用者若能在外部杜絕空指針引用,則可添加條件編譯開關「剔除」這些校驗宏,以提升代碼執行效率。

 

     對於鏈表結點的操做比較固定,所以也用宏定義加以封裝:

//建立結點爲做爲鏈表頭以生成雙向循環空鏈表
#define OMCI_INIT_NODE(pNode) do{ \
    (pNode)->pNext = (pNode)->pPrev = (pNode); \
}while(0)
//"孤立"鏈表結點,避免經過該結點訪問其前驅和後繼結點(進而遍歷鏈表)
#define OMCI_ISOL_NODE(pNode) do{ \
    (pNode)->pNext = (pNode)->pPrev = NULL; \
}while(0)
//判斷鏈表是否僅含頭結點
#define OMCI_LIST_WITH_HEAD(pHeadNode) do{ \
    (((pHeadNode)->pPrev == (pHeadNode)) && ((pHeadNode->pNext == pHeadNode))); \
}while(0)

//插入鏈表結點
#define OMCI_INSERT_NODE(prevNode, insertNode) do{ \
    (insertNode)->pNext      = (prevNode)->pNext;  \
    (insertNode)->pPrev      = (prevNode);         \
    (prevNode)->pNext->pPrev = (insertNode);       \
    (prevNode)->pNext        = (insertNode);       \
}while(0)
//刪除鏈表結點
#define OMCI_REMOVE_NODE(removeNode) do{ \
    (removeNode)->pPrev->pNext = (removeNode)->pNext;  \
    (removeNode)->pNext->pPrev = (removeNode)->pPrev;  \
}while(0)

//獲取鏈表結點及其數據(不作安全性檢查)
#define GET_NODE_NUM(pList)      ((pList)->dwNodeNum)
#define GET_HEAD_NODE(pList)     ((pList)->pHead)
#define GET_TAIL_NODE(pList)     ((pList)->pTail)
#define GET_PREV_NODE(pNode)     ((pNode)->pPrev)
#define GET_NEXT_NODE(pNode)     ((pNode)->pNext)
#define GET_NODE_DATA(pNode)     ((pNode)->pvNodeData)

//雙向循環鏈表遍歷校驗宏
#define LIST_ITER_CHECK(pList, retVal) do{\
    CHECK_SINGLE_POINTER((pList), retVal); \
    CHECK_SINGLE_POINTER((pList)->pHead, retVal); \
    CHECK_SINGLE_POINTER((pList)->pHead->pNext, retVal); \
}while(0)
//雙向循環鏈表遍歷宏
//pList: 鏈表指針;pLoopNode: 鏈表結點,用做循環計數器;
//pTmpNode: 鏈表結點,用做刪除pLoopNode時臨時保存pLoopNode->pNext
//某些狀況下采用遍歷宏代替OmciLocateListNode或OmciTraverseListNode函數可提升執行效率。
//如外部數據和結點數據需按共同的規則轉換時,採用遍歷宏可以使外部數據沒必要重複轉換。
#define LIST_ITER_LOOP(pList, pLoopNode) \
  for(pLoopNode = (pList)->pHead->pNext; \
      pLoopNode != (pList)->pHead; \
      pLoopNode = pLoopNode->pNext)
#define LIST_ITER_LOOP_SAFE(pList, pLoopNode, pTmpNode) \
  for(pLoopNode = (pList)->pHead->pNext, pTmpNode = pLoopNode->pNext; \
      pLoopNode != (pList)->pHead; \
      pLoopNode = pTmpNode, pTmpNode = pLoopNode->pNext)

     結點的插入和刪除操做可參考1.1節雙向鏈表的圖例。GET_HEAD_NODE等宏可高效(但不安全)地獲取鏈表結點及其數據,後續將給出其函數版本。LIST_ITER_LOOP宏旨在給使用者提供必定程度的自由度,某些狀況下可提升執行效率。

2.3 函數接口

     首先定義一組私有函數,主要是建立、刪除和銷燬鏈表結點。這些內部使用的函數已儘量保證參數安全性,故省去參數校驗處理。

     爲簡便起見,下文中「XX指針」均表示指向XX的指針。

     建立新的鏈表結點:

/**********************************************************************
* 函數名稱: CreateListNode
* 功能描述: 建立新的鏈表結點
* 輸入參數: T_OMCI_LIST *pList :鏈表指針
*           VOID *pvNodeData  :待插入的鏈表結點數據指針
* 輸出參數: NA
* 返 回 值: T_OMCI_LIST_NODE*
***********************************************************************/
static T_OMCI_LIST_NODE *CreateListNode(T_OMCI_LIST *pList, VOID *pvNodeData)
{
    T_OMCI_LIST_NODE *pInsertNode = (T_OMCI_LIST_NODE*)calloc((sizeof(T_OMCI_LIST_NODE)+pList->dwNodeDataSize), 1);
    if(NULL == pInsertNode)
    {
        printf("[%s]pList(%p) failed to alloc for pInsertNode!\n", FUNC_NAME, pList);
        return NULL;
    }

    pInsertNode->pvNodeData = (INT8U *)pInsertNode + sizeof(T_OMCI_LIST_NODE);
    if(NULL != pvNodeData)
    {   //建立非頭結點時
        memmove(pInsertNode->pvNodeData, pvNodeData, pList->dwNodeDataSize);
    }

    return pInsertNode;
}

     刪除指定的鏈表結點:

/**********************************************************************
* 函數名稱: RemoveListNode
* 功能描述: 刪除指定的鏈表結點(釋放結點內存並置其前驅後繼指針爲NULL)
* 輸入參數: T_OMCI_LIST *pList :鏈表指針
*           VOID *pvNode       :待刪除的鏈表結點指針
* 輸出參數: NA
* 返 回 值: LIST_STATUS
* 注意事項: 本函數未置待刪除結點指針爲NULL,請避免訪問已刪除結點
***********************************************************************/
static LIST_STATUS RemoveListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE *pNode)
{
    OMCI_ISOL_NODE(pNode);
    free(pNode);  //釋放鏈表結點

    return OMCI_LIST_OK;
}

 

     OMCI_ISOL_NODE 宏用於"孤立"待刪除的鏈表結點,避免經過該結點訪問其前驅和後繼結點(進而遍歷鏈表)。由於RemoveListNode函數沒法將結點指針置空(C語言值傳遞特性),故調用者需注意避免再次使用已刪除的結點。若要達到結點指針置空的目的,可調用銷燬結點的接口函數:

/**********************************************************************
* 函數名稱: DestroyListNode
* 功能描述: 銷燬指定的鏈表結點(釋放結點內存並置結點指針爲NULL)
* 輸入參數: T_OMCI_LIST *pList :鏈表指針
*           VOID **pNode       :待銷燬的鏈表結點指針的指針
* 輸出參數: NA
* 返 回 值: LIST_STATUS
* 注意事項: 當指向待銷燬結點的指針存在多份拷貝且散佈程序各處時(尤爲當
*           調用鏈未能保證**pNode指向原始結點時),沒法完全銷燬該結點
***********************************************************************/
static LIST_STATUS DestroyListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE **pNode)
{
    free(*pNode);  //釋放鏈表結點
    *pNode = NULL;

    return OMCI_LIST_OK;
}

     DestroyListNode函數會釋放指定結點的內存並將結點指針置空。但當代碼中存在該結點指針的其餘副本時,該函數顯然沒法將這些指針副本置空。

     至於RemoveListNode和DestroyListNode函數孰優孰劣,可參考附註中對「迷途指針」的討論。

     有時可能須要獲知鏈表的確切佔用狀況(一般沒有必要),如不含任何結點、僅含頭結點或者還包含其餘有用結點。GetListOccupation函數可知足這一「吹毛求疵」的需求,其餘狀況應使用下文將要給出的判空函數OmciIsListEmpty。OmciIsListEmpty將不含任何結點和僅含頭結點均視爲空鏈表,以隱藏內部細節。

/**********************************************************************
* 函數名稱: GetListOccupation
* 功能描述: 獲取鏈表佔用狀況
* 輸入參數: T_OMCI_LIST *pList :鏈表指針
* 輸出參數: NA
* 返 回 值: LIST_OCCUPATION
* 注意事項: 本函數僅用於內部測試。
***********************************************************************/
static LIST_OCCUPATION GetListOccupation(T_OMCI_LIST *pList)
{
    CHECK_SINGLE_POINTER(pList, OMCI_LIST_NULL);
    CHECK_SINGLE_POINTER(pList->pHead, OMCI_LIST_NULL);

    return (0 == pList->dwNodeNum) ? OMCI_LIST_EMPTY : OMCI_LIST_OCCUPIED;
}

     基於上述私有函數,可進一步構建鏈表及其結點的基本操做接口。

2.3.1 鏈表操做

     使用鏈表前,必須對其初始化。初始化時將建立頭結點,並肯定後續將要連接的結點數據寬度。

/**********************************************************************
* 函數名稱: OmciInitList
* 功能描述: 鏈表初始化,產生空的雙向循環鏈表
* 輸入參數: T_OMCI_LIST *pList    :鏈表指針
*           INT32U dwNodeDataSize :鏈表結點保存的數據字節數
* 輸出參數: NA
* 返 回 值: LIST_STATUS
***********************************************************************/
LIST_STATUS OmciInitList(T_OMCI_LIST *pList, INT32U dwNodeDataSize)
{
    CHECK_SINGLE_POINTER(pList, OMCI_LIST_ERROR);

    if(0 == dwNodeDataSize)
    {
        printf("[%s]pList=%p, dwNodeDataSize=%uBytes, undesired initialization!\n",
               FUNC_NAME, pList, dwNodeDataSize);
        return OMCI_LIST_ERROR;
    }
    pList->dwNodeDataSize = dwNodeDataSize;  //給予從新修改結點數據大小的機會

    if(NULL != pList->pHead)
    {
        printf("[%s]pList(%p) has been initialized!\n", FUNC_NAME, pList);
        return OMCI_LIST_OK;
    }

    T_OMCI_LIST_NODE *pHeadNode = CreateListNode(pList, NULL);
    if(NULL == pHeadNode)
    {
        printf("[%s]pList(%p) failed to create pHeadNode!\n", FUNC_NAME, pList);
        return OMCI_LIST_ERROR;
    }

    OMCI_INIT_NODE(pHeadNode);
    pList->pHead = pList->pTail = pHeadNode;
    pList->dwNodeNum = 0;

    return OMCI_LIST_OK;
}

     一般不會中途修改dwNodeDataSize。僅當使用者確知數據寬度的變化邊界(如確知前N個結點數據爲四字節,其後爲八字節)時,中途修改dwNodeDataSize纔有意義。固然,也可新增一個OmciResizeList接口。

     調用OmciInitList接口後,將建立一張僅含頭結點的空雙向循環鏈表。此後可向鏈表中插入結點。

     暫時不須要當前鏈表時,可清空鏈表除頭結點外的結點。這樣再次使用時無需初始化鏈表,直接插入結點便可。若肯定再也不須要當前鏈表時,可銷燬鏈表的全部結點。OmciClearList和OmciDestroyList函數分別完成鏈表的清空和銷燬。

/**********************************************************************
* 函數名稱: OmciClearList
* 功能描述: 清空雙向循環鏈表除頭結點外的結點
* 輸入參數: T_OMCI_LIST *pList :鏈表指針
* 輸出參數: NA
* 返 回 值: LIST_STATUS
* 注意事項: 清空鏈表結點後,再次插入結點時不須要初始化鏈表。
***********************************************************************/
LIST_STATUS OmciClearList(T_OMCI_LIST *pList)
{
    LIST_ITER_CHECK(pList, OMCI_LIST_ERROR);

    T_OMCI_LIST_NODE *pNextNode, *pListNode = pList->pHead->pNext;
    while(pListNode != pList->pHead)
    {
        pNextNode = pListNode->pNext;
        RemoveListNode(pList, pListNode);
        pListNode = pNextNode;
    }

    OMCI_INIT_NODE(pList->pHead);
    pList->pTail = pList->pHead;
    pList->dwNodeNum = 0;

    return OMCI_LIST_OK;
}
/**********************************************************************
* 函數名稱: OmciDestroyList
* 功能描述: 銷燬雙向循環鏈表,包括頭結點
* 輸入參數: T_OMCI_LIST *pList :鏈表指針
* 輸出參數: NA
* 返 回 值: LIST_STATUS
* 注意事項: 銷燬鏈表後,再次插入結點時須要初始化鏈表。
***********************************************************************/
LIST_STATUS OmciDestroyList(T_OMCI_LIST *pList)
{
    LIST_ITER_CHECK(pList, OMCI_LIST_ERROR);

    T_OMCI_LIST_NODE *pNextNode, *pListNode = pList->pHead->pNext;
    while(pListNode != pList->pHead)
    {
        pNextNode = pListNode->pNext;
        DestroyListNode(pList, &pListNode);
        pListNode = pNextNode;
    }

    DestroyListNode(pList, &(pList->pHead)); //銷燬頭結點
    pList->pTail = NULL;                     //尾結點指針置空
    pList->dwNodeNum = 0;
    pList->dwNodeDataSize = 0;

    return OMCI_LIST_OK;
}

     清空或銷燬鏈表後,OmciIsListEmpty函數的返回值將爲邏輯真,代表當前鏈表爲空。

/**********************************************************************
* 函數名稱: OmciIsListEmpty
* 功能描述: 判斷鏈表是否爲空(僅含頭結點或不含任何結點)
* 輸入參數: T_OMCI_LIST *pList :鏈表指針
* 輸出參數: NA
* 返 回 值: BOOL
***********************************************************************/
BOOL OmciIsListEmpty(T_OMCI_LIST *pList)
{
    CHECK_SINGLE_POINTER(pList, OMCI_LIST_TRUE);
    CHECK_SINGLE_POINTER(pList->pHead, OMCI_LIST_TRUE);

    T_OMCI_LIST_NODE *pHeadNode = pList->pHead;
    if((0 == pList->dwNodeNum) &&
       (pHeadNode->pPrev == pHeadNode) && //冗餘校驗以增強安全性
       (pHeadNode->pNext == pHeadNode))
    {
        return OMCI_LIST_TRUE;
    }
    else
    {
        return OMCI_LIST_FALSE;
    }
}

     此處爲增強安全性對頭結點進行檢驗,但並不是必要。若剔除冗餘校驗,則OmciIsListEmpty函數的實現會更爲簡潔高效。

2.3.2 結點操做

     鏈表初始化後,可在鏈表頭結點後逆序或順序依次插入新的結點。當從頭結點向後繼方向遍歷時,逆序插入的行爲相似於棧,而順序插入的行爲相似於隊列。

/**********************************************************************
* 函數名稱: OmciPrependListNode
* 功能描述: 在鏈表頭結點後逆序增長結點,尾結點恆爲頭結點
*           在頭結點指針pHead所指向結點和pHead->pNext所指向結點
*           之間插入新結點,先插入的結點向右移動。遍歷鏈表時
*           從pHead開始向右依次訪問至最早插入的結點,相似於棧。
* 輸入參數: T_OMCI_LIST *pList :鏈表指針
*           VOID *pvNodeData   :待插入的鏈表結點數據指針
* 輸出參數: NA
* 返 回 值: LIST_STATUS
***********************************************************************/
LIST_STATUS OmciPrependListNode(T_OMCI_LIST *pList, VOID *pvNodeData)
{
    CHECK_DOUBLE_POINTER(pList, pvNodeData, OMCI_LIST_ERROR);

    if(0 == pList->dwNodeDataSize)
    {
        printf("[%s]pList=%p, dwNodeDataSize=0Bytes, probably uninitialized or initialized improperly. See 'OmciInitList'!\n",
               FUNC_NAME, pList);
        return OMCI_LIST_ERROR;
    }
    T_OMCI_LIST_NODE *pInsertNode = CreateListNode(pList, pvNodeData);
    if(NULL == pInsertNode)
    {
        printf("[%s]pList(%p) failed to create pInsertNode!\n", FUNC_NAME, pList);
        return OMCI_LIST_ERROR;
    }

    OMCI_INSERT_NODE(pList->pHead, pInsertNode); //在鏈表頭結點後增長一個結點

    pList->dwNodeNum++;

    return OMCI_LIST_OK;
}

/**********************************************************************
* 函數名稱: OmciAppendListNode
* 功能描述: 在鏈表頭結點後順序增長結點,新結點做爲尾結點
*           在頭結點指針pHead所指向結點前(即尾結點後)插入新結點,
*           先插入的結點向左移動。遍歷鏈表時從pHead開始向右依次
*           訪問至最後插入的結點,相似於隊列。
*           雙向循環鏈表已保證pList->pTail(即pHead->pPrev)非空。
* 輸入參數: T_OMCI_LIST *pList :鏈表指針
*           VOID *pvNodeData   :待插入的鏈表結點數據指針
* 輸出參數: NA
* 返 回 值: LIST_STATUS
***********************************************************************/
LIST_STATUS OmciAppendListNode(T_OMCI_LIST *pList, VOID *pvNodeData)
{
    CHECK_DOUBLE_POINTER(pList, pvNodeData, OMCI_LIST_ERROR);

    if(0 == pList->dwNodeDataSize)
    {
        printf("[%s]pList=%p, dwNodeDataSize=0Bytes, probably uninitialized or initialized improperly. See 'OmciInitList'!\n",
               FUNC_NAME, pList);
        return OMCI_LIST_ERROR;
    }

    T_OMCI_LIST_NODE *pInsertNode = CreateListNode(pList, pvNodeData);
    if(NULL == pInsertNode)
    {
        printf("[%s]pList(%p) failed to create pInsertNode!\n", FUNC_NAME, pList);
        return OMCI_LIST_ERROR;
    }

    OMCI_INSERT_NODE(pList->pTail, pInsertNode); //在鏈表尾結點後增長一個結點
    pList->pTail = pInsertNode;                  //新的尾結點指向當前添加的結點

    pList->dwNodeNum++;

    return OMCI_LIST_OK;
}

     對dwNodeDataSize 的校驗用於指示鏈表未初始化或未正確初始化的錯誤。將該校驗置於私有函數CreateListNode中可簡化Prepend和Append代碼。但FUNC_NAME信息將暴露內部函數,從而給使用者形成疑惑,故該校驗予以保留。

     有時須要在鏈表中任意位置插入結點,此時可以使用OmciInsertListNode接口。

/**********************************************************************
* 函數名稱: OmciInsertListNode
* 功能描述: 在鏈表中任意位置插入結點
* 輸入參數: T_OMCI_LIST *pList          :鏈表指針
*           T_OMCI_LIST_NODE *pPrevNode :待插入結點的前驅結點指針
*           VOID *pvNodeData            :待插入結點的數據域指針
* 輸出參數: NA
* 返 回 值: LIST_STATUS
* 注意事項: 若pPrevNode恆爲頭結點或尾結點,請使用OmciPrependListNode
*           或OmciAppendListNode函數
***********************************************************************/
LIST_STATUS OmciInsertListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE *pPrevNode, VOID *pvNodeData)
{
    CHECK_TRIPLE_POINTER(pList, pPrevNode, pvNodeData, OMCI_LIST_ERROR);

    if(0 == pList->dwNodeDataSize)
    {
        printf("[%s]pList=%p, dwNodeDataSize=0Bytes, probably uninitialized or initialized improperly. See 'OmciInitList'!\n",
               FUNC_NAME, pList);
        return OMCI_LIST_ERROR;
    }

    T_OMCI_LIST_NODE *pInsertNode = CreateListNode(pList, pvNodeData);
    if(NULL == pInsertNode)
    {
        printf("[%s]pList(%p) failed to create pInsertNode!\n", FUNC_NAME, pList);
        return OMCI_LIST_ERROR;
    }

    OMCI_INSERT_NODE(pPrevNode, pInsertNode);
    if(pPrevNode == pList->pTail)
        pList->pTail = pInsertNode;

    pList->dwNodeNum++;

    return OMCI_LIST_OK;
}

     當pPrevNode恆爲頭結點時,OmciInsertListNode接口等效於OmciPrependListNode;當pPrevNode恆爲尾結點時,OmciInsertListNode接口等效於OmciAppendListNode。這兩種狀況建議使用Prepend或Append接口(畢竟減小一個參數)。

     插入若干結點後,就可刪除或銷燬鏈表中除頭結點外的任一結點。

/**********************************************************************
* 函數名稱: OmciRemoveListNode
* 功能描述: 刪除雙向循環鏈表中除頭結點外的某一結點
* 輸入參數: T_OMCI_LIST *pList      :鏈表指針
*           T_OMCI_LIST_NODE *pNode :待刪除的鏈表結點指針
* 輸出參數: NA
* 返 回 值: LIST_STATUS
***********************************************************************/
LIST_STATUS OmciRemoveListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE *pNode)
{
    CHECK_DOUBLE_POINTER(pList, pNode, OMCI_LIST_ERROR);
    CHECK_DOUBLE_POINTER(pNode->pPrev, pNode->pNext, OMCI_LIST_ERROR);

    if(0 == pList->dwNodeNum)
    {
        printf("[%s]pList(%p) has no node to be Removed!\n", FUNC_NAME, pList);
        return OMCI_LIST_ERROR;
    }

    OMCI_REMOVE_NODE(pNode);
    if(pNode->pNext == pList->pHead)
    {
        pList->pTail = pNode->pPrev; //刪除尾結點
    }

    RemoveListNode(pList, pNode);
    pList->dwNodeNum--;

    return OMCI_LIST_OK;
}
/**********************************************************************
* 函數名稱: OmciDestroyListNode
* 功能描述: 銷燬雙向循環鏈表中除頭結點外的某一結點
* 輸入參數: T_OMCI_LIST *pList       :鏈表指針
*           T_OMCI_LIST_NODE **pNode :待銷燬的鏈表結點二級指針
* 輸出參數: NA
* 返 回 值: LIST_STATUS
***********************************************************************/
LIST_STATUS OmciDestroyListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE **pNode)
{
    CHECK_DOUBLE_POINTER(pList, pNode, OMCI_LIST_ERROR);
    CHECK_SINGLE_POINTER(*pNode, OMCI_LIST_ERROR);

    if(0 == pList->dwNodeNum)
    {
        printf("[%s]pList(%p) has no node to be Removed!\n", FUNC_NAME, pList);
        return OMCI_LIST_ERROR;
    }

    OMCI_REMOVE_NODE(*pNode);
    if((*pNode)->pNext == pList->pHead)
    {
        pList->pTail = (*pNode)->pPrev; //刪除尾結點
    }

    DestroyListNode(pList, pNode);
    pList->dwNodeNum--;

    return OMCI_LIST_OK;
}

     然而,要刪除或銷燬鏈表結點,必須先定位到該結點。

     在鏈表中「定位」某個結點有兩種手段:一是經過結點編號查找,如OmciGetListNodeByIndex;二是經過某種給定條件匹配,如OmciLocateListNode(查找首個知足給定條件的結點)。

/**********************************************************************
* 函數名稱: OmciGetListNodeByIndex
* 功能描述: 獲取鏈表中指定序號的結點(按頭結點後繼方向排序)
* 輸入參數: T_OMCI_LIST* pList :鏈表指針
*           INT32U dwNodeIndex :結點序號(從1開始)
* 輸出參數: NA
* 返 回 值: T_OMCI_LIST_NODE* 鏈表結點指針(空表返回NULL)
***********************************************************************/
T_OMCI_LIST_NODE* OmciGetListNodeByIndex(T_OMCI_LIST *pList, INT32U dwNodeIndex)
{
    CHECK_SINGLE_POINTER(pList, NULL);
    
    if(0 == dwNodeIndex)
        return pList->pHead;  //也可返回NULL
    if(dwNodeIndex >= pList->dwNodeNum)
        return pList->pTail;

    INT32U dwNodeIdx = 1;
    T_OMCI_LIST_NODE *pListNode = pList->pHead;
    for(; dwNodeIdx <= dwNodeIndex; dwNodeIdx++)
        pListNode = pListNode->pNext;

    return pListNode;
}
/**********************************************************************
* 函數名稱: OmciLocateListNode
* 功能描述: 查找鏈表中首個與pData知足函數fpCompareNode斷定關係的結點
* 輸入參數: T_OMCI_LIST* pList            :鏈表指針
*           VOID* pvData                  :待比較數據指針
*           CompareNodeFunc fpCompareNode :比較回調函數指針
* 輸出參數: NA
* 返 回 值: T_OMCI_LIST_NODE* 鏈表結點指針(未找到時返回NULL)
***********************************************************************/
/* 比較回調函數原型,用來自定義鏈表節點比較 */
typedef INT8U (*CompareNodeFunc)(VOID *pvNodeData, VOID *pvData, INT32U dwNodeDataSize);
T_OMCI_LIST_NODE* OmciLocateListNode(T_OMCI_LIST *pList, VOID *pvData, CompareNodeFunc fpCompareNode)
{
    CHECK_TRIPLE_POINTER(pList, pvData, fpCompareNode, NULL);
    CHECK_SINGLE_POINTER(pList->pHead, NULL);
    CHECK_SINGLE_POINTER(pList->pHead->pNext, NULL);

    T_OMCI_LIST_NODE *pListNode = pList->pHead->pNext;
    while(pListNode != pList->pHead)
    {
        if(0 == fpCompareNode(pListNode->pvNodeData, pvData, pList->dwNodeDataSize))
            return pListNode;

        pListNode = pListNode->pNext;
    }

    return NULL;
}

     可見,OmciLocateListNode接口本質上就是「遍歷+匹配」。要進行單純而強大的遍歷操做,可以使用OmciTraverseListNode接口。

/**********************************************************************
* 函數名稱: OmciTraverseListNode
* 功能描述: 鏈表結點遍歷函數,遍歷操做由fpTravNode指定
* 輸入參數: T_OMCI_LIST* pList      :鏈表指針
*           VOID* pvTravInfo        :遍歷操做回調函數所需信息
*                                    也可爲空,取決於回調函數具體實現
*           TravNodeFunc fpTravNode :遍歷操做回調函數指針
* 輸出參數: NA
* 返 回 值: LIST_STATUS
* 注意事項: 本函數可間接實現Print等操做,但不建議代替後者。
*           fpTravNode返回非0(OMCI_LIST_OK)值時停止遍歷
***********************************************************************/
typedef LIST_STATUS (*TravNodeFunc)(VOID *pvNode, VOID *pvTravInfo, INT32U dwNodeDataSize);
LIST_STATUS OmciTraverseListNode(T_OMCI_LIST *pList, VOID *pvTravInfo, TravNodeFunc fpTravNode)
{
    CHECK_DOUBLE_POINTER(pList, fpTravNode, OMCI_LIST_ERROR);
    CHECK_SINGLE_POINTER(pList->pHead, OMCI_LIST_ERROR);
    CHECK_SINGLE_POINTER(pList->pHead->pNext, OMCI_LIST_ERROR);

    T_OMCI_LIST_NODE *pListNode = pList->pHead->pNext;
    while(pListNode != pList->pHead)
    {
        T_OMCI_LIST_NODE *pTmpNode = pListNode->pNext; //fpTravNode內可能會銷燬結點pListNode
        if(OMCI_LIST_OK != fpTravNode(pListNode, pvTravInfo, pList->dwNodeDataSize))
            break;

        pListNode = pTmpNode;
    }

    return OMCI_LIST_OK;
}

     由於OmciAppendListNode和OmciPrependListNode已暗含「正序」和「逆序」的意思,故僅提供OmciTraverseListNode函數,而無需再增長逆序遍歷的接口(除非須要同時雙序遍歷)。

     經常須要打印輸出鏈表結點的數據域內容,而OmciTraverseListNode接口稍顯笨重。此時可以使用專門的打印接口OmciPrintListNode。

/**********************************************************************
* 函數名稱: OmciPrintListNode
* 功能描述: 打印輸出鏈表結點的數據域內容
* 輸入參數: T_OMCI_LIST* pList        :鏈表指針
*           PrintListFunc fpPrintList :打印回調函數指針
* 輸出參數: NA
* 返 回 值: LIST_STATUS
***********************************************************************/
/* 打印回調函數原型,用來自定義鏈表內容打印 */
typedef VOID (*PrintListFunc)(VOID *pNodeData, INT32U dwNodeNum);
LIST_STATUS OmciPrintListNode(T_OMCI_LIST *pList, PrintListFunc fpPrintList)
{
    CHECK_DOUBLE_POINTER(pList, fpPrintList, OMCI_LIST_ERROR);
    CHECK_SINGLE_POINTER(pList->pHead, OMCI_LIST_ERROR);
    CHECK_SINGLE_POINTER(pList->pHead->pNext, OMCI_LIST_ERROR);

    T_OMCI_LIST_NODE *pListNode = pList->pHead->pNext;
    while(pListNode != pList->pHead)
    {
        //具體打印格式交給回調函數靈活處理(可直接打印也可拷貝至本地處理後打印)
        fpPrintList(pListNode->pvNodeData, pList->dwNodeNum);
        pListNode = pListNode->pNext;
    }
    printf("\n");

    return OMCI_LIST_OK;
}

     對於CompareNodeFunc 和PrintListFunc,如下給出兩個範例:

/**********************************************************************
* 函數名稱: CompareNodeGeneric
* 功能描述: 通用鏈表結點內存比較
* 輸入參數: VOID *pvNodeData      :鏈表結點數據指針
*           VOID *pvData          :待比較外部數據指針
*           INT32U dwNodeDataSize :鏈表結點數據大小
* 輸出參數: NA
* 返 回 值: 0:Equal; !0:Unequal
* 注意事項: 比較長度爲結點數據字節數,即默認與外部數據大小一致
***********************************************************************/
INT8U CompareNodeGeneric(VOID *pvNodeData, VOID *pvData, INT32U dwNodeDataSize)
{
    CHECK_DOUBLE_POINTER(pvNodeData, pvData, 1);
    return memcmp(pvNodeData, pvData, dwNodeDataSize);
}
/**********************************************************************
* 函數名稱: PrintListWord
* 功能描述: 打印鏈表結點,結點數據域爲兩字節整數
* 輸入參數: VOID *pvNodeData   :鏈表節點數據指針
*           INT32U dwNodeNum  :鏈表節點數目
* 輸出參數: NA
* 返 回 值: VOID
* 注意事項: 僅做示例,未考慮字節序等問題。
***********************************************************************/
VOID PrintListWord(VOID *pvNodeData, INT32U dwNodeNum)
{
    CHECK_SINGLE_POINTER(pvNodeData, RETURN_VOID);
    printf("%d ", *((INT16U *)pvNodeData));
}

     最後,給出獲取鏈表結點及其數據的安全接口:

/**********************************************************************
* 函數名稱: OmciGetListNodeNum
* 功能描述: 獲取鏈表結點數目
* 輸入參數: T_OMCI_LIST *pList :鏈表指針
* 輸出參數: NA
* 返 回 值: INT32U 鏈表結點數目
***********************************************************************/
INT32U OmciGetListNodeNum(T_OMCI_LIST *pList)
{
    CHECK_SINGLE_POINTER(pList, 0);
    return (pList->dwNodeNum);
}

/**********************************************************************
* 函數名稱: OmciGetListHead/OmciGetListTail
* 功能描述: 獲取鏈表頭結點/尾結點指針
* 輸入參數: T_OMCI_LIST *pList :鏈表指針
***********************************************************************/
T_OMCI_LIST_NODE* OmciGetListHead(T_OMCI_LIST *pList)
{
    CHECK_SINGLE_POINTER(pList, NULL);
    return (pList->pHead);
}
T_OMCI_LIST_NODE* OmciGetListTail(T_OMCI_LIST *pList)
{
    CHECK_SINGLE_POINTER(pList, NULL);
    return (pList->pTail);
}

/**********************************************************************
* 函數名稱: OmciGetPrevNode/OmciGetNextNode
* 功能描述: 獲取鏈表指定結點的前驅結點/後繼結點指針
* 輸入參數: T_OMCI_LIST_NODE *pNode :指定結點的指針
***********************************************************************/
T_OMCI_LIST_NODE* OmciGetPrevNode(T_OMCI_LIST_NODE *pNode)
{
    CHECK_SINGLE_POINTER(pNode, NULL);
    return (pNode->pPrev);
}
T_OMCI_LIST_NODE* OmciGetNextNode(T_OMCI_LIST_NODE *pNode)
{
    CHECK_SINGLE_POINTER(pNode, NULL);
    return (pNode->pNext);
}

/**********************************************************************
* 函數名稱: OmciGetNodeData
* 功能描述: 獲取鏈表指定結點的數據域
* 輸入參數: T_OMCI_LIST_NODE *pNode :指定結點的指針
***********************************************************************/
VOID* OmciGetNodeData(T_OMCI_LIST_NODE *pNode)
{
    CHECK_DOUBLE_POINTER(pNode, pNode->pvNodeData, NULL);
    return (pNode->pvNodeData);
}

三  測試

     本節將對上文實現的鏈表操做接口進行測試,測試函數兼做使用示例。

#ifdef TEST_AND_EXAMPLE 

static LIST_STATUS TravPrintWord(VOID *pvNode, VOID *pvTravInfo, INT32U dwNodeDataSize)
{
    CHECK_SINGLE_POINTER(pvNode, OMCI_LIST_ERROR);
    T_OMCI_LIST_NODE *pNode = (T_OMCI_LIST_NODE *)pvNode;
    printf("%d ", *((INT16U *)GET_NODE_DATA(pNode)));
    return OMCI_LIST_OK;
}

T_OMCI_LIST gExampleList = {0};
VOID ListTestExample(VOID)
{   //本函數並不是嚴格意義上的測試函數,主要用做示例,且示例並不是最佳用法。
    INT8U ucTestIndex = 1;
    INT16U aTestListData[] = {11, 22, 33, 44, 55, 66};

    printf("\n<Test Case %u>: Initialization!\n", ucTestIndex++);
    OmciInitList(&gExampleList, sizeof(INT16U));
    printf("gExampleList=%p, pHead=%p, pTail=%p\n", &gExampleList,
           OmciGetListHead(&gExampleList), OmciGetListTail(&gExampleList));

    printf("\n<Test Case %u>: Append Node to List!\n", ucTestIndex++);
    OmciAppendListNode(&gExampleList, &aTestListData[0]);
    OmciAppendListNode(&gExampleList, &aTestListData[1]);
    OmciAppendListNode(&gExampleList, &aTestListData[2]);
    printf("OmciIsListEmpty=%u(0-Occupied; 1-Empty)\n", OmciIsListEmpty(&gExampleList));
    printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
    OmciPrintListNode(&gExampleList, PrintListWord);

    printf("\n<Test Case %u>: Insert Node to List!\n", ucTestIndex++);
    T_OMCI_LIST_NODE *pPrevNode = OmciGetListNodeByIndex(&gExampleList, 2);
    printf("NodeData2=%d\n", *((INT16U *)OmciGetNodeData(pPrevNode)));
    OmciInsertListNode(&gExampleList, pPrevNode, &aTestListData[4]);
    printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
    OmciPrintListNode(&gExampleList, PrintListWord);

    printf("\n<Test Case %u>: Remove Node from List!\n", ucTestIndex++);
    T_OMCI_LIST_NODE *pDeleteNode = OmciLocateListNode(&gExampleList, &aTestListData[1], CompareNodeGeneric);
    OmciRemoveListNode(&gExampleList, pDeleteNode);
    printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
    OmciPrintListNode(&gExampleList, PrintListWord);

    printf("\n<Test Case %u>: Clear List!\n", ucTestIndex++);
    OmciClearList(&gExampleList);
    printf("gExampleList=%p, pHead=%p, pTail=%p\n", &gExampleList,
           GET_HEAD_NODE(&gExampleList), GET_TAIL_NODE(&gExampleList));
    printf("OmciIsListEmpty=%u(0-Occupied; 1-Empty)\n", OmciIsListEmpty(&gExampleList));
    printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));

    printf("\n<Test Case %u>: Prepend Node to List!\n", ucTestIndex++);
    OmciPrependListNode(&gExampleList, &aTestListData[3]);
    OmciPrependListNode(&gExampleList, &aTestListData[4]);
    OmciPrependListNode(&gExampleList, &aTestListData[5]);
    printf("OmciIsListEmpty=%u(0-Occupied; 1-Empty)\n", OmciIsListEmpty(&gExampleList));
    printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
    OmciPrintListNode(&gExampleList, PrintListWord);

    T_OMCI_LIST_NODE *pListNode = NULL;
    LIST_ITER_LOOP(&gExampleList, pListNode)
    {
        printf("%d ", *((INT16U *)GET_NODE_DATA(pListNode)));
    }
    printf("\n");

    OmciTraverseListNode(&gExampleList, NULL, TravPrintWord);
    printf("\n");

    printf("\n<Test Case %u>: Destory List!\n", ucTestIndex++);
    OmciDestroyList(&gExampleList);
    printf("gExampleList=%p, pHead=%p, pTail=%p\n", &gExampleList,
           GET_HEAD_NODE(&gExampleList), GET_TAIL_NODE(&gExampleList));
    printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
    printf("GetListOccupation=%u(0-Occupied; 1-Empty; 2-Null)\n", GetListOccupation(&gExampleList));
    return;
}

#endif

     在上述測試代碼中,Prepend或Append結點的代碼若用OmciInsertListNode實現,以下:

OmciInsertListNode(&gExampleList, OmciGetListHead(&gExampleList), &aTestListData[3]);
OmciInsertListNode(&gExampleList, OmciGetListHead(&gExampleList), &aTestListData[4]);
OmciInsertListNode(&gExampleList, OmciGetListHead(&gExampleList), &aTestListData[5]);
//Or
OmciInsertListNode(&gExampleList, OmciGetListTail(&gExampleList), &aTestListData[1]);
OmciInsertListNode(&gExampleList, OmciGetListTail(&gExampleList), &aTestListData[2]);
OmciInsertListNode(&gExampleList, OmciGetListTail(&gExampleList), &aTestListData[3]);

     測試結果以下所示:

<Test Case 1>: Initialization!
gExampleList=0x804bc8c, pHead=0x8b39010, pTail=0x8b39010

<Test Case 2>: Append Node to List!
OmciIsListEmpty=0(0-Occupied; 1-Empty)
gExampleList NodeNum=3
11 22 33 

<Test Case 3>: Insert Node to List!
NodeData2=22
gExampleList NodeNum=4
11 22 55 33 

<Test Case 4>: Remove Node from List!
gExampleList NodeNum=3
11 55 33 

<Test Case 5>: Clear List!
gExampleList=0x804bc8c, pHead=0x8b39010, pTail=0x8b39010
OmciIsListEmpty=1(0-Occupied; 1-Empty)
gExampleList NodeNum=0

<Test Case 6>: Prepend Node to List!
OmciIsListEmpty=0(0-Occupied; 1-Empty)
gExampleList NodeNum=3
66 55 44 
66 55 44 
66 55 44 

<Test Case 7>: Destory List!
gExampleList=0x804bc8c, pHead=(nil), pTail=(nil)
gExampleList NodeNum=0
[GetListOccupation(140)]Null Pointer: pList->pHead!
GetListOccupation=2(0-Occupied; 1-Empty; 2-Null)

四  附註

     迷途指針(Dangling pointer,亦稱懸垂指針)和野指針(Wild pointer)

  • 迷途指針:所指向的對象被釋放或收回,但該指針仍指向原對象的內存地址(想象被強拆後無家可歸的人...)。
  • 野指針:指針在使用以前未進行必要的初始化(未顯式初始化的靜態指針不是野指針)。

     可見,迷途指針和野指針均指向不合法的對象,應禁止讀寫其指向的內存。野指針簡單且易於處理,如下主要討論迷途指針。

     在C語言中,當指針所指向的動態內存被顯式地釋放(free)後,該指針就成爲迷途指針。若經過迷途指針訪問或修改已釋放的動態分配內存,則可能引起難以排查的故障(尤爲當原對象內存分配做他用時)。若指針是函數內的自動變量,函數退出時會被自動銷燬;不然,最好在釋放動態內存後將該指針置空(NULL)。雖然將迷途指針從新置空的作法可能隱藏諸如double free之類的邏輯問題,但卻使得對它的讀寫錯誤更容易暴露(尤爲是在多線程環境中)。
     在C語言中,可經過下述兩種free替代版原本儘量避免迷途指針錯誤:

#define SAFE_FREE((pointer)) do{ \
    if(pointer != NULL){ \
        free(pointer); \
        pointer = NULL; \
}while(0);

void SafeFree(void **pointer)
{
    if(pointer != NULL)
    {
        free(*pointer);
        *pointer = NULL;
    }
}

     然而,當指向動態分配內存的指針存在多個副本且散佈程序各處時,該技術不會置空其餘指針變量,從而致使釋放後指針行爲的不一致。所以,編碼者應保證每一個指針都有其明確的用途和生存期。

     注意,由於C語言的值傳遞特性,現有的free庫函數內不可能將入參指針置空。若要達到置空的目的,必須傳入二級指針,如SafeFree。但SafeFree必然與其內存分配版本(如SafeAlloc)的入參類型不一致,這會增長使用者出錯的機率。而由上面的討論可知,即便置空當前入參指針,也沒法清除其副本。所以,最好由調用者自行決定如何置空。至於做者傾向於free仍是SafeFree,可參考《關於Linux系統basename函數缺陷的思考》一文,或者試想下逐級釋放的順序性。
     另外一種常見的迷途指針產生於試圖返回棧上分配的局部變量的地址。詳見《已釋放的棧內存》一文。

相關文章
相關標籤/搜索