说明

相比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),
  4. OMCI_LIST_ERROR = (INT8U)
  5. }LIST_STATUS;
  6.  
  7. //链表结点空闲情况枚举值
  8. typedef enum{
  9. OMCI_LIST_OCCUPIED = (INT8U),
  10. OMCI_LIST_EMPTY = (INT8U),
  11. OMCI_LIST_NULL = (INT8U)
  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()
  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()
  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()

若待检查的指针中至少有一个指针为空时,校验宏打印所有待检查的指针值并退出。但其实现使得下面的语句在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()
  5. //"孤立"链表结点,避免通过该结点访问其前驱和后继结点(进而遍历链表)
  6. #define OMCI_ISOL_NODE(pNode) do{ \
  7. (pNode)->pNext = (pNode)->pPrev = NULL; \
  8. }while()
  9. //判断链表是否仅含头结点
  10. #define OMCI_LIST_WITH_HEAD(pHeadNode) do{ \
  11. (((pHeadNode)->pPrev == (pHeadNode)) && ((pHeadNode->pNext == pHeadNode))); \
  12. }while()
  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()
  21. //删除链表结点
  22. #define OMCI_REMOVE_NODE(removeNode) do{ \
  23. (removeNode)->pPrev->pNext = (removeNode)->pNext; \
  24. (removeNode)->pNext->pPrev = (removeNode)->pPrev; \
  25. }while()
  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()
  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), );
  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 ( == 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( == 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 = ;
  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 = ;
  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 = ;
  50. pList->dwNodeDataSize = ;
  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(( == 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( == 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( == 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( == 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( == 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( == 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( == dwNodeIndex)
  14. return pList->pHead; //也可返回NULL
  15. if(dwNodeIndex >= pList->dwNodeNum)
  16. return pList->pTail;
  17.  
  18. INT32U dwNodeIdx = ;
  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( == 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, );
  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, );
  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 = {};
  12. VOID ListTestExample(VOID)
  13. { //本函数并非严格意义上的测试函数,主要用作示例,且示例并非最佳用法。
  14. INT8U ucTestIndex = ;
  15. INT16U aTestListData[] = {, , , , , };
  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[]);
  24. OmciAppendListNode(&gExampleList, &aTestListData[]);
  25. OmciAppendListNode(&gExampleList, &aTestListData[]);
  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, );
  32. printf("NodeData2=%d\n", *((INT16U *)OmciGetNodeData(pPrevNode)));
  33. OmciInsertListNode(&gExampleList, pPrevNode, &aTestListData[]);
  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[], 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[]);
  52. OmciPrependListNode(&gExampleList, &aTestListData[]);
  53. OmciPrependListNode(&gExampleList, &aTestListData[]);
  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[]);
  2. OmciInsertListNode(&gExampleList, OmciGetListHead(&gExampleList), &aTestListData[]);
  3. OmciInsertListNode(&gExampleList, OmciGetListHead(&gExampleList), &aTestListData[]);
  4. //Or
  5. OmciInsertListNode(&gExampleList, OmciGetListTail(&gExampleList), &aTestListData[]);
  6. OmciInsertListNode(&gExampleList, OmciGetListTail(&gExampleList), &aTestListData[]);
  7. OmciInsertListNode(&gExampleList, OmciGetListTail(&gExampleList), &aTestListData[]);

测试结果如下所示:

  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();
  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函数缺陷的思考》一文,或者试想下逐级释放的顺序性。
     另一种常见的迷途指针产生于试图返回栈上分配的局部变量的地址。详见《已释放的栈内存》一文。

C语言通用双向循环链表操作函数集的更多相关文章

  1. c语言实现--双向循环链表操作

    1,双向链表相当于两个单向循环链表. 2,双向链表的结点定义. 1 struct DULNode 2 { 3 int data; 4 struct DULNode * prior; 5 struct ...

  2. Linux内核中的通用双向循环链表

    开发中接触Linux越来越多,休息放松之余,免不了翻看翻看神秘的Linux的内核.看到双向链表时,觉得挺有意思的,此文记下. 作为众多基础数据结构中的一员,双向循环链表在各种“教科书”中的实现是相当的 ...

  3. C语言字符串操作函数集

    1)字符串操作 strcpy(p, p1) 复制字符串 strncpy(p, p1, n) 复制指定长度字符串 strcat(p, p1) 附加字符串 strncat(p, p1, n) 附加指定长度 ...

  4. IIC的标准操作函数集(C51)包含C和H文件

    /********************************************************************* 头文件名 VIIC_C51.H 这个头文件对应的库是VII ...

  5. c语言实现--单向循环链表操作

    1,什么叫单向循环链表.单向循环链表是指在单链表的基础上,表的最后一个元素指向链表头结点,不再是为空. 2,由图可知,单向循环链表的判断条件不再是表为空了,而变成了是否到表头. 3,链表的结点表示 1 ...

  6. postgis几何操作函数集

    管理操作函数 AddGeometryColumn - Adds a geometry column to an existing table of attributes. By default use ...

  7. C语言实现双向循环链表

    #include <stdio.h> #include <stdlib.h> #include <string.h> struct list_head { stru ...

  8. 1.Go语言copy函数、sort排序、双向链表、list操作和双向循环链表

    1.1.copy函数 通过copy函数可以把一个切片内容复制到另一个切片中 (1)把长切片拷贝到短切片中 package main import "fmt" func main() ...

  9. (C语言版)链表(四)——实现双向循环链表创建、插入、删除、释放内存等简单操作

    双向循环链表是基于双向链表的基础上实现的,和双向链表的操作差不多,唯一的区别就是它是个循环的链表,通过每个节点的两个指针把它们扣在一起组成一个环状.所以呢,每个节点都有前驱节点和后继节点(包括头节点和 ...

随机推荐

  1. weblogic部署项目包,报空指针错误

    贴出 报错代码 <weblogic> <> <> <1479765377228> <BEA-240003> <Console enco ...

  2. MongoDB的数据库基本操作(一)

    查看当前数据库 MongoDB shell version:1.8.1connecting to:test> dbtest 查看全部数据库列表 >show dbsChatRoom    0 ...

  3. Dependency Injection

    Inversion of Control - Dependency Injection - Dependency Lookup loose coupling/maintainability/ late ...

  4. [CareerCup] 18.11 Maximum Subsquare 最大子方形

    18.11 Imagine you have a square matrix, where each cell (pixel) is either black or white. Design an ...

  5. Linux中下载、解压、安装文件

    一.将解压包发送到linux服务器上: 1.在windos上下载好压缩包文件后,通过winscp等SFTP客户端传送给linux 2.在linux中通过wget命令直接下载 #wget [选项] [下 ...

  6. HTML静态网页导航制作

    普通导航栏制作 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www ...

  7. CentOS 安装

    选择选择启动介质 Install or upgrade an existing system: 安装或升级现有的系统 install system with basic video driver: 安 ...

  8. 面试题-->写一个函数,返回一个数组中所有元素被第一个元素除的结果

    package com.rui.test; import java.util.Random; /** * @author poseidon * @version 1.0 * @date:2015年10 ...

  9. 图解Java内存回收机制

    在Java中,它的内存管理包括两方面:内存分配(创建Java对象的时候)和内存回收,这两方面工作都是由JVM自动完成的,降低了Java程序员的学习难度,避免了像C/C++直接操作内存的危险.但是,也正 ...

  10. IOS第11天(3:UIPickerView省市联动)

    ********* #import "ViewController.h" #import "Province.h" @interface ViewControl ...