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


 

說明

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

 

一  概念

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

     鏈表可分為單鏈表(Singly Linked List)、雙向鏈表(Doubly Linked List)和循環鏈表(Circular List)等。一個單鏈表結點僅包含一個指向其直接后繼結點的指針,因此當需要操作某個結點的直接前驅結點時,必須從單鏈表表頭開始查找。

     雙向鏈表和循環鏈表均為單鏈表的變體。通常創建雙向循環鏈表以綜合利用兩者的優點。

1.1 雙向鏈表

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

 

圖1 雙向鏈表的結點結構

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

 

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

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

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字段均指向頭結點。

     鏈表結點定義如下:

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

     相應地,鏈表定義如下:

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

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

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

 1 //鏈表函數返回狀態枚舉值
 2 typedef enum{
 3     OMCI_LIST_OK    = (INT8U)0,
 4     OMCI_LIST_ERROR = (INT8U)1
 5 }LIST_STATUS;
 6 
 7 //鏈表結點空閑情況枚舉值
 8 typedef enum{
 9     OMCI_LIST_OCCUPIED = (INT8U)0,
10     OMCI_LIST_EMPTY    = (INT8U)1,
11     OMCI_LIST_NULL     = (INT8U)2
12 }LIST_OCCUPATION;
13 
14 //BOOL型常量,適用於'Is'前綴函數
15 #define  OMCI_LIST_TRUE   (BOOL)1
16 #define  OMCI_LIST_FALSE  (BOOL)0

2.2 宏代碼

     為確保安全性,鏈表操作中需要進行大量的指針校驗。因此,定義幾個校驗空指針的宏,以簡化代碼篇幅:

 1 #define   FUNC_NAME    __FUNCTION__ //(__func__)
 2 
 3 /* 指針校驗宏 */
 4 //若無返回值則retVal置RETURN_VOID
 5 #define RETURN_VOID
 6 #define CHECK_SINGLE_POINTER(ptr1, retVal) do{\
 7     if(NULL == (ptr1)) 
 8     { \
 9         printf("[%s(%d)]Null Pointer: "#ptr1"!\n\r", FUNC_NAME, __LINE__); \
10         return retVal; \
11     } \
12 }while(0)
13 #define CHECK_DOUBLE_POINTER(ptr1, ptr2, retVal) do{\
14     if((NULL == (ptr1)) || (NULL == (ptr2))) \
15     { \
16         printf("[%s(%d)]Null Pointer: "#ptr1"(%p), "#ptr2"(%p)!\n\r", FUNC_NAME, __LINE__, ptr1, ptr2); \
17         return retVal; \
18     } \
19 }while(0)
20 #define CHECK_TRIPLE_POINTER(ptr1, ptr2, ptr3, retVal) do{\
21     if((NULL == (ptr1)) || (NULL == (ptr2)) || (NULL == (ptr3))) \
22     { \
23         printf("[%s(%d)]Null Pointer: "#ptr1"(%p), "#ptr2"(%p), "#ptr3"(%p)!\n\r", FUNC_NAME, __LINE__, ptr1, ptr2, ptr3); \
24         return retVal; \
25     } \
26 }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節函數接口中,以保證其安全性。使用者若能在外部杜絕空指針引用,則可添加條件編譯開關“剔除”這些校驗宏,以提高代碼執行效率。

 

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

 1 //創建結點為作為鏈表頭以生成雙向循環空鏈表
 2 #define OMCI_INIT_NODE(pNode) do{ \
 3     (pNode)->pNext = (pNode)->pPrev = (pNode); \
 4 }while(0)
 5 //"孤立"鏈表結點,避免通過該結點訪問其前驅和后繼結點(進而遍歷鏈表)
 6 #define OMCI_ISOL_NODE(pNode) do{ \
 7     (pNode)->pNext = (pNode)->pPrev = NULL; \
 8 }while(0)
 9 //判斷鏈表是否僅含頭結點
10 #define OMCI_LIST_WITH_HEAD(pHeadNode) do{ \
11     (((pHeadNode)->pPrev == (pHeadNode)) && ((pHeadNode->pNext == pHeadNode))); \
12 }while(0)
13 
14 //插入鏈表結點
15 #define OMCI_INSERT_NODE(prevNode, insertNode) do{ \
16     (insertNode)->pNext      = (prevNode)->pNext;  \
17     (insertNode)->pPrev      = (prevNode);         \
18     (prevNode)->pNext->pPrev = (insertNode);       \
19     (prevNode)->pNext        = (insertNode);       \
20 }while(0)
21 //刪除鏈表結點
22 #define OMCI_REMOVE_NODE(removeNode) do{ \
23     (removeNode)->pPrev->pNext = (removeNode)->pNext;  \
24     (removeNode)->pNext->pPrev = (removeNode)->pPrev;  \
25 }while(0)
26 
27 //獲取鏈表結點及其數據(不做安全性檢查)
28 #define GET_NODE_NUM(pList)      ((pList)->dwNodeNum)
29 #define GET_HEAD_NODE(pList)     ((pList)->pHead)
30 #define GET_TAIL_NODE(pList)     ((pList)->pTail)
31 #define GET_PREV_NODE(pNode)     ((pNode)->pPrev)
32 #define GET_NEXT_NODE(pNode)     ((pNode)->pNext)
33 #define GET_NODE_DATA(pNode)     ((pNode)->pvNodeData)
34 
35 //雙向循環鏈表遍歷校驗宏
36 #define LIST_ITER_CHECK(pList, retVal) do{\
37     CHECK_SINGLE_POINTER((pList), retVal); \
38     CHECK_SINGLE_POINTER((pList)->pHead, retVal); \
39     CHECK_SINGLE_POINTER((pList)->pHead->pNext, retVal); \
40 }while(0)
41 //雙向循環鏈表遍歷宏
42 //pList: 鏈表指針;pLoopNode: 鏈表結點,用作循環計數器;
43 //pTmpNode: 鏈表結點,用作刪除pLoopNode時臨時保存pLoopNode->pNext
44 //某些情況下采用遍歷宏代替OmciLocateListNode或OmciTraverseListNode函數可提高執行效率。
45 //如外部數據和結點數據需按共同的規則轉換時,采用遍歷宏可使外部數據不必重復轉換。
46 #define LIST_ITER_LOOP(pList, pLoopNode) \
47   for(pLoopNode = (pList)->pHead->pNext; \
48       pLoopNode != (pList)->pHead; \
49       pLoopNode = pLoopNode->pNext)
50 #define LIST_ITER_LOOP_SAFE(pList, pLoopNode, pTmpNode) \
51   for(pLoopNode = (pList)->pHead->pNext, pTmpNode = pLoopNode->pNext; \
52       pLoopNode != (pList)->pHead; \
53       pLoopNode = pTmpNode, pTmpNode = pLoopNode->pNext)

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

2.3 函數接口

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

     為簡便起見,下文中“XX指針”均表示指向XX的指針。

     創建新的鏈表結點:

 1 /**********************************************************************
 2 * 函數名稱: CreateListNode
 3 * 功能描述: 創建新的鏈表結點
 4 * 輸入參數: T_OMCI_LIST *pList :鏈表指針
 5 *           VOID *pvNodeData  :待插入的鏈表結點數據指針
 6 * 輸出參數: NA
 7 * 返 回 值: T_OMCI_LIST_NODE*
 8 ***********************************************************************/
 9 static T_OMCI_LIST_NODE *CreateListNode(T_OMCI_LIST *pList, VOID *pvNodeData)
10 {
11     T_OMCI_LIST_NODE *pInsertNode = (T_OMCI_LIST_NODE*)calloc((sizeof(T_OMCI_LIST_NODE)+pList->dwNodeDataSize), 1);
12     if(NULL == pInsertNode)
13     {
14         printf("[%s]pList(%p) failed to alloc for pInsertNode!\n", FUNC_NAME, pList);
15         return NULL;
16     }
17 
18     pInsertNode->pvNodeData = (INT8U *)pInsertNode + sizeof(T_OMCI_LIST_NODE);
19     if(NULL != pvNodeData)
20     {   //創建非頭結點時
21         memmove(pInsertNode->pvNodeData, pvNodeData, pList->dwNodeDataSize);
22     }
23 
24     return pInsertNode;
25 }

     刪除指定的鏈表結點:

 1 /**********************************************************************
 2 * 函數名稱: RemoveListNode
 3 * 功能描述: 刪除指定的鏈表結點(釋放結點內存並置其前驅后繼指針為NULL)
 4 * 輸入參數: T_OMCI_LIST *pList :鏈表指針
 5 *           VOID *pvNode       :待刪除的鏈表結點指針
 6 * 輸出參數: NA
 7 * 返 回 值: LIST_STATUS
 8 * 注意事項: 本函數未置待刪除結點指針為NULL,請避免訪問已刪除結點
 9 ***********************************************************************/
10 static LIST_STATUS RemoveListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE *pNode)
11 {
12     OMCI_ISOL_NODE(pNode);
13     free(pNode);  //釋放鏈表結點
14 
15     return OMCI_LIST_OK;
16 }

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

 1 /**********************************************************************
 2 * 函數名稱: DestroyListNode
 3 * 功能描述: 銷毀指定的鏈表結點(釋放結點內存並置結點指針為NULL)
 4 * 輸入參數: T_OMCI_LIST *pList :鏈表指針
 5 *           VOID **pNode       :待銷毀的鏈表結點指針的指針
 6 * 輸出參數: NA
 7 * 返 回 值: LIST_STATUS
 8 * 注意事項: 當指向待銷毀結點的指針存在多份拷貝且散布程序各處時(尤其當
 9 *           調用鏈未能保證**pNode指向原始結點時),無法徹底銷毀該結點
10 ***********************************************************************/
11 static LIST_STATUS DestroyListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE **pNode)
12 {
13     free(*pNode);  //釋放鏈表結點
14     *pNode = NULL;
15 
16     return OMCI_LIST_OK;
17 }

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

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

     有時可能需要獲知鏈表的確切占用情況(通常沒有必要),如不含任何結點、僅含頭結點或者還包含其他有用結點。GetListOccupation函數可滿足這一“吹毛求疵”的需求,其他情況應使用下文將要給出的判空函數OmciIsListEmpty。OmciIsListEmpty將不含任何結點和僅含頭結點均視為空鏈表,以隱藏內部細節。

 1 /**********************************************************************
 2 * 函數名稱: GetListOccupation
 3 * 功能描述: 獲取鏈表占用情況
 4 * 輸入參數: T_OMCI_LIST *pList :鏈表指針
 5 * 輸出參數: NA
 6 * 返 回 值: LIST_OCCUPATION
 7 * 注意事項: 本函數僅用於內部測試。
 8 ***********************************************************************/
 9 static LIST_OCCUPATION GetListOccupation(T_OMCI_LIST *pList)
10 {
11     CHECK_SINGLE_POINTER(pList, OMCI_LIST_NULL);
12     CHECK_SINGLE_POINTER(pList->pHead, OMCI_LIST_NULL);
13 
14     return (0 == pList->dwNodeNum) ? OMCI_LIST_EMPTY : OMCI_LIST_OCCUPIED;
15 }

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

2.3.1 鏈表操作

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

 1 /**********************************************************************
 2 * 函數名稱: OmciInitList
 3 * 功能描述: 鏈表初始化,產生空的雙向循環鏈表
 4 * 輸入參數: T_OMCI_LIST *pList    :鏈表指針
 5 *           INT32U dwNodeDataSize :鏈表結點保存的數據字節數
 6 * 輸出參數: NA
 7 * 返 回 值: LIST_STATUS
 8 ***********************************************************************/
 9 LIST_STATUS OmciInitList(T_OMCI_LIST *pList, INT32U dwNodeDataSize)
10 {
11     CHECK_SINGLE_POINTER(pList, OMCI_LIST_ERROR);
12 
13     if(0 == dwNodeDataSize)
14     {
15         printf("[%s]pList=%p, dwNodeDataSize=%uBytes, undesired initialization!\n",
16                FUNC_NAME, pList, dwNodeDataSize);
17         return OMCI_LIST_ERROR;
18     }
19     pList->dwNodeDataSize = dwNodeDataSize;  //給予重新修改結點數據大小的機會
20 
21     if(NULL != pList->pHead)
22     {
23         printf("[%s]pList(%p) has been initialized!\n", FUNC_NAME, pList);
24         return OMCI_LIST_OK;
25     }
26 
27     T_OMCI_LIST_NODE *pHeadNode = CreateListNode(pList, NULL);
28     if(NULL == pHeadNode)
29     {
30         printf("[%s]pList(%p) failed to create pHeadNode!\n", FUNC_NAME, pList);
31         return OMCI_LIST_ERROR;
32     }
33 
34     OMCI_INIT_NODE(pHeadNode);
35     pList->pHead = pList->pTail = pHeadNode;
36     pList->dwNodeNum = 0;
37 
38     return OMCI_LIST_OK;
39 }

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

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

     暫時不需要當前鏈表時,可清空鏈表除頭結點外的結點。這樣再次使用時無需初始化鏈表,直接插入結點即可。若確定不再需要當前鏈表時,可銷毀鏈表的所有結點。OmciClearList和OmciDestroyList函數分別完成鏈表的清空和銷毀。

 1 /**********************************************************************
 2 * 函數名稱: OmciClearList
 3 * 功能描述: 清空雙向循環鏈表除頭結點外的結點
 4 * 輸入參數: T_OMCI_LIST *pList :鏈表指針
 5 * 輸出參數: NA
 6 * 返 回 值: LIST_STATUS
 7 * 注意事項: 清空鏈表結點后,再次插入結點時不需要初始化鏈表。
 8 ***********************************************************************/
 9 LIST_STATUS OmciClearList(T_OMCI_LIST *pList)
10 {
11     LIST_ITER_CHECK(pList, OMCI_LIST_ERROR);
12 
13     T_OMCI_LIST_NODE *pNextNode, *pListNode = pList->pHead->pNext;
14     while(pListNode != pList->pHead)
15     {
16         pNextNode = pListNode->pNext;
17         RemoveListNode(pList, pListNode);
18         pListNode = pNextNode;
19     }
20 
21     OMCI_INIT_NODE(pList->pHead);
22     pList->pTail = pList->pHead;
23     pList->dwNodeNum = 0;
24 
25     return OMCI_LIST_OK;
26 }
27 /**********************************************************************
28 * 函數名稱: OmciDestroyList
29 * 功能描述: 銷毀雙向循環鏈表,包括頭結點
30 * 輸入參數: T_OMCI_LIST *pList :鏈表指針
31 * 輸出參數: NA
32 * 返 回 值: LIST_STATUS
33 * 注意事項: 銷毀鏈表后,再次插入結點時需要初始化鏈表。
34 ***********************************************************************/
35 LIST_STATUS OmciDestroyList(T_OMCI_LIST *pList)
36 {
37     LIST_ITER_CHECK(pList, OMCI_LIST_ERROR);
38 
39     T_OMCI_LIST_NODE *pNextNode, *pListNode = pList->pHead->pNext;
40     while(pListNode != pList->pHead)
41     {
42         pNextNode = pListNode->pNext;
43         DestroyListNode(pList, &pListNode);
44         pListNode = pNextNode;
45     }
46 
47     DestroyListNode(pList, &(pList->pHead)); //銷毀頭結點
48     pList->pTail = NULL;                     //尾結點指針置空
49     pList->dwNodeNum = 0;
50     pList->dwNodeDataSize = 0;
51 
52     return OMCI_LIST_OK;
53 }

     清空或銷毀鏈表后,OmciIsListEmpty函數的返回值將為邏輯真,表明當前鏈表為空。

 1 /**********************************************************************
 2 * 函數名稱: OmciIsListEmpty
 3 * 功能描述: 判斷鏈表是否為空(僅含頭結點或不含任何結點)
 4 * 輸入參數: T_OMCI_LIST *pList :鏈表指針
 5 * 輸出參數: NA
 6 * 返 回 值: BOOL
 7 ***********************************************************************/
 8 BOOL OmciIsListEmpty(T_OMCI_LIST *pList)
 9 {
10     CHECK_SINGLE_POINTER(pList, OMCI_LIST_TRUE);
11     CHECK_SINGLE_POINTER(pList->pHead, OMCI_LIST_TRUE);
12 
13     T_OMCI_LIST_NODE *pHeadNode = pList->pHead;
14     if((0 == pList->dwNodeNum) &&
15        (pHeadNode->pPrev == pHeadNode) && //冗余校驗以加強安全性
16        (pHeadNode->pNext == pHeadNode))
17     {
18         return OMCI_LIST_TRUE;
19     }
20     else
21     {
22         return OMCI_LIST_FALSE;
23     }
24 }

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

2.3.2 結點操作

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

 1 /**********************************************************************
 2 * 函數名稱: OmciPrependListNode
 3 * 功能描述: 在鏈表頭結點后逆序增加結點,尾結點恆為頭結點
 4 *           在頭結點指針pHead所指向結點和pHead->pNext所指向結點
 5 *           之間插入新結點,先插入的結點向右移動。遍歷鏈表時
 6 *           從pHead開始向右依次訪問至最先插入的結點,類似於棧。
 7 * 輸入參數: T_OMCI_LIST *pList :鏈表指針
 8 *           VOID *pvNodeData   :待插入的鏈表結點數據指針
 9 * 輸出參數: NA
10 * 返 回 值: LIST_STATUS
11 ***********************************************************************/
12 LIST_STATUS OmciPrependListNode(T_OMCI_LIST *pList, VOID *pvNodeData)
13 {
14     CHECK_DOUBLE_POINTER(pList, pvNodeData, OMCI_LIST_ERROR);
15 
16     if(0 == pList->dwNodeDataSize)
17     {
18         printf("[%s]pList=%p, dwNodeDataSize=0Bytes, probably uninitialized or initialized improperly. See 'OmciInitList'!\n",
19                FUNC_NAME, pList);
20         return OMCI_LIST_ERROR;
21     }
22     T_OMCI_LIST_NODE *pInsertNode = CreateListNode(pList, pvNodeData);
23     if(NULL == pInsertNode)
24     {
25         printf("[%s]pList(%p) failed to create pInsertNode!\n", FUNC_NAME, pList);
26         return OMCI_LIST_ERROR;
27     }
28 
29     OMCI_INSERT_NODE(pList->pHead, pInsertNode); //在鏈表頭結點后增加一個結點
30 
31     pList->dwNodeNum++;
32 
33     return OMCI_LIST_OK;
34 }
35 
36 /**********************************************************************
37 * 函數名稱: OmciAppendListNode
38 * 功能描述: 在鏈表頭結點后順序增加結點,新結點作為尾結點
39 *           在頭結點指針pHead所指向結點前(即尾結點后)插入新結點,
40 *           先插入的結點向左移動。遍歷鏈表時從pHead開始向右依次
41 *           訪問至最后插入的結點,類似於隊列。
42 *           雙向循環鏈表已保證pList->pTail(即pHead->pPrev)非空。
43 * 輸入參數: T_OMCI_LIST *pList :鏈表指針
44 *           VOID *pvNodeData   :待插入的鏈表結點數據指針
45 * 輸出參數: NA
46 * 返 回 值: LIST_STATUS
47 ***********************************************************************/
48 LIST_STATUS OmciAppendListNode(T_OMCI_LIST *pList, VOID *pvNodeData)
49 {
50     CHECK_DOUBLE_POINTER(pList, pvNodeData, OMCI_LIST_ERROR);
51 
52     if(0 == pList->dwNodeDataSize)
53     {
54         printf("[%s]pList=%p, dwNodeDataSize=0Bytes, probably uninitialized or initialized improperly. See 'OmciInitList'!\n",
55                FUNC_NAME, pList);
56         return OMCI_LIST_ERROR;
57     }
58 
59     T_OMCI_LIST_NODE *pInsertNode = CreateListNode(pList, pvNodeData);
60     if(NULL == pInsertNode)
61     {
62         printf("[%s]pList(%p) failed to create pInsertNode!\n", FUNC_NAME, pList);
63         return OMCI_LIST_ERROR;
64     }
65 
66     OMCI_INSERT_NODE(pList->pTail, pInsertNode); //在鏈表尾結點后增加一個結點
67     pList->pTail = pInsertNode;                  //新的尾結點指向當前添加的結點
68 
69     pList->dwNodeNum++;
70 
71     return OMCI_LIST_OK;
72 }

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

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

 1 /**********************************************************************
 2 * 函數名稱: OmciInsertListNode
 3 * 功能描述: 在鏈表中任意位置插入結點
 4 * 輸入參數: T_OMCI_LIST *pList          :鏈表指針
 5 *           T_OMCI_LIST_NODE *pPrevNode :待插入結點的前驅結點指針
 6 *           VOID *pvNodeData            :待插入結點的數據域指針
 7 * 輸出參數: NA
 8 * 返 回 值: LIST_STATUS
 9 * 注意事項: 若pPrevNode恆為頭結點或尾結點,請使用OmciPrependListNode
10 *           或OmciAppendListNode函數
11 ***********************************************************************/
12 LIST_STATUS OmciInsertListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE *pPrevNode, VOID *pvNodeData)
13 {
14     CHECK_TRIPLE_POINTER(pList, pPrevNode, pvNodeData, OMCI_LIST_ERROR);
15 
16     if(0 == pList->dwNodeDataSize)
17     {
18         printf("[%s]pList=%p, dwNodeDataSize=0Bytes, probably uninitialized or initialized improperly. See 'OmciInitList'!\n",
19                FUNC_NAME, pList);
20         return OMCI_LIST_ERROR;
21     }
22 
23     T_OMCI_LIST_NODE *pInsertNode = CreateListNode(pList, pvNodeData);
24     if(NULL == pInsertNode)
25     {
26         printf("[%s]pList(%p) failed to create pInsertNode!\n", FUNC_NAME, pList);
27         return OMCI_LIST_ERROR;
28     }
29 
30     OMCI_INSERT_NODE(pPrevNode, pInsertNode);
31     if(pPrevNode == pList->pTail)
32         pList->pTail = pInsertNode;
33 
34     pList->dwNodeNum++;
35 
36     return OMCI_LIST_OK;
37 }

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

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

 1 /**********************************************************************
 2 * 函數名稱: OmciRemoveListNode
 3 * 功能描述: 刪除雙向循環鏈表中除頭結點外的某一結點
 4 * 輸入參數: T_OMCI_LIST *pList      :鏈表指針
 5 *           T_OMCI_LIST_NODE *pNode :待刪除的鏈表結點指針
 6 * 輸出參數: NA
 7 * 返 回 值: LIST_STATUS
 8 ***********************************************************************/
 9 LIST_STATUS OmciRemoveListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE *pNode)
10 {
11     CHECK_DOUBLE_POINTER(pList, pNode, OMCI_LIST_ERROR);
12     CHECK_DOUBLE_POINTER(pNode->pPrev, pNode->pNext, OMCI_LIST_ERROR);
13 
14     if(0 == pList->dwNodeNum)
15     {
16         printf("[%s]pList(%p) has no node to be Removed!\n", FUNC_NAME, pList);
17         return OMCI_LIST_ERROR;
18     }
19 
20     OMCI_REMOVE_NODE(pNode);
21     if(pNode->pNext == pList->pHead)
22     {
23         pList->pTail = pNode->pPrev; //刪除尾結點
24     }
25 
26     RemoveListNode(pList, pNode);
27     pList->dwNodeNum--;
28 
29     return OMCI_LIST_OK;
30 }
31 /**********************************************************************
32 * 函數名稱: OmciDestroyListNode
33 * 功能描述: 銷毀雙向循環鏈表中除頭結點外的某一結點
34 * 輸入參數: T_OMCI_LIST *pList       :鏈表指針
35 *           T_OMCI_LIST_NODE **pNode :待銷毀的鏈表結點二級指針
36 * 輸出參數: NA
37 * 返 回 值: LIST_STATUS
38 ***********************************************************************/
39 LIST_STATUS OmciDestroyListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE **pNode)
40 {
41     CHECK_DOUBLE_POINTER(pList, pNode, OMCI_LIST_ERROR);
42     CHECK_SINGLE_POINTER(*pNode, OMCI_LIST_ERROR);
43 
44     if(0 == pList->dwNodeNum)
45     {
46         printf("[%s]pList(%p) has no node to be Removed!\n", FUNC_NAME, pList);
47         return OMCI_LIST_ERROR;
48     }
49 
50     OMCI_REMOVE_NODE(*pNode);
51     if((*pNode)->pNext == pList->pHead)
52     {
53         pList->pTail = (*pNode)->pPrev; //刪除尾結點
54     }
55 
56     DestroyListNode(pList, pNode);
57     pList->dwNodeNum--;
58 
59     return OMCI_LIST_OK;
60 }

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

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

 1 /**********************************************************************
 2 * 函數名稱: OmciGetListNodeByIndex
 3 * 功能描述: 獲取鏈表中指定序號的結點(按頭結點后繼方向排序)
 4 * 輸入參數: T_OMCI_LIST* pList :鏈表指針
 5 *           INT32U dwNodeIndex :結點序號(從1開始)
 6 * 輸出參數: NA
 7 * 返 回 值: T_OMCI_LIST_NODE* 鏈表結點指針(空表返回NULL)
 8 ***********************************************************************/
 9 T_OMCI_LIST_NODE* OmciGetListNodeByIndex(T_OMCI_LIST *pList, INT32U dwNodeIndex)
10 {
11     CHECK_SINGLE_POINTER(pList, NULL);
12     
13     if(0 == dwNodeIndex)
14         return pList->pHead;  //也可返回NULL
15     if(dwNodeIndex >= pList->dwNodeNum)
16         return pList->pTail;
17 
18     INT32U dwNodeIdx = 1;
19     T_OMCI_LIST_NODE *pListNode = pList->pHead;
20     for(; dwNodeIdx <= dwNodeIndex; dwNodeIdx++)
21         pListNode = pListNode->pNext;
22 
23     return pListNode;
24 }
25 /**********************************************************************
26 * 函數名稱: OmciLocateListNode
27 * 功能描述: 查找鏈表中首個與pData滿足函數fpCompareNode判定關系的結點
28 * 輸入參數: T_OMCI_LIST* pList            :鏈表指針
29 *           VOID* pvData                  :待比較數據指針
30 *           CompareNodeFunc fpCompareNode :比較回調函數指針
31 * 輸出參數: NA
32 * 返 回 值: T_OMCI_LIST_NODE* 鏈表結點指針(未找到時返回NULL)
33 ***********************************************************************/
34 /* 比較回調函數原型,用來自定義鏈表節點比較 */
35 typedef INT8U (*CompareNodeFunc)(VOID *pvNodeData, VOID *pvData, INT32U dwNodeDataSize);
36 T_OMCI_LIST_NODE* OmciLocateListNode(T_OMCI_LIST *pList, VOID *pvData, CompareNodeFunc fpCompareNode)
37 {
38     CHECK_TRIPLE_POINTER(pList, pvData, fpCompareNode, NULL);
39     CHECK_SINGLE_POINTER(pList->pHead, NULL);
40     CHECK_SINGLE_POINTER(pList->pHead->pNext, NULL);
41 
42     T_OMCI_LIST_NODE *pListNode = pList->pHead->pNext;
43     while(pListNode != pList->pHead)
44     {
45         if(0 == fpCompareNode(pListNode->pvNodeData, pvData, pList->dwNodeDataSize))
46             return pListNode;
47 
48         pListNode = pListNode->pNext;
49     }
50 
51     return NULL;
52 }

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

 1 /**********************************************************************
 2 * 函數名稱: OmciTraverseListNode
 3 * 功能描述: 鏈表結點遍歷函數,遍歷操作由fpTravNode指定
 4 * 輸入參數: T_OMCI_LIST* pList      :鏈表指針
 5 *           VOID* pvTravInfo        :遍歷操作回調函數所需信息
 6 *                                    也可為空,取決於回調函數具體實現
 7 *           TravNodeFunc fpTravNode :遍歷操作回調函數指針
 8 * 輸出參數: NA
 9 * 返 回 值: LIST_STATUS
10 * 注意事項: 本函數可間接實現Print等操作,但不建議代替后者。
11 *           fpTravNode返回非0(OMCI_LIST_OK)值時中止遍歷
12 ***********************************************************************/
13 typedef LIST_STATUS (*TravNodeFunc)(VOID *pvNode, VOID *pvTravInfo, INT32U dwNodeDataSize);
14 LIST_STATUS OmciTraverseListNode(T_OMCI_LIST *pList, VOID *pvTravInfo, TravNodeFunc fpTravNode)
15 {
16     CHECK_DOUBLE_POINTER(pList, fpTravNode, OMCI_LIST_ERROR);
17     CHECK_SINGLE_POINTER(pList->pHead, OMCI_LIST_ERROR);
18     CHECK_SINGLE_POINTER(pList->pHead->pNext, OMCI_LIST_ERROR);
19 
20     T_OMCI_LIST_NODE *pListNode = pList->pHead->pNext;
21     while(pListNode != pList->pHead)
22     {
23         T_OMCI_LIST_NODE *pTmpNode = pListNode->pNext; //fpTravNode內可能會銷毀結點pListNode
24         if(OMCI_LIST_OK != fpTravNode(pListNode, pvTravInfo, pList->dwNodeDataSize))
25             break;
26 
27         pListNode = pTmpNode;
28     }
29 
30     return OMCI_LIST_OK;
31 }

     因為OmciAppendListNode和OmciPrependListNode已暗含“正序”和“逆序”的意思,故僅提供OmciTraverseListNode函數,而無需再增加逆序遍歷的接口(除非需要同時雙序遍歷)。

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

 1 /**********************************************************************
 2 * 函數名稱: OmciPrintListNode
 3 * 功能描述: 打印輸出鏈表結點的數據域內容
 4 * 輸入參數: T_OMCI_LIST* pList        :鏈表指針
 5 *           PrintListFunc fpPrintList :打印回調函數指針
 6 * 輸出參數: NA
 7 * 返 回 值: LIST_STATUS
 8 ***********************************************************************/
 9 /* 打印回調函數原型,用來自定義鏈表內容打印 */
10 typedef VOID (*PrintListFunc)(VOID *pNodeData, INT32U dwNodeNum);
11 LIST_STATUS OmciPrintListNode(T_OMCI_LIST *pList, PrintListFunc fpPrintList)
12 {
13     CHECK_DOUBLE_POINTER(pList, fpPrintList, OMCI_LIST_ERROR);
14     CHECK_SINGLE_POINTER(pList->pHead, OMCI_LIST_ERROR);
15     CHECK_SINGLE_POINTER(pList->pHead->pNext, OMCI_LIST_ERROR);
16 
17     T_OMCI_LIST_NODE *pListNode = pList->pHead->pNext;
18     while(pListNode != pList->pHead)
19     {
20         //具體打印格式交給回調函數靈活處理(可直接打印也可拷貝至本地處理后打印)
21         fpPrintList(pListNode->pvNodeData, pList->dwNodeNum);
22         pListNode = pListNode->pNext;
23     }
24     printf("\n");
25 
26     return OMCI_LIST_OK;
27 }

     對於CompareNodeFunc 和PrintListFunc,以下給出兩個范例:

 1 /**********************************************************************
 2 * 函數名稱: CompareNodeGeneric
 3 * 功能描述: 通用鏈表結點內存比較
 4 * 輸入參數: VOID *pvNodeData      :鏈表結點數據指針
 5 *           VOID *pvData          :待比較外部數據指針
 6 *           INT32U dwNodeDataSize :鏈表結點數據大小
 7 * 輸出參數: NA
 8 * 返 回 值: 0:Equal; !0:Unequal
 9 * 注意事項: 比較長度為結點數據字節數,即默認與外部數據大小一致
10 ***********************************************************************/
11 INT8U CompareNodeGeneric(VOID *pvNodeData, VOID *pvData, INT32U dwNodeDataSize)
12 {
13     CHECK_DOUBLE_POINTER(pvNodeData, pvData, 1);
14     return memcmp(pvNodeData, pvData, dwNodeDataSize);
15 }
16 /**********************************************************************
17 * 函數名稱: PrintListWord 18 * 功能描述: 打印鏈表結點,結點數據域為兩字節整數 19 * 輸入參數: VOID *pvNodeData :鏈表節點數據指針 20 * INT32U dwNodeNum :鏈表節點數目 21 * 輸出參數: NA 22 * 返 回 值: VOID 23 * 注意事項: 僅作示例,未考慮字節序等問題。 24 ***********************************************************************/
25 VOID PrintListWord(VOID *pvNodeData, INT32U dwNodeNum)
26 {
27     CHECK_SINGLE_POINTER(pvNodeData, RETURN_VOID);
28     printf("%d ", *((INT16U *)pvNodeData));
29 }

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

 1 /**********************************************************************
 2 * 函數名稱: OmciGetListNodeNum
 3 * 功能描述: 獲取鏈表結點數目
 4 * 輸入參數: T_OMCI_LIST *pList :鏈表指針
 5 * 輸出參數: NA
 6 * 返 回 值: INT32U 鏈表結點數目
 7 ***********************************************************************/
 8 INT32U OmciGetListNodeNum(T_OMCI_LIST *pList)
 9 {
10     CHECK_SINGLE_POINTER(pList, 0);
11     return (pList->dwNodeNum);
12 }
13 
14 /**********************************************************************
15 * 函數名稱: OmciGetListHead/OmciGetListTail
16 * 功能描述: 獲取鏈表頭結點/尾結點指針
17 * 輸入參數: T_OMCI_LIST *pList :鏈表指針
18 ***********************************************************************/
19 T_OMCI_LIST_NODE* OmciGetListHead(T_OMCI_LIST *pList)
20 {
21     CHECK_SINGLE_POINTER(pList, NULL);
22     return (pList->pHead);
23 }
24 T_OMCI_LIST_NODE* OmciGetListTail(T_OMCI_LIST *pList)
25 {
26     CHECK_SINGLE_POINTER(pList, NULL);
27     return (pList->pTail);
28 }
29 
30 /**********************************************************************
31 * 函數名稱: OmciGetPrevNode/OmciGetNextNode
32 * 功能描述: 獲取鏈表指定結點的前驅結點/后繼結點指針
33 * 輸入參數: T_OMCI_LIST_NODE *pNode :指定結點的指針
34 ***********************************************************************/
35 T_OMCI_LIST_NODE* OmciGetPrevNode(T_OMCI_LIST_NODE *pNode)
36 {
37     CHECK_SINGLE_POINTER(pNode, NULL);
38     return (pNode->pPrev);
39 }
40 T_OMCI_LIST_NODE* OmciGetNextNode(T_OMCI_LIST_NODE *pNode)
41 {
42     CHECK_SINGLE_POINTER(pNode, NULL);
43     return (pNode->pNext);
44 }
45 
46 /**********************************************************************
47 * 函數名稱: OmciGetNodeData
48 * 功能描述: 獲取鏈表指定結點的數據域
49 * 輸入參數: T_OMCI_LIST_NODE *pNode :指定結點的指針
50 ***********************************************************************/
51 VOID* OmciGetNodeData(T_OMCI_LIST_NODE *pNode)
52 {
53     CHECK_DOUBLE_POINTER(pNode, pNode->pvNodeData, NULL);
54     return (pNode->pvNodeData);
55 }

 


三  測試

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

 1 #ifdef TEST_AND_EXAMPLE 
 2 
 3 static LIST_STATUS TravPrintWord(VOID *pvNode, VOID *pvTravInfo, INT32U dwNodeDataSize)
 4 {
 5     CHECK_SINGLE_POINTER(pvNode, OMCI_LIST_ERROR);
 6     T_OMCI_LIST_NODE *pNode = (T_OMCI_LIST_NODE *)pvNode;
 7     printf("%d ", *((INT16U *)GET_NODE_DATA(pNode)));
 8     return OMCI_LIST_OK;
 9 }
10 
11 T_OMCI_LIST gExampleList = {0};
12 VOID ListTestExample(VOID)
13 {   //本函數並非嚴格意義上的測試函數,主要用作示例,且示例並非最佳用法。
14     INT8U ucTestIndex = 1;
15     INT16U aTestListData[] = {11, 22, 33, 44, 55, 66};
16 
17     printf("\n<Test Case %u>: Initialization!\n", ucTestIndex++);
18     OmciInitList(&gExampleList, sizeof(INT16U));
19     printf("gExampleList=%p, pHead=%p, pTail=%p\n", &gExampleList,
20            OmciGetListHead(&gExampleList), OmciGetListTail(&gExampleList));
21 
22     printf("\n<Test Case %u>: Append Node to List!\n", ucTestIndex++);
23     OmciAppendListNode(&gExampleList, &aTestListData[0]);
24     OmciAppendListNode(&gExampleList, &aTestListData[1]);
25     OmciAppendListNode(&gExampleList, &aTestListData[2]);
26     printf("OmciIsListEmpty=%u(0-Occupied; 1-Empty)\n", OmciIsListEmpty(&gExampleList));
27     printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
28     OmciPrintListNode(&gExampleList, PrintListWord);
29 
30     printf("\n<Test Case %u>: Insert Node to List!\n", ucTestIndex++);
31     T_OMCI_LIST_NODE *pPrevNode = OmciGetListNodeByIndex(&gExampleList, 2);
32     printf("NodeData2=%d\n", *((INT16U *)OmciGetNodeData(pPrevNode)));
33     OmciInsertListNode(&gExampleList, pPrevNode, &aTestListData[4]);
34     printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
35     OmciPrintListNode(&gExampleList, PrintListWord);
36 
37     printf("\n<Test Case %u>: Remove Node from List!\n", ucTestIndex++);
38     T_OMCI_LIST_NODE *pDeleteNode = OmciLocateListNode(&gExampleList, &aTestListData[1], CompareNodeGeneric);
39     OmciRemoveListNode(&gExampleList, pDeleteNode);
40     printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
41     OmciPrintListNode(&gExampleList, PrintListWord);
42 
43     printf("\n<Test Case %u>: Clear List!\n", ucTestIndex++);
44     OmciClearList(&gExampleList);
45     printf("gExampleList=%p, pHead=%p, pTail=%p\n", &gExampleList,
46            GET_HEAD_NODE(&gExampleList), GET_TAIL_NODE(&gExampleList));
47     printf("OmciIsListEmpty=%u(0-Occupied; 1-Empty)\n", OmciIsListEmpty(&gExampleList));
48     printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
49 
50     printf("\n<Test Case %u>: Prepend Node to List!\n", ucTestIndex++);
51     OmciPrependListNode(&gExampleList, &aTestListData[3]);
52     OmciPrependListNode(&gExampleList, &aTestListData[4]);
53     OmciPrependListNode(&gExampleList, &aTestListData[5]);
54     printf("OmciIsListEmpty=%u(0-Occupied; 1-Empty)\n", OmciIsListEmpty(&gExampleList));
55     printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
56     OmciPrintListNode(&gExampleList, PrintListWord);
57 
58     T_OMCI_LIST_NODE *pListNode = NULL;
59     LIST_ITER_LOOP(&gExampleList, pListNode)
60     {
61         printf("%d ", *((INT16U *)GET_NODE_DATA(pListNode)));
62     }
63     printf("\n");
64 
65     OmciTraverseListNode(&gExampleList, NULL, TravPrintWord);
66     printf("\n");
67 
68     printf("\n<Test Case %u>: Destory List!\n", ucTestIndex++);
69     OmciDestroyList(&gExampleList);
70     printf("gExampleList=%p, pHead=%p, pTail=%p\n", &gExampleList,
71            GET_HEAD_NODE(&gExampleList), GET_TAIL_NODE(&gExampleList));
72     printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
73     printf("GetListOccupation=%u(0-Occupied; 1-Empty; 2-Null)\n", GetListOccupation(&gExampleList));
74     return;
75 }
76 
77 #endif

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

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

     測試結果如下所示:

 1 <Test Case 1>: Initialization!
 2 gExampleList=0x804bc8c, pHead=0x8b39010, pTail=0x8b39010
 3 
 4 <Test Case 2>: Append Node to List!
 5 OmciIsListEmpty=0(0-Occupied; 1-Empty)
 6 gExampleList NodeNum=3
 7 11 22 33 
 8 
 9 <Test Case 3>: Insert Node to List!
10 NodeData2=22
11 gExampleList NodeNum=4
12 11 22 55 33 
13 
14 <Test Case 4>: Remove Node from List!
15 gExampleList NodeNum=3
16 11 55 33 
17 
18 <Test Case 5>: Clear List!
19 gExampleList=0x804bc8c, pHead=0x8b39010, pTail=0x8b39010
20 OmciIsListEmpty=1(0-Occupied; 1-Empty)
21 gExampleList NodeNum=0
22 
23 <Test Case 6>: Prepend Node to List!
24 OmciIsListEmpty=0(0-Occupied; 1-Empty)
25 gExampleList NodeNum=3
26 66 55 44 
27 66 55 44 
28 66 55 44 
29 
30 <Test Case 7>: Destory List!
31 gExampleList=0x804bc8c, pHead=(nil), pTail=(nil)
32 gExampleList NodeNum=0
33 [GetListOccupation(140)]Null Pointer: pList->pHead!
34 GetListOccupation=2(0-Occupied; 1-Empty; 2-Null)

 

 

四  附注

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

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

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

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

 1 #define SAFE_FREE((pointer)) do{ \
 2     if(pointer != NULL){ \
 3         free(pointer); \
 4         pointer = NULL; \
 5 }while(0);
 6 
 7 void SafeFree(void **pointer)
 8 {
 9     if(pointer != NULL)
10     {
11         free(*pointer);
12         *pointer = NULL;
13     }
14 }

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

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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM