FreeRTOS之xLIST

基础结构决定上层建筑。FreeRTOS作为实时操作系统,首先作为操作系统肯定重要任务就是任务调度。那么任务调度就涉及到多种任务状态的管理。比如:挂起的任务、就绪的任务、正在执行的任务。那么这些任务的管理就需要数据结构。可以采用数组来管理,但是任务是频繁在挂起、就绪、运行状态切换。因此数组需要来回移动元素,太低效了。同时每个任务列表的大小也不好定,定义大了浪费空间,定义小了空间不够还得拷贝数组。

要理解FreeRTOS的任务调度就必须先理解FreeRTOS的列表实现。下图为空List初始化状态,和插入第一个列表项状态及第二个列表项状态。

对应执行方法(开始直接理解插入列表项有点费解,画图就明白了):

void vListInitialise(List_t * const pxList)
void vListInsertEnd(List_t * const pxList,ListItem_t * const pxNewListItem)
void vListInsertEnd(List_t * const pxList,ListItem_t * const pxNewListItem)

在这里插入图片描述

为此需要为OS内核调度量身定做一种列表数据结构。该结构采用双向指针的链表结构。每个列表项有一个列表项指针指向前一个列表项。和一个列表项指针指向下一个列表项。同时每个列表项可以指向所属的列表或者指向空指针。这样在任务切换状态时候只要改列表项的所属列表指向就完成列表项移动。

首先申明列表的结构(因为列表项要指向所属列表)

//申明列表结构
struct xLIST;

然后定义列表结构体

//定义列表项结构
struct xLIST_ITEM
{
  //检测位点
  listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
  //列表项的值,告诉编译器不要优化,随时可能变化	
  configLIST_VOLATILE TickType_t xItemValue;  
  //指向下一个列表项	
  struct xLIST_ITEM * configLIST_VOLATILE pxNext;   
  //指向前一个列表项  
  struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; 
  //指向所属对象,主要指列表项所在的tcb
  void * pvOwner;
  //指向所属列表	
  struct xLIST * configLIST_VOLATILE pxContainer;    
  //检测位点
  listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE          
};
typedef struct xLIST_ITEM ListItem_t;    

在这里插入图片描述

再定义一个Mini的列表项结构体,这是给列表的End节点用的。Mini列表项没有所属对象和所属列表,节省8字节空间。

//定义列表项结构迷你版。End节点不需要所属对象指针和所属列表。为了少8个字节设计的
struct xMINI_LIST_ITEM
{
  //检测位点
  listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE 
  //列表项的值,默认设置类型最大值
  configLIST_VOLATILE TickType_t xItemValue;
  //指向下一个列表项	
  struct xLIST_ITEM * configLIST_VOLATILE pxNext;
  //指向前一个列表项  
  struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

在这里插入图片描述

然后定义列表结构体

//定义列表结构体,调度器基于该结构进行调度实现
typedef struct xLIST
{
  //检测位点
  listFIRST_LIST_INTEGRITY_CHECK_VALUE
  //该列表的元素个数	
  volatile UBaseType_t uxNumberOfItems;
  //通过他遍历列表。指向一个列表节点或终结点
  ListItem_t * configLIST_VOLATILE pxIndex;
  //终结点	
  MiniListItem_t xListEnd;                 
  //检测位点  
  listSECOND_LIST_INTEGRITY_CHECK_VALUE    
} List_t;

在这里插入图片描述

列表结构定义就是以上三个结构体。然后实现一下列表操作的api供操作列表。

//设置列表项所属对象。通常指向tcb
#define listSET_LIST_ITEM_OWNER(pxListItem, pxOwner)
//得到列表项所属,通常是得到列表所在的tcb
#define listGET_LIST_ITEM_OWNER(pxListItem) `
//得到列表项的值
#define listGET_LIST_ITEM_VALUE(pxListItem) 
//得到列表头部第一项的值
#define listGET_ITEM_VALUE_OF_HEAD_ENTRY(pxList)
//得到列表头部第一项
#define listGET_HEAD_ENTRY(pxList)
//得到当前列表项的下一项
#define listGET_NEXT(pxListItem)
//得到列表的终结点
#define listGET_END_MARKER(pxList)
//得到列表是否为空
#define listLIST_IS_EMPTY(pxList)
//得到列表项目个数
#define listCURRENT_LIST_LENGTH(pxList)
//得到下一个tcb
#define listGET_OWNER_OF_NEXT_ENTRY(pxTCB,pxList)
//把列表项从列表里面移除,返回列表数量
//pxItemToRemove:要异常的列表项
#define listREMOVE_ITEM(pxItemToRemove)

//用宏定义的把列表项插入列表结尾
//pxList:列表常量指针
//pxNewListItem:要插入的新列表项指针
#define listINSERT_END( pxList, pxNewListItem ) 


//得到列表第一项
#define listGET_OWNER_OF_HEAD_ENTRY( pxList )
//检测列表项是否在列表里
#define listIS_CONTAINED_WITHIN( pxList, pxListItem )
//得到列表项的容器对象
#define listLIST_ITEM_CONTAINER( pxListItem )
//检测列表的End节点是否设置为最大值
#define listLIST_IS_INITIALISED( pxList )
//初始化列表,列表在使用前必须被初始化
void vListInitialise( List_t * const pxList )
//初始化列表项目,列表项目使用前也要初始化
void vListInitialiseItem( ListItem_t * const pxItem )
//往列表里插入列表项,按值排序
//pxList:列表常量指针
//pxNewListItem:要插入的新列表项指针
void vListInsert( List_t * const pxList,ListItem_t * const pxNewListItem )

//往列表结尾加入列表项元素
//pxList:列表常量指针
//pxNewListItem:要插入的新列表项指针
void vListInsertEnd( List_t * const pxList,ListItem_t * const pxNewListItem )

//把列表项从列表里面移除,返回列表数量
//pxItemToRemove:要异常的列表项
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )

list.h的完整头文件

/*
	任务调度的基础数据结构,很重要。任务调度都依赖该列表操作。
*/

#ifndef LIST_H
#define LIST_H

#ifndef INC_FREERTOS_H
    #error "FreeRTOS.h must be included before list.h"
#endif

#ifndef configLIST_VOLATILE
    #define configLIST_VOLATILE
#endif 

//C++相关
#ifdef __cplusplus
    extern "C" {
#endif


//检测列表用的
#if ( configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES == 0 )
    /* Define the macros to do nothing. */
    #define listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
    #define listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE
    #define listFIRST_LIST_INTEGRITY_CHECK_VALUE
    #define listSECOND_LIST_INTEGRITY_CHECK_VALUE
    #define listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem )
    #define listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem )
    #define listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList )
    #define listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList )
    #define listTEST_LIST_ITEM_INTEGRITY( pxItem )
    #define listTEST_LIST_INTEGRITY( pxList )
#else /* if ( configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES == 0 ) */
    /* Define macros that add new members into the list structures. */
    #define listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE     TickType_t xListItemIntegrityValue1;
    #define listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE    TickType_t xListItemIntegrityValue2;
    #define listFIRST_LIST_INTEGRITY_CHECK_VALUE          TickType_t xListIntegrityValue1;
    #define listSECOND_LIST_INTEGRITY_CHECK_VALUE         TickType_t xListIntegrityValue2;

/* Define macros that set the new structure members to known values. */
    #define listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem )     ( pxItem )->xListItemIntegrityValue1 = pdINTEGRITY_CHECK_VALUE
    #define listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem )    ( pxItem )->xListItemIntegrityValue2 = pdINTEGRITY_CHECK_VALUE
    #define listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList )              ( pxList )->xListIntegrityValue1 = pdINTEGRITY_CHECK_VALUE
    #define listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList )              ( pxList )->xListIntegrityValue2 = pdINTEGRITY_CHECK_VALUE

/* Define macros that will assert if one of the structure members does not
 * contain its expected value. */
    #define listTEST_LIST_ITEM_INTEGRITY( pxItem )                      configASSERT( ( ( pxItem )->xListItemIntegrityValue1 == pdINTEGRITY_CHECK_VALUE ) && ( ( pxItem )->xListItemIntegrityValue2 == pdINTEGRITY_CHECK_VALUE ) )
    #define listTEST_LIST_INTEGRITY( pxList )                           configASSERT( ( ( pxList )->xListIntegrityValue1 == pdINTEGRITY_CHECK_VALUE ) && ( ( pxList )->xListIntegrityValue2 == pdINTEGRITY_CHECK_VALUE ) )
#endif /* configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES */


//申明列表结构
struct xLIST;

//定义列表项结构
struct xLIST_ITEM
{
	//检测位点
  listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
  //列表项的值,告诉编译器不要优化,随时可能变化	
  configLIST_VOLATILE TickType_t xItemValue;  
  //指向下一个列表项	
  struct xLIST_ITEM * configLIST_VOLATILE pxNext;   
  //指向前一个列表项  
  struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; 
	//指向所属对象,主要指列表项所在的tcb
  void * pvOwner;
  //指向所属列表	
  struct xLIST * configLIST_VOLATILE pxContainer;    
  //检测位点
	listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE          
};
typedef struct xLIST_ITEM ListItem_t;                   

//定义列表项结构迷你版。End节点不需要所属对象指针和所属列表。为了少8个字节设计的
struct xMINI_LIST_ITEM
{
  //检测位点
  listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE 
	//列表项的值,默认设置类型最大值
  configLIST_VOLATILE TickType_t xItemValue;
	//指向下一个列表项	
  struct xLIST_ITEM * configLIST_VOLATILE pxNext;
	//指向前一个列表项  
  struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

//定义列表结构体,调度器基于该结构进行调度实现
typedef struct xLIST
{
	//检测位点
  listFIRST_LIST_INTEGRITY_CHECK_VALUE
  //该列表的元素个数	
  volatile UBaseType_t uxNumberOfItems;
	//通过他遍历列表。指向一个列表节点或终结点
  ListItem_t * configLIST_VOLATILE pxIndex;
  //终结点	
  MiniListItem_t xListEnd;                 
  //检测位点  
	listSECOND_LIST_INTEGRITY_CHECK_VALUE    
} List_t;

//设置列表项所属对象。通常指向tcb
#define listSET_LIST_ITEM_OWNER(pxListItem, pxOwner)    (( pxListItem )->pvOwner = (void *)(pxOwner))

//得到列表项所属,通常是得到列表所在的tcb
#define listGET_LIST_ITEM_OWNER(pxListItem)             ((pxListItem)->pvOwner )

//设置列表项的值
#define listSET_LIST_ITEM_VALUE(pxListItem, xValue)     ((pxListItem)->xItemValue = (xValue))

//得到列表项的值
#define listGET_LIST_ITEM_VALUE(pxListItem)             ((pxListItem)->xItemValue)

//得到列表头部第一项的值
#define listGET_ITEM_VALUE_OF_HEAD_ENTRY(pxList)        (((pxList)->xListEnd).pxNext->xItemValue )

//得到列表头部第一项
#define listGET_HEAD_ENTRY(pxList)                      (((pxList)->xListEnd).pxNext)

//得到当前列表项的下一项
#define listGET_NEXT(pxListItem)                        ((pxListItem )->pxNext)

//得到列表的终结点
#define listGET_END_MARKER(pxList)                      ((ListItem_t const *) (&((pxList )->xListEnd)))

//得到列表是否为空
#define listLIST_IS_EMPTY(pxList)                       (((pxList)->uxNumberOfItems == (UBaseType_t )0) ? pdTRUE : pdFALSE)

//得到列表项目个数
#define listCURRENT_LIST_LENGTH(pxList)                 ((pxList)->uxNumberOfItems)

//得到下一个tcb
#define listGET_OWNER_OF_NEXT_ENTRY(pxTCB,pxList)                                           \
    {                                                                                       \
        List_t * const pxConstList = (pxList);                                              \
			  /*移动指针到下一个元素*/                                                             \
        (pxConstList)->pxIndex = (pxConstList)->pxIndex->pxNext;                            \
			   /*移动到最后了*/                                                                    \
        if((void *) (pxConstList)->pxIndex ==(void *) &((pxConstList)->xListEnd))           \
        {                                                                                   \
            (pxConstList)->pxIndex=(pxConstList)->pxIndex->pxNext;                          \
        }                                                                                   \
				/*得到TCB*/                                                                         \
        (pxTCB)=(pxConstList)->pxIndex->pvOwner;                                            \
    }

//把列表项从列表里面移除,返回列表数量
//pxItemToRemove:要异常的列表项
#define listREMOVE_ITEM(pxItemToRemove)                                                    \
    {                                                                                      \
        /*得到要移除列表项指向的列表指针*/                                                   \
        List_t * const pxList = (pxItemToRemove)->pxContainer;                             \
                                                                                           \
			  /*要移除列表项的后面节点的前一项指针指向要移除节点的前一个节点*/                       \
        ( pxItemToRemove)->pxNext->pxPrevious = (pxItemToRemove)->pxPrevious;              \
			  /*要移除列表项前一个节点的后一项指针指向要移除节点后一项*/                             \
        ( pxItemToRemove)->pxPrevious->pxNext = (pxItemToRemove)->pxNext;                  \
        /*确保索引指向一个有效的项目*/                                                       \
        if(pxList->pxIndex == (pxItemToRemove))                                            \
        {                                                                                  \
            pxList->pxIndex = (pxItemToRemove)->pxPrevious;                                \
        }                                                                                  \
        /*把要移除项的所属设置空指针,不属于任何列表*/                                         \
        (pxItemToRemove)->pxContainer = NULL;                                              \
				/*列表数量减1*/                                                                     \
        (pxList->uxNumberOfItems)--;                                                       \
    }

//用宏定义的把列表项插入列表结尾
//pxList:列表常量指针
//pxNewListItem:要插入的新列表项指针
#define listINSERT_END( pxList, pxNewListItem )                           \
    {                                                                     \
			 ListItem_t * const pxIndex = (pxList)->pxIndex;                    \
       listTEST_LIST_INTEGRITY((pxList));                                 \
       listTEST_LIST_ITEM_INTEGRITY((pxNewListItem));                     \
       (pxNewListItem)->pxNext = pxIndex;                                 \
       (pxNewListItem)->pxPrevious = pxIndex->pxPrevious;                 \
                                                                          \
       pxIndex->pxPrevious->pxNext = (pxNewListItem);                     \
       pxIndex->pxPrevious = (pxNewListItem);                             \
                                                                          \
       (pxNewListItem)->pxContainer = (pxList);                           \
                                                                          \
       ((pxList)->uxNumberOfItems)++;                                     \
    }

//得到列表第一项
#define listGET_OWNER_OF_HEAD_ENTRY( pxList )            ( ( &( ( pxList )->xListEnd ) )->pxNext->pvOwner )

//检测列表项是否在列表里
#define listIS_CONTAINED_WITHIN( pxList, pxListItem )    ( ( ( pxListItem )->pxContainer == (pxList)) ? (pdTRUE) : (pdFALSE))

//得到列表项的容器对象
#define listLIST_ITEM_CONTAINER( pxListItem )            ( ( pxListItem )->pxContainer )

//检测列表的End节点是否设置为最大值
#define listLIST_IS_INITIALISED( pxList )                ( ( pxList )->xListEnd.xItemValue == portMAX_DELAY )

//初始化列表,列表在使用前必须被初始化
void vListInitialise( List_t * const pxList ) PRIVILEGED_FUNCTION;

//初始化列表项目,列表项目使用前也要初始化
void vListInitialiseItem( ListItem_t * const pxItem ) PRIVILEGED_FUNCTION;

//往列表里插入列表项,按值排序
//pxList:列表常量指针
//pxNewListItem:要插入的新列表项指针
void vListInsert( List_t * const pxList,ListItem_t * const pxNewListItem ) PRIVILEGED_FUNCTION;

//往列表结尾加入列表项元素
//pxList:列表常量指针
//pxNewListItem:要插入的新列表项指针
void vListInsertEnd( List_t * const pxList,ListItem_t * const pxNewListItem ) PRIVILEGED_FUNCTION;

//把列表项从列表里面移除,返回列表数量
//pxItemToRemove:要异常的列表项
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove ) PRIVILEGED_FUNCTION;

//C++相关
#ifdef __cplusplus
    }
#endif

#endif 
		

list.c的实现

/*列表作为FreeRTOS内核的基础数据结构,主要为进程调度量身定做的数据结构
  用列表存储就绪任务、延迟任务等。列表设计为双向指针的列表,移除插入元素
	比数组高效。任务调度时候频繁涉及到任务在就绪、延迟等列表中移动
*/

#include <stdlib.h>

/* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining
 * all the API functions to use the MPU wrappers.  That should only be done when
 * task.h is included from an application file. */
#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE

#include "FreeRTOS.h"
#include "list.h"

/* Lint e9021, e961 and e750 are suppressed as a MISRA exception justified
 * because the MPU ports require MPU_WRAPPERS_INCLUDED_FROM_API_FILE to be
 * defined for the header files above, but not in this file, in order to
 * generate the correct privileged Vs unprivileged linkage and placement. */
#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE 


//初始化列表方法
//pxList:列表指针
void vListInitialise(List_t * const pxList)
{
	  //pxIndex指向xListEnd
    pxList->pxIndex = (ListItem_t * ) &( pxList->xListEnd); 

	  //设置结束元素的值为最大值
    pxList->xListEnd.xItemValue = portMAX_DELAY;

	  //结束元素的下一个元素指向自己
    pxList->xListEnd.pxNext = (ListItem_t *) &(pxList->xListEnd);    
    //结束元素的前一个元素也指向自己
		pxList->xListEnd.pxPrevious = (ListItem_t *) &( pxList->xListEnd);
		//设置列表元素数量为0
    pxList->uxNumberOfItems = (UBaseType_t) 0U;

    //写入固定值,检测用的位点,目前不关注
    listSET_LIST_INTEGRITY_CHECK_1_VALUE(pxList);
    listSET_LIST_INTEGRITY_CHECK_2_VALUE(pxList);
}

/*-----------------------------------------------------------*/
//初始化列表像
//pxItem:列表项常量指针
void vListInitialiseItem(ListItem_t * const pxItem)
{
	  //设置列表项所属为空指针,不属于任何列表
    pxItem->pxContainer = NULL;

    //写入固定值,检测用的位点,目前不关注
    listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE(pxItem);
    listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE(pxItem);
}

/*-----------------------------------------------------------*/
//往列表结尾加入列表项元素
//pxList:列表常量指针
//pxNewListItem:要插入的新列表项指针
void vListInsertEnd(List_t * const pxList,ListItem_t * const pxNewListItem)
{
	  //pxIndex指向放入常量
    ListItem_t * const pxIndex = pxList->pxIndex;

    //测试用的位点,目前不关注
    listTEST_LIST_INTEGRITY(pxList);
    listTEST_LIST_ITEM_INTEGRITY(pxNewListItem);

	  //新项目下一个元素指向xListEnd形成闭环
    pxNewListItem->pxNext = pxIndex;
	  //由于xListEnd的pxPrevious一直指向最后一个元素
	  //所以这个的意思就是新元素的前一个元素指向XList最后一个元素
    pxNewListItem->pxPrevious = pxIndex->pxPrevious;

    //测试用的位点,目前不关注
    mtCOVERAGE_TEST_DELAY();
    //当前最后一个元素的下一个元素知道新元素
    pxIndex->pxPrevious->pxNext = pxNewListItem;
		//xListEnd的pxPrevious重新执行新加的最后一个元素
    pxIndex->pxPrevious = pxNewListItem;

    //新项目的所属执行xList
    pxNewListItem->pxContainer = pxList;
		//XList的项目数加1
    (pxList->uxNumberOfItems)++;
}

/*-----------------------------------------------------------*/
//往列表里插入列表项,按值排序
//pxList:列表常量指针
//pxNewListItem:要插入的新列表项指针
void vListInsert(List_t * const pxList,ListItem_t * const pxNewListItem)
{
    ListItem_t * pxIterator;
	  //要插入的列表项的值
    const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;

    //测试用的位点,目前不关注
    listTEST_LIST_INTEGRITY(pxList);
    listTEST_LIST_ITEM_INTEGRITY(pxNewListItem);

		//按值大小排序插入新项目到列表里。如果有相同值的项目,那么放在相同值项目后面。确保TCB相同值共享CPU
    if(xValueOfInsertion == portMAX_DELAY)
    {
        pxIterator = pxList->xListEnd.pxPrevious;
    }
    else
    {
				//从xListEnd节点开始。一直找到值大于新插入项值的节点
        for( pxIterator = (ListItem_t *) &(pxList->xListEnd); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext) 
        {
            
        }
    }
    //新节点执行目标节点下一个节点
    pxNewListItem->pxNext = pxIterator->pxNext;
		//目标节点下一个节点的前一个节点指向新节点。这两步把新节点和目标节点下一个节点双向连好了
    pxNewListItem->pxNext->pxPrevious = pxNewListItem;
		//新节点前一个节点指向目标节点。
    pxNewListItem->pxPrevious = pxIterator;
		//前一个节点的下一个节点指向新节点。这两步把新节点和目标节点双向连好了
    pxIterator->pxNext = pxNewListItem;

    //新节点所属指向列表
    pxNewListItem->pxContainer = pxList;
		//列表数量加1
    (pxList->uxNumberOfItems)++;
}

/*-----------------------------------------------------------*/
//把列表项从列表里面移除,返回列表数量
//pxItemToRemove:要异常的列表项
UBaseType_t uxListRemove(ListItem_t * const pxItemToRemove)
{
	  //得到要移除列表项指向的列表指针
		List_t * const pxList = pxItemToRemove->pxContainer;
		//要移除列表项的后面节点的前一项指针指向要移除节点的前一个节点
    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
	  //要移除列表项前一个节点的后一项指针指向要移除节点后一项
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

    //测试用的位点,暂时不关注
    mtCOVERAGE_TEST_DELAY();

    //确保索引指向一个有效的项目
    if(pxList->pxIndex == pxItemToRemove)
    {
        pxList->pxIndex = pxItemToRemove->pxPrevious;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
		//把要移除项的所属设置空指针,不属于任何列表
    pxItemToRemove->pxContainer = NULL;
		//列表数量减1
    (pxList->uxNumberOfItems)--;
		//返回列表数量
    return pxList->uxNumberOfItems;
}
/*-----------------------------------------------------------*/



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小乌鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值