zoukankan      html  css  js  c++  java
  • 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"!
    
    ", 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)!
    
    ", 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)!
    
    ", 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!
    ", 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!
    ",
    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!
    ", 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!
    ", 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'!
    ",
    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!
    ", 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'!
    ",
    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!
    ", 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'!
    ",
    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!
    ", 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!
    ", 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!
    ", 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("
    ");
    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("
    <Test Case %u>: Initialization!
    ", ucTestIndex++);
    18     OmciInitList(&gExampleList, sizeof(INT16U));
    19     printf("gExampleList=%p, pHead=%p, pTail=%p
    ", &gExampleList,
    20            OmciGetListHead(&gExampleList), OmciGetListTail(&gExampleList));
    21 
    22     printf("
    <Test Case %u>: Append Node to List!
    ", ucTestIndex++);
    23     OmciAppendListNode(&gExampleList, &aTestListData[0]);
    24     OmciAppendListNode(&gExampleList, &aTestListData[1]);
    25     OmciAppendListNode(&gExampleList, &aTestListData[2]);
    26     printf("OmciIsListEmpty=%u(0-Occupied; 1-Empty)
    ", OmciIsListEmpty(&gExampleList));
    27     printf("gExampleList NodeNum=%u
    ", OmciGetListNodeNum(&gExampleList));
    28     OmciPrintListNode(&gExampleList, PrintListWord);
    29 
    30     printf("
    <Test Case %u>: Insert Node to List!
    ", ucTestIndex++);
    31     T_OMCI_LIST_NODE *pPrevNode = OmciGetListNodeByIndex(&gExampleList, 2);
    32     printf("NodeData2=%d
    ", *((INT16U *)OmciGetNodeData(pPrevNode)));
    33     OmciInsertListNode(&gExampleList, pPrevNode, &aTestListData[4]);
    34     printf("gExampleList NodeNum=%u
    ", OmciGetListNodeNum(&gExampleList));
    35     OmciPrintListNode(&gExampleList, PrintListWord);
    36 
    37     printf("
    <Test Case %u>: Remove Node from List!
    ", ucTestIndex++);
    38     T_OMCI_LIST_NODE *pDeleteNode = OmciLocateListNode(&gExampleList, &aTestListData[1], CompareNodeGeneric);
    39     OmciRemoveListNode(&gExampleList, pDeleteNode);
    40     printf("gExampleList NodeNum=%u
    ", OmciGetListNodeNum(&gExampleList));
    41     OmciPrintListNode(&gExampleList, PrintListWord);
    42 
    43     printf("
    <Test Case %u>: Clear List!
    ", ucTestIndex++);
    44     OmciClearList(&gExampleList);
    45     printf("gExampleList=%p, pHead=%p, pTail=%p
    ", &gExampleList,
    46            GET_HEAD_NODE(&gExampleList), GET_TAIL_NODE(&gExampleList));
    47     printf("OmciIsListEmpty=%u(0-Occupied; 1-Empty)
    ", OmciIsListEmpty(&gExampleList));
    48     printf("gExampleList NodeNum=%u
    ", OmciGetListNodeNum(&gExampleList));
    49 
    50     printf("
    <Test Case %u>: Prepend Node to List!
    ", ucTestIndex++);
    51     OmciPrependListNode(&gExampleList, &aTestListData[3]);
    52     OmciPrependListNode(&gExampleList, &aTestListData[4]);
    53     OmciPrependListNode(&gExampleList, &aTestListData[5]);
    54     printf("OmciIsListEmpty=%u(0-Occupied; 1-Empty)
    ", OmciIsListEmpty(&gExampleList));
    55     printf("gExampleList NodeNum=%u
    ", 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("
    ");
    64 
    65     OmciTraverseListNode(&gExampleList, NULL, TravPrintWord);
    66     printf("
    ");
    67 
    68     printf("
    <Test Case %u>: Destory List!
    ", ucTestIndex++);
    69     OmciDestroyList(&gExampleList);
    70     printf("gExampleList=%p, pHead=%p, pTail=%p
    ", &gExampleList,
    71            GET_HEAD_NODE(&gExampleList), GET_TAIL_NODE(&gExampleList));
    72     printf("gExampleList NodeNum=%u
    ", OmciGetListNodeNum(&gExampleList));
    73     printf("GetListOccupation=%u(0-Occupied; 1-Empty; 2-Null)
    ", 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函数缺陷的思考》一文,或者试想下逐级释放的顺序性。
         另一种常见的迷途指针产生于试图返回栈上分配的局部变量的地址。详见《已释放的栈内存》一文。

  • 相关阅读:
    谈对信息增益与决策树的理解
    k近邻法
    感知机相关难点细解
    点到空间中面的距离
    统计学习方法中的标注问题
    Hoeffding不等式与泛化误差上界
    经验风险与期望风险
    先验概率与后验概率
    spring和springboot常用注解总结
    多环境下读取不同的配置文件
  • 原文地址:https://www.cnblogs.com/clover-toeic/p/3793131.html
Copyright © 2011-2022 走看看