LVGL V8 代码细读——极致的链表使用

极致的链表的使用

序章碎碎念

对于链表,大家应该都不陌生。学过数据结构的,都知道它作为链式储存的基础,重要性不言而喻。

由于本人自动化专业,实际上不学数据结构。在大四众人考研的考研,找工作的找工作的情况下。本人在学校开始了蹭课之旅。在一场和大二学弟们同堂听课的过程中,这里也第一次遇见本次的主题------链表。

早期学习链表的时候,实际上对malloc和free是几乎没有理解的。老师在讲解的时候,就轻描淡写的一带而过,这里如果心里没有对它有初步的认识,很容易搞不清楚情况。

这里在序言中,我就简单的对基础比较差的读者介绍一下这两位后续常用的东西------malloc和free。

c 复制代码
// 这是包含我们需要使用的malloc和free函数的头文件
#include <stdlib.h> 

// 这里是主角之一,malloc
// 首先,malloc本身不是单词,是memory allocation缩写
// 从功能上,malloc会在运行时动态申请内存大小为size字节的内存,然后返回给你这段内存的首地址
// 额外要注意的是,malloc申请的内存只能通过free释放
// 如果申请失败(内存不足等)则返回NULL
void* malloc(size_t size) 

// 这里是另一位主角,free
// free比较简单,它会通过指针释放掉之前申请的内存
// 那么有问题,如果值传入指针,free函数如何知道自己释放多大内存?
// 这个实际上在malloc的时候,会多申请一块出来用于存放大小信息,一边free的时候能够访问
// 有兴趣的可以探究一下这个问题
void free(void* ptr)
数组 malloc+free
声明时申请内存,超过使用域则由系统自动回收(主打一个省心) 自己申请,自己释放(主打一个自由)
编译初期固定大小(除c99标准下可变数组) 编译过程中可以通过变量动态申请大小
随便申请释放,无所畏惧 小内存频繁申请释放会产生内存碎片
不怕产生内存泄漏 当内存未释放,但是未有任何指针引用它的时候,会产生内存泄漏
局部变量出作用域就被释放,有时候会导致野指针 内存申请后就算出函数也不会消失,必须等待你释放

既然讲到这里,其实基本认为读者已经对指针有所了解了。如果再往下科普,这个话题就越说越跑偏了。

回到这个话题来,初学者的链表之旅会较为容易。更多的精力可能会放到研究链表如何增删改查。学到的也是以简单的单向链表为主:

在学习的时候,咱们看到最多的链表也是形如下面的结构体:

c 复制代码
struct Node {
    int data;           // 数据
    struct Node* next;  // 指向下一个节点的指针
};

然后每次都是手撸创建、销毁、增删改查。对于新手的我们来说,很难做到封装成模块使用。

于是乎,漫长的时间里,我几乎都不想用这玩意。手撸一套太麻烦了。我开始寻找有无能复用的链表模块。

从redis源码里掏出来的adlist

某个偶然的机会,我从一篇文章里看到了C语言写的数据库 redis 中的一个数据结构。正式咱们后续要说的主角:adlist。我于是乎去翻阅源码,抽丝剥茧。最终总结如下:

c 复制代码
#include <stdlib.h>
#include "adlist.h"

/* Create a new list. The created list can be freed with
 * listRelease(), but private value of every node need to be freed
 * by the user before to call listRelease(), or by setting a free method using
 * listSetFreeMethod.
 *
 * On error, NULL is returned. Otherwise the pointer to the new list. */
list *listCreate(void)
{
    struct list *list;

    if ((list = malloc(sizeof(*list))) == NULL)
        return NULL;
    list->head = list->tail = NULL;
    list->len = 0;
    list->dup = NULL;
    list->free = NULL;
    list->match = NULL;
    return list;
}

/* Remove all the elements from the list without destroying the list itself. */
void listEmpty(list *list)
{
    unsigned long len;
    listNode *current, *next;

    current = list->head;
    len = list->len;
    while (len--)
    {
        next = current->next;
        if (list->free)
            list->free(current->value);
        free(current);
        current = next;
    }
    list->head = list->tail = NULL;
    list->len = 0;
}

/* Free the whole list.
 *
 * This function can't fail. */
void listRelease(list *list)
{
    listEmpty(list);
    free(list);
}

/* Add a new node to the list, to head, containing the specified 'value'
 * pointer as value.
 *
 * On error, NULL is returned and no operation is performed (i.e. the
 * list remains unaltered).
 * On success the 'list' pointer you pass to the function is returned. */
list *listAddNodeHead(list *list, void *value)
{
    listNode *node;

    if ((node = malloc(sizeof(*node))) == NULL)
        return NULL;
    node->value = value;
    if (list->len == 0)
    {
        list->head = list->tail = node;
        node->prev = node->next = NULL;
    }
    else
    {
        node->prev = NULL;
        node->next = list->head;
        list->head->prev = node;
        list->head = node;
    }
    list->len++;
    return list;
}

/* Add a new node to the list, to tail, containing the specified 'value'
 * pointer as value.
 *
 * On error, NULL is returned and no operation is performed (i.e. the
 * list remains unaltered).
 * On success the 'list' pointer you pass to the function is returned. */
list *listAddNodeTail(list *list, void *value)
{
    listNode *node;

    if ((node = malloc(sizeof(*node))) == NULL)
        return NULL;
    node->value = value;
    if (list->len == 0)
    {
        list->head = list->tail = node;
        node->prev = node->next = NULL;
    }
    else
    {
        node->prev = list->tail;
        node->next = NULL;
        list->tail->next = node;
        list->tail = node;
    }
    list->len++;
    return list;
}

list *listInsertNode(list *list, listNode *old_node, void *value, int after)
{
    listNode *node;

    if ((node = malloc(sizeof(*node))) == NULL)
        return NULL;
    node->value = value;
    if (after)
    {
        node->prev = old_node;
        node->next = old_node->next;
        if (list->tail == old_node)
        {
            list->tail = node;
        }
    }
    else
    {
        node->next = old_node;
        node->prev = old_node->prev;
        if (list->head == old_node)
        {
            list->head = node;
        }
    }
    if (node->prev != NULL)
    {
        node->prev->next = node;
    }
    if (node->next != NULL)
    {
        node->next->prev = node;
    }
    list->len++;
    return list;
}

/* Remove the specified node from the specified list.
 * It's up to the caller to free the private value of the node.
 *
 * This function can't fail. */
void listDelNode(list *list, listNode *node)
{
    if (node->prev)
        node->prev->next = node->next;
    else
        list->head = node->next;
    if (node->next)
        node->next->prev = node->prev;
    else
        list->tail = node->prev;
    if (list->free)
        list->free(node->value);
    free(node);
    list->len--;
}

/* Returns a list iterator 'iter'. After the initialization every
 * call to listNext() will return the next element of the list.
 *
 * This function can't fail. */
listIter *listGetIterator(list *list, int direction)
{
    listIter *iter;

    if ((iter = malloc(sizeof(*iter))) == NULL)
        return NULL;
    if (direction == AL_START_HEAD)
        iter->next = list->head;
    else
        iter->next = list->tail;
    iter->direction = direction;
    return iter;
}

/* Release the iterator memory */
void listReleaseIterator(listIter *iter)
{
    free(iter);
}

/* Create an iterator in the list private iterator structure */
void listRewind(list *list, listIter *li)
{
    li->next = list->head;
    li->direction = AL_START_HEAD;
}

void listRewindTail(list *list, listIter *li)
{
    li->next = list->tail;
    li->direction = AL_START_TAIL;
}

/* Return the next element of an iterator.
 * It's valid to remove the currently returned element using
 * listDelNode(), but not to remove other elements.
 *
 * The function returns a pointer to the next element of the list,
 * or NULL if there are no more elements, so the classical usage
 * pattern is:
 *
 * iter = listGetIterator(list,<direction>);
 * while ((node = listNext(iter)) != NULL) {
 *     doSomethingWith(listNodeValue(node));
 * }
 *
 * */
listNode *listNext(listIter *iter)
{
    listNode *current = iter->next;

    if (current != NULL)
    {
        if (iter->direction == AL_START_HEAD)
            iter->next = current->next;
        else
            iter->next = current->prev;
    }
    return current;
}

/* Duplicate the whole list. On out of memory NULL is returned.
 * On success a copy of the original list is returned.
 *
 * The 'Dup' method set with listSetDupMethod() function is used
 * to copy the node value. Otherwise the same pointer value of
 * the original node is used as value of the copied node.
 *
 * The original list both on success or error is never modified. */
list *listDup(list *orig)
{
    list *copy;
    listIter iter;
    listNode *node;

    if ((copy = listCreate()) == NULL)
        return NULL;
    copy->dup = orig->dup;
    copy->free = orig->free;
    copy->match = orig->match;
    listRewind(orig, &iter);
    while ((node = listNext(&iter)) != NULL)
    {
        void *value;

        if (copy->dup)
        {
            value = copy->dup(node->value);
            if (value == NULL)
            {
                listRelease(copy);
                return NULL;
            }
        }
        else
        {
            value = node->value;
        }

        if (listAddNodeTail(copy, value) == NULL)
        {
            /* Free value if dup succeed but listAddNodeTail failed. */
            if (copy->free)
                copy->free(value);

            listRelease(copy);
            return NULL;
        }
    }
    return copy;
}

/* Search the list for a node matching a given key.
 * The match is performed using the 'match' method
 * set with listSetMatchMethod(). If no 'match' method
 * is set, the 'value' pointer of every node is directly
 * compared with the 'key' pointer.
 *
 * On success the first matching node pointer is returned
 * (search starts from head). If no matching node exists
 * NULL is returned. */
listNode *listSearchKey(list *list, void *key)
{
    listIter iter;
    listNode *node;

    listRewind(list, &iter);
    while ((node = listNext(&iter)) != NULL)
    {
        if (list->match)
        {
            if (list->match(node->value, key))
            {
                return node;
            }
        }
        else
        {
            if (key == node->value)
            {
                return node;
            }
        }
    }
    return NULL;
}

/* Return the element at the specified zero-based index
 * where 0 is the head, 1 is the element next to head
 * and so on. Negative integers are used in order to count
 * from the tail, -1 is the last element, -2 the penultimate
 * and so on. If the index is out of range NULL is returned. */
listNode *listIndex(list *list, long index)
{
    listNode *n;

    if (index < 0)
    {
        index = (-index) - 1;
        n = list->tail;
        while (index-- && n)
            n = n->prev;
    }
    else
    {
        n = list->head;
        while (index-- && n)
            n = n->next;
    }
    return n;
}

/* Rotate the list removing the tail node and inserting it to the head. */
void listRotateTailToHead(list *list)
{
    if (listLength(list) <= 1)
        return;

    /* Detach current tail */
    listNode *tail = list->tail;
    list->tail = tail->prev;
    list->tail->next = NULL;
    /* Move it as head */
    list->head->prev = tail;
    tail->prev = NULL;
    tail->next = list->head;
    list->head = tail;
}

/* Rotate the list removing the head node and inserting it to the tail. */
void listRotateHeadToTail(list *list)
{
    if (listLength(list) <= 1)
        return;

    listNode *head = list->head;
    /* Detach current head */
    list->head = head->next;
    list->head->prev = NULL;
    /* Move it as tail */
    list->tail->next = head;
    head->next = NULL;
    head->prev = list->tail;
    list->tail = head;
}

/* Add all the elements of the list 'o' at the end of the
 * list 'l'. The list 'other' remains empty but otherwise valid. */
void listJoin(list *l, list *o)
{
    if (o->len == 0)
        return;

    o->head->prev = l->tail;

    if (l->tail)
        l->tail->next = o->head;
    else
        l->head = o->head;

    l->tail = o->tail;
    l->len += o->len;

    /* Setup other as an empty list. */
    o->head = o->tail = NULL;
    o->len = 0;
}
c 复制代码
#ifndef __ADLIST_H__
#define __ADLIST_H__

/* Node, List, and Iterator are the only data structures used currently. */

typedef struct listNode
{
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;

typedef struct listIter
{
    listNode *next;
    int direction;
} listIter;

typedef struct list
{
    listNode *head;
    listNode *tail;
    void *(*dup)(void *ptr);
    void (*free)(void *ptr);
    int (*match)(void *ptr, void *key);
    unsigned long len;
} list;

/* Functions implemented as macros */
#define listLength(l) ((l)->len)
#define listFirst(l) ((l)->head)
#define listLast(l) ((l)->tail)
#define listPrevNode(n) ((n)->prev)
#define listNextNode(n) ((n)->next)
#define listNodeValue(n) ((n)->value)

#define listSetDupMethod(l, m) ((l)->dup = (m))
#define listSetFreeMethod(l, m) ((l)->free = (m))
#define listSetMatchMethod(l, m) ((l)->match = (m))

#define listGetDupMethod(l) ((l)->dup)
#define listGetFreeMethod(l) ((l)->free)
#define listGetMatchMethod(l) ((l)->match)

/* Prototypes */
list *listCreate(void);
void listRelease(list *list);
void listEmpty(list *list);
list *listAddNodeHead(list *list, void *value);
list *listAddNodeTail(list *list, void *value);
list *listInsertNode(list *list, listNode *old_node, void *value, int after);
void listDelNode(list *list, listNode *node);
listIter *listGetIterator(list *list, int direction);
listNode *listNext(listIter *iter);
void listReleaseIterator(listIter *iter);
list *listDup(list *orig);
listNode *listSearchKey(list *list, void *key);
listNode *listIndex(list *list, long index);
void listRewind(list *list, listIter *li);
void listRewindTail(list *list, listIter *li);
void listRotateTailToHead(list *list);
void listRotateHeadToTail(list *list);
void listJoin(list *l, list *o);

/* Directions for iterators */
#define AL_START_HEAD 0
#define AL_START_TAIL 1

#endif /* __ADLIST_H__ */

在原代码的基础上修改的不多,删掉了一些依赖,替换成了malloc之类的。这部分大家可以自己复制到自己的adlist.c adlist.h文件中,然后引入到自己的工程里面,就能够尝试去使用了。

这里给出一个,本人自己写的简单的使用例程:

c 复制代码
#include "adlist.h"
#include <stdio.h>

/**
 * @brief 简单的遍历打印链表
 * 
 * @param l 链表对象
 */
void print_list(list *l)
{
    printf("list len: %d\n", listLength(l));
    listIter *iter = listGetIterator(l, AL_START_HEAD);  // 创建迭代器
    
    // 使用迭代器进行迭代
    for (listNode *node = listNext(iter);
            node != NULL;
            node = listNext(iter)) {
        if (node != NULL) {
            printf("list value:%d\n", *(int *)listNodeValue(node));
        }
    }
    listReleaseIterator(iter); // 结束后销毁迭代器
}

int main(void)
{
    list *list1 = listCreate(); // 创建链表对象

    // 这里最好是malloc或者全局变量
    // 不然出函数就会被回收,导致链表保存的指针为野指针
    int value1 = 1; 
    int value2 = 2;

    // 添加数据到链表尾部
    listAddNodeTail(list1, &value1); 
    listAddNodeTail(list1, &value2);
    print_list(list1);
    return 0;
}

redis里面的adlist我认为是功能最全,代码可读性最好的链表,使用起来也最友好的链表了。代码不需要注释也能明白它用法。所以这里也不多做笔墨去分析它了。

极致的精简的list.h

而后的时间,与朋友交流的时候发现,他们更多的使用linux内核链表。

c 复制代码
#ifndef __LIST_H__
#define __LIST_H__

#ifndef offsetof
#define offsetof(type,member) __builtin_offsetof(type,member)
#endif


struct list_head {
    struct list_head *next, *prev;
};

typedef struct list_head list_t;

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:    the pointer to the member.
 * @type:   the type of the container struct this is embedded in.
 * @member: the name of the member within the struct.
 *
 */

#ifndef container_of
#define container_of(ptr, type, member) ({          \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})
#endif

/*
 * Simple doubly linked list implementation.
 *
 * Some of the internal functions ("__xxx") are useful when
 * manipulating whole lists rather than single entries, as
 * sometimes we already know the next/prev entries and we can
 * generate better code by using them directly rather than
 * using the generic single-entry routines.
 */
//static define
#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)

//动态初始化
static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}

/*
 * Insert a new_node entry between two known consecutive entries.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_add(struct list_head *new_node,
                              struct list_head *prev,
                              struct list_head *next)
{
    next->prev = new_node;
    new_node->next = next;
    new_node->prev = prev;
    prev->next = new_node;
}

/**
 * list_add - add a new_node entry
 * @new_node: new_node entry to be added
 * @head: list head to add it after
 *
 * Insert a new_node entry after the specified head.
 * This is good for implementing stacks.
 */
static inline void list_add(struct list_head *new_node, struct list_head *head)
{
    __list_add(new_node, head, head->next);
}


/**
 * list_add_tail - add a new_node entry
 * @new_node: new_node entry to be added
 * @head: list head to add it before
 *
 * Insert a new_node entry before the specified head.
 * This is useful for implementing queues.
 */
static inline void list_add_tail(struct list_head *new_node, struct list_head *head)
{
    __list_add(new_node, head->prev, head);
}

/*
 * Delete a list entry by making the prev/next entries
 * point to each other.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
    next->prev = prev;
    prev->next = next;
}

/**
 * list_del - deletes entry from list.
 * @entry: the element to delete from the list.
 * Note: list_empty() on entry does not return true after this, the entry is
 * in an undefined state.
 */
static inline void __list_del_entry(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
}

static inline void list_del(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
    //entry->next = LIST_POISON1;
    //entry->prev = LIST_POISON2;
}

/**
 * list_replace - replace old entry by new_node one
 * @old : the element to be replaced
 * @new_node : the new_node element to insert
 *
 * If @old was empty, it will be overwritten.
 */
static inline void list_replace(struct list_head *old,
                                struct list_head *new_node)
{
    new_node->next = old->next;
    new_node->next->prev = new_node;
    new_node->prev = old->prev;
    new_node->prev->next = new_node;
}

static inline void list_replace_init(struct list_head *old,
                                     struct list_head *new_node)
{
    list_replace(old, new_node);
    INIT_LIST_HEAD(old);
}

/**
 * list_del_init - deletes entry from list and reinitialize it.
 * @entry: the element to delete from the list.
 */
static inline void list_del_init(struct list_head *entry)
{
    __list_del_entry(entry);
    INIT_LIST_HEAD(entry);
}

/**
 * list_move - delete from one list and add as another's head
 * @list: the entry to move
 * @head: the head that will precede our entry
 */
static inline void list_move(struct list_head *list, struct list_head *head)
{
    __list_del_entry(list);
    list_add(list, head);
}

/**
 * list_move_tail - delete from one list and add as another's tail
 * @list: the entry to move
 * @head: the head that will follow our entry
 */
static inline void list_move_tail(struct list_head *list,
                                  struct list_head *head)
{
    __list_del_entry(list);
    list_add_tail(list, head);
}

/**
 * list_is_last - tests whether @list is the last entry in list @head
 * @list: the entry to test
 * @head: the head of the list
 */
static inline int list_is_last(const struct list_head *list,
                               const struct list_head *head)
{
    return list->next == head;
}

/**
 * list_empty - tests whether a list is empty
 * @head: the list to test.
 */
static inline int list_empty(const struct list_head *head)
{
    return head->next == head;
}

/**
 * list_empty_careful - tests whether a list is empty and not being modified
 * @head: the list to test
 *
 * Description:
 * tests whether a list is empty _and_ checks that no other CPU might be
 * in the process of modifying either member (next or prev)
 *
 * NOTE: using list_empty_careful() without synchronization
 * can only be safe if the only activity that can happen
 * to the list entry is list_del_init(). Eg. it cannot be used
 * if another CPU could re-list_add() it.
 */
static inline int list_empty_careful(const struct list_head *head)
{
    struct list_head *next = head->next;
    return (next == head) && (next == head->prev);
}

/**
 * list_rotate_left - rotate the list to the left
 * @head: the head of the list
 */
static inline void list_rotate_left(struct list_head *head)
{
    struct list_head *first;

    if (!list_empty(head)) {
        first = head->next;
        list_move_tail(first, head);
    }
}

/**
 * list_is_singular - tests whether a list has just one entry.
 * @head: the list to test.
 */
static inline int list_is_singular(const struct list_head *head)
{
    return !list_empty(head) && (head->next == head->prev);
}

static inline void __list_cut_position(struct list_head *list,
                                       struct list_head *head, struct list_head *entry)
{
    struct list_head *new_node_first = entry->next;
    list->next = head->next;
    list->next->prev = list;
    list->prev = entry;
    entry->next = list;
    head->next = new_node_first;
    new_node_first->prev = head;
}

/**
 * list_cut_position - cut a list into two
 * @list: a new_node list to add all removed entries
 * @head: a list with entries
 * @entry: an entry within head, could be the head itself
 *  and if so we won't cut the list
 *
 * This helper moves the initial part of @head, up to and
 * including @entry, from @head to @list. You should
 * pass on @entry an element you know is on @head. @list
 * should be an empty list or a list you do not care about
 * losing its data.
 *
 */
static inline void list_cut_position(struct list_head *list,
                                     struct list_head *head, struct list_head *entry)
{
    if (list_empty(head)) {
        return;
    }
    if (list_is_singular(head) &&
            (head->next != entry && head != entry)) {
        return;
    }
    if (entry == head) {
        INIT_LIST_HEAD(list);
    } else {
        __list_cut_position(list, head, entry);
    }
}

static inline void __list_splice(const struct list_head *list,
                                 struct list_head *prev,
                                 struct list_head *next)
{
    struct list_head *first = list->next;
    struct list_head *last = list->prev;

    first->prev = prev;
    prev->next = first;

    last->next = next;
    next->prev = last;
}

/**
 * list_splice - join two lists, this is designed for stacks
 * @list: the new_node list to add.
 * @head: the place to add it in the first list.
 */
static inline void list_splice(const struct list_head *list,
                               struct list_head *head)
{
    if (!list_empty(list)) {
        __list_splice(list, head, head->next);
    }
}

/**
 * list_splice_tail - join two lists, each list being a queue
 * @list: the new_node list to add.
 * @head: the place to add it in the first list.
 */
static inline void list_splice_tail(struct list_head *list,
                                    struct list_head *head)
{
    if (!list_empty(list)) {
        __list_splice(list, head->prev, head);
    }
}

/**
 * list_splice_init - join two lists and reinitialise the emptied list.
 * @list: the new_node list to add.
 * @head: the place to add it in the first list.
 *
 * The list at @list is reinitialised
 */
static inline void list_splice_init(struct list_head *list,
                                    struct list_head *head)
{
    if (!list_empty(list)) {
        __list_splice(list, head, head->next);
        INIT_LIST_HEAD(list);
    }
}

/**
 * list_splice_tail_init - join two lists and reinitialise the emptied list
 * @list: the new_node list to add.
 * @head: the place to add it in the first list.
 *
 * Each of the lists is a queue.
 * The list at @list is reinitialised
 */
static inline void list_splice_tail_init(struct list_head *list,
        struct list_head *head)
{
    if (!list_empty(list)) {
        __list_splice(list, head->prev, head);
        INIT_LIST_HEAD(list);
    }
}

/**
 * list_entry - get the struct for this entry
 * @ptr:    the &struct list_head pointer.
 * @type:   the type of the struct this is embedded in.
 * @member: the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

/**
 * list_first_entry - get the first element from a list
 * @ptr:    the list head to take the element from.
 * @type:   the type of the struct this is embedded in.
 * @member: the name of the list_struct within the struct.
 *
 * Note, that list is expected to be not empty.
 */
#define list_first_entry(ptr, type, member) \
    list_entry((ptr)->next, type, member)

/**
 * list_for_each    -   iterate over a list
 * @pos:    the &struct list_head to use as a loop cursor.
 * @head:   the head for your list.
 */
#define list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)

/**
 * __list_for_each  -   iterate over a list
 * @pos:    the &struct list_head to use as a loop cursor.
 * @head:   the head for your list.
 *
 * This variant doesn't differ from list_for_each() any more.
 * We don't do prefetching in either case.
 */
#define __list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)

/**
 * list_for_each_prev   -   iterate over a list backwards
 * @pos:    the &struct list_head to use as a loop cursor.
 * @head:   the head for your list.
 */
#define list_for_each_prev(pos, head) \
    for (pos = (head)->prev; pos != (head); pos = pos->prev)

/**
 * list_for_each_safe - iterate over a list safe against removal of list entry
 * @pos:    the &struct list_head to use as a loop cursor.
 * @n:      another &struct list_head to use as temporary storage
 * @head:   the head for your list.
 */
#define list_for_each_safe(pos, n, head) \
    for (pos = (head)->next, n = pos->next; pos != (head); \
        pos = n, n = pos->next)

/**
 * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
 * @pos:    the &struct list_head to use as a loop cursor.
 * @n:      another &struct list_head to use as temporary storage
 * @head:   the head for your list.
 */
#define list_for_each_prev_safe(pos, n, head) \
    for (pos = (head)->prev, n = pos->prev; \
         pos != (head); \
         pos = n, n = pos->prev)

/**
 * list_for_each_entry  -   iterate over list of given type
 * @pos:    the type * to use as a loop cursor.
 * @head:   the head for your list.
 * @member: the name of the list_struct within the struct.
 */
#define list_for_each_entry(pos, head, member)              \
    for (pos = list_entry((head)->next, typeof(*pos), member);  \
         &pos->member != (head);    \
         pos = list_entry(pos->member.next, typeof(*pos), member))

/**
 * list_for_each_entry_reverse - iterate backwards over list of given type.
 * @pos:    the type * to use as a loop cursor.
 * @head:   the head for your list.
 * @member: the name of the list_struct within the struct.
 */
#define list_for_each_entry_reverse(pos, head, member)          \
    for (pos = list_entry((head)->prev, typeof(*pos), member);  \
         &pos->member != (head);    \
         pos = list_entry(pos->member.prev, typeof(*pos), member))

/**
 * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()
 * @pos:    the type * to use as a start point
 * @head:   the head of the list
 * @member: the name of the list_struct within the struct.
 *
 * Prepares a pos entry for use as a start point in list_for_each_entry_continue().
 */
#define list_prepare_entry(pos, head, member) \
    ((pos) ? : list_entry(head, typeof(*pos), member))

/**
 * list_for_each_entry_continue - continue iteration over list of given type
 * @pos:    the type * to use as a loop cursor.
 * @head:   the head for your list.
 * @member: the name of the list_struct within the struct.
 *
 * Continue to iterate over list of given type, continuing after
 * the current position.
 */
#define list_for_each_entry_continue(pos, head, member)         \
    for (pos = list_entry(pos->member.next, typeof(*pos), member);  \
         &pos->member != (head);    \
         pos = list_entry(pos->member.next, typeof(*pos), member))

/**
 * list_for_each_entry_continue_reverse - iterate backwards from the given point
 * @pos:    the type * to use as a loop cursor.
 * @head:   the head for your list.
 * @member: the name of the list_struct within the struct.
 *
 * Start to iterate over list of given type backwards, continuing after
 * the current position.
 */
#define list_for_each_entry_continue_reverse(pos, head, member)     \
    for (pos = list_entry(pos->member.prev, typeof(*pos), member);  \
         &pos->member != (head);    \
         pos = list_entry(pos->member.prev, typeof(*pos), member))

/**
 * list_for_each_entry_from - iterate over list of given type from the current point
 * @pos:    the type * to use as a loop cursor.
 * @head:   the head for your list.
 * @member: the name of the list_struct within the struct.
 *
 * Iterate over list of given type, continuing from current position.
 */
#define list_for_each_entry_from(pos, head, member)             \
    for (; &pos->member != (head);  \
         pos = list_entry(pos->member.next, typeof(*pos), member))

/**
 * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
 * @pos:    the type * to use as a loop cursor.
 * @n:      another type * to use as temporary storage
 * @head:   the head for your list.
 * @member: the name of the list_struct within the struct.
 */
#define list_for_each_entry_safe(pos, n, head, member)          \
    for (pos = list_entry((head)->next, typeof(*pos), member),  \
        n = list_entry(pos->member.next, typeof(*pos), member); \
         &pos->member != (head);                    \
         pos = n, n = list_entry(n->member.next, typeof(*n), member))

/**
 * list_for_each_entry_safe_continue - continue list iteration safe against removal
 * @pos:    the type * to use as a loop cursor.
 * @n:      another type * to use as temporary storage
 * @head:   the head for your list.
 * @member: the name of the list_struct within the struct.
 *
 * Iterate over list of given type, continuing after current point,
 * safe against removal of list entry.
 */
#define list_for_each_entry_safe_continue(pos, n, head, member)         \
    for (pos = list_entry(pos->member.next, typeof(*pos), member),      \
        n = list_entry(pos->member.next, typeof(*pos), member);     \
         &pos->member != (head);                        \
         pos = n, n = list_entry(n->member.next, typeof(*n), member))

/**
 * list_for_each_entry_safe_from - iterate over list from current point safe against removal
 * @pos:    the type * to use as a loop cursor.
 * @n:      another type * to use as temporary storage
 * @head:   the head for your list.
 * @member: the name of the list_struct within the struct.
 *
 * Iterate over list of given type from current point, safe against
 * removal of list entry.
 */
#define list_for_each_entry_safe_from(pos, n, head, member)             \
    for (n = list_entry(pos->member.next, typeof(*pos), member);        \
         &pos->member != (head);                        \
         pos = n, n = list_entry(n->member.next, typeof(*n), member))

/**
 * list_for_each_entry_safe_reverse - iterate backwards over list safe against removal
 * @pos:    the type * to use as a loop cursor.
 * @n:      another type * to use as temporary storage
 * @head:   the head for your list.
 * @member: the name of the list_struct within the struct.
 *
 * Iterate backwards over list of given type, safe against removal
 * of list entry.
 */
#define list_for_each_entry_safe_reverse(pos, n, head, member)      \
    for (pos = list_entry((head)->prev, typeof(*pos), member),  \
        n = list_entry(pos->member.prev, typeof(*pos), member); \
         &pos->member != (head);                    \
         pos = n, n = list_entry(n->member.prev, typeof(*n), member))

/**
 * list_safe_reset_next - reset a stale list_for_each_entry_safe loop
 * @pos:    the loop cursor used in the list_for_each_entry_safe loop
 * @n:      temporary storage used in list_for_each_entry_safe
 * @member: the name of the list_struct within the struct.
 *
 * list_safe_reset_next is not safe to use in general if the list may be
 * modified concurrently (eg. the lock is dropped in the loop body). An
 * exception to this is if the cursor element (pos) is pinned in the list,
 * and list_safe_reset_next is called after re-taking the lock and before
 * completing the current iteration of the loop body.
 */
#define list_safe_reset_next(pos, n, member)                \
    n = list_entry(pos->member.next, typeof(*pos), member)

#endif /* __LIST_H__*/

这个链表更加极致,整体就只有一个头文件。代码不是宏就是内联函数。理论上不使用它,它开销是0。这样的做法导致它的开销极低,但是它使用起来不是很方便。代码的可读性也比较差。但是效率就很高

c 复制代码
#include "list.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


typedef struct _student_t {
    char name[20];
    int age;
    list_t list;
} student_t;


void student_reg(list_t* student_list, const char *name, int age)
{
    student_t *student = (student_t *)malloc(sizeof(student_t));
    student->age = age;
    strncpy(student->name, name, sizeof(student->name));
    list_add_tail(&student->list, student_list);
}

void main(void)
{
    list_t student_list = LIST_HEAD_INIT(student_list);

    student_reg(&student_list, "Alice", 20);
    student_reg(&student_list, "Bob", 22);

    student_t *entry, *_entry;
    list_for_each_entry(entry, &student_list, list) {
        printf("Name: %s, Age: %d\n", entry->name, entry->age);
    }

    list_for_each_entry_safe(entry, _entry, &student_list, list) {
        list_del(&entry->list); // 从链表中删除节点
        free(entry);
    }
}

如果说adlist的做法,是认为万物都可以看成指针,包到adlist的void *value;中去。那么,list.h的做法则是讲自己打包,包到任何的数据类型里面去。遍历链表后,可以通过它的container_of找到每个包含了链表节点的结构体的头。从而实现链表的功能。它本身还不涉及任何内存分配和释放,也不依赖任何头文件。真的做到了0依赖,0动态内存分配。对于这种特别底层的东西做到这样的程度是尤为厉害的。这也使得我不得不"捏着鼻子"用。

lvgl对链表的巧思lv_ll

终于到了咱们的主角,lvgl的源码

这次我们先从我写的例子入手,再看源码。逐步分析它:

c 复制代码
#include "lv_ll.h"

#include <stdio.h>

int main()
{
    // 链表头的创建
    lv_ll_t list;

    // 初始化,并设置了链表的value的大小
    lv_ll_init(&list, sizeof(int));

    // 插入链表的时候顺带申请了空间(一次malloc)
    int* a = (int*)lv_ll_ins_tail(&list);
    int* b = (int*)lv_ll_ins_tail(&list);

    // 设置数据
    *a = 1;
    *b = 2;

    // 开始遍历链表吗,并打印值
    for (int* list_node  = (int*)lv_ll_get_head(&list); list_node != NULL; list_node = lv_ll_get_next(&list, list_node))
    {
        printf("num:%d\n", *list_node);
    }
    
    return 0;
}

根着我的注释,大家应该了解了如何使用链表去存储变量。

咱们把adlist list 和 lv_ll拉到一起,做个对比

对比内容 adlist list lv_ll
链表本身 创建时分配内存 外部传入(用静态动态用户选择) 创建时分配
链表数据 用户创建传入指针 用户创建 和链表一起创建

实际上list所有内容都是由外部传入,它本身是仅仅关注链表的操作内容。这种方式优点就在于无论使用静态(数组)还是动态(malloc)内存都是可以用户自定义的。

在adlist的时候, 链表的内存使用了malloc。不用再用户端角度考虑传入了,但是void* value还是需要传入。如果这里是局部变量的指针就会导致出现野指针的危险,使用时应当注意。

lv_ll则非同寻常的做法,在初始化的时候会固定好你每个节点的value大小。让我们来看看它链表头是啥样的:

c 复制代码
/** Dummy type to make handling easier*/
typedef uint8_t lv_ll_node_t;

/** Description of a linked list*/
typedef struct {
    uint32_t n_size;
    lv_ll_node_t * head;
    lv_ll_node_t * tail;
} lv_ll_t;

这里链表头里面有保存每个节点的大小倒是不惊讶。但是这个uint8_t *类型的头尾节点着实和我们之前接触到的链表有所不同。我们继续看看它初始化在做什么:

c 复制代码
void lv_ll_init(lv_ll_t *ll_p, uint32_t node_size)
{
    ll_p->head = NULL;
    ll_p->tail = NULL;
#ifdef LV_ARCH_64
    /*Round the size up to 8*/
    node_size = (node_size + 7) & (~0x7);
#else
    /*Round the size up to 4*/
    node_size = (node_size + 3) & (~0x3);
#endif

    ll_p->n_size = node_size;
}

从初始化开始我们来对比一下三者不同的点:

list的结构, 无论是头还是节点,都只用同一个数据结构list_t。遍历的时候判断结尾也是判断下一个是否等于头,是双向循环链表:

adlist则是有它专属的头list,和节点listNode两种对象

节点本质上是双向链表,遍历到next为NULL的时候结束遍历

从初始化上来看,我们的主角应该也是adlist这样的,所以初始化是NULL。计算node_size 这里是根据架构进行字节对齐(不足32bit或者64bit的算32bit或64bit)。这样不会出现内存对齐的意外。

接下来看看lv_ll_ins_tail的源码:

c 复制代码
/**
 * Add a new tail to a linked list
 * @param ll_p pointer to linked list
 * @return pointer to the new tail
 */
void *lv_ll_ins_tail(lv_ll_t *ll_p)
{
    lv_ll_node_t *n_new;

    n_new = malloc(ll_p->n_size + LL_NODE_META_SIZE);

    if (n_new != NULL) {
        node_set_next(ll_p, n_new, NULL);       /*No next after the new tail*/
        node_set_prev(ll_p, n_new, ll_p->tail); /*The prev. before new is the old tail*/
        if (ll_p->tail != NULL) {               /*If there is old tail then the new comes after it*/
            node_set_next(ll_p, ll_p->tail, n_new);
        }

        ll_p->tail = n_new;      /*Set the new tail in the dsc.*/
        if (ll_p->head == NULL) { /*If there is no head (1. node) set the head too*/
            ll_p->head = n_new;
        }
    }

    return n_new;
}

这里就是一个技巧点。之前说了lvgl的链表不仅仅将链表使用malloc申请好了,也将你value使用malloc申请好了。这里的节点数据结构实际上是隐藏的,本质上是一个uint8_t的next一个uint8_t类型的prev。所以这里malloc的大小是之前设置好的vlaue的size大小加上"元节点"大小。

c 复制代码
#define LL_NODE_META_SIZE (sizeof(lv_ll_node_t *) + sizeof(lv_ll_node_t *))

这里一大块数据被分成了数据区域和next和prev指针,三个结构体元素,且不用结构体。这里实际上没解决一个问题。为啥用uint8_t类型去指向下一个或者上一个节点内存的区域,而不是void 。这个比较好理解,首先要了解的是,不同类型的指针本质都是一个指针大小的vlaue,不过里面存放的位置不同罢了。如果value相同(指向同一片内存)。类型不同会导致偏移一个单位后的偏移字节不相同。比如,针对uint32_t类型+1则会偏移四个字节,而uint8_t +1则会偏移一个字节。所以,这里在设计的时候不会对这个指针取值,所以uint8_t设计是为了方便做偏移计算。
很明显一开始声明的n_new是lv_ll_node_t 。但是返回值确实void
类型的,其中涉及一个隐式类型转换。也就是说用户拿到手里还是void*类型。

那实际上lvgl的链表本质上和adlist很相似。不过加入了自己的一些小特色。

文章尾部附上lvgl中的lv_ll源码(魔改后无其它依赖)

c 复制代码
/**
 * @file lv_ll.c
 * Handle linked lists.
 * The nodes are dynamically allocated by the 'lv_mem' module,
 */

/*********************
 *      INCLUDES
 *********************/
#include "lv_ll.h"
#include <stdlib.h>

/*********************
 *      DEFINES
 *********************/
#define LL_NODE_META_SIZE (sizeof(lv_ll_node_t *) + sizeof(lv_ll_node_t *))
#define LL_PREV_P_OFFSET(ll_p) (ll_p->n_size)
#define LL_NEXT_P_OFFSET(ll_p) (ll_p->n_size + sizeof(lv_ll_node_t *))

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void node_set_prev(lv_ll_t *ll_p, lv_ll_node_t *act, lv_ll_node_t *prev);
static void node_set_next(lv_ll_t *ll_p, lv_ll_node_t *act, lv_ll_node_t *next);

/**********************
 *  STATIC VARIABLES
 **********************/

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

/**
 * Initialize linked list
 * @param ll_p pointer to lv_ll_t variable
 * @param node_size the size of 1 node in bytes
 */
void lv_ll_init(lv_ll_t *ll_p, uint32_t node_size)
{
    ll_p->head = NULL;
    ll_p->tail = NULL;
#ifdef LV_ARCH_64
    /*Round the size up to 8*/
    node_size = (node_size + 7) & (~0x7);
#else
    /*Round the size up to 4*/
    node_size = (node_size + 3) & (~0x3);
#endif

    ll_p->n_size = node_size;
}

/**
 * Add a new head to a linked list
 * @param ll_p pointer to linked list
 * @return pointer to the new head
 */
void *lv_ll_ins_head(lv_ll_t *ll_p)
{
    lv_ll_node_t *n_new;

    n_new = malloc(ll_p->n_size + LL_NODE_META_SIZE);

    if (n_new != NULL) {
        node_set_prev(ll_p, n_new, NULL);       /*No prev. before the new head*/
        node_set_next(ll_p, n_new, ll_p->head); /*After new comes the old head*/

        if (ll_p->head != NULL) { /*If there is old head then before it goes the new*/
            node_set_prev(ll_p, ll_p->head, n_new);
        }

        ll_p->head = n_new;      /*Set the new head in the dsc.*/
        if (ll_p->tail == NULL) { /*If there is no tail (1. node) set the tail too*/
            ll_p->tail = n_new;
        }
    }

    return n_new;
}

/**
 * Insert a new node in front of the n_act node
 * @param ll_p pointer to linked list
 * @param n_act pointer a node
 * @return pointer to the new node
 */
void *lv_ll_ins_prev(lv_ll_t *ll_p, void *n_act)
{
    lv_ll_node_t *n_new;

    if (NULL == ll_p || NULL == n_act) {
        return NULL;
    }

    if (lv_ll_get_head(ll_p) == n_act) {
        n_new = lv_ll_ins_head(ll_p);
        if (n_new == NULL) {
            return NULL;
        }
    } else {
        n_new = malloc(ll_p->n_size + LL_NODE_META_SIZE);
        if (n_new == NULL) {
            return NULL;
        }

        lv_ll_node_t *n_prev;
        n_prev = lv_ll_get_prev(ll_p, n_act);
        node_set_next(ll_p, n_prev, n_new);
        node_set_prev(ll_p, n_new, n_prev);
        node_set_prev(ll_p, n_act, n_new);
        node_set_next(ll_p, n_new, n_act);
    }

    return n_new;
}

/**
 * Add a new tail to a linked list
 * @param ll_p pointer to linked list
 * @return pointer to the new tail
 */
void *lv_ll_ins_tail(lv_ll_t *ll_p)
{
    lv_ll_node_t *n_new;

    n_new = malloc(ll_p->n_size + LL_NODE_META_SIZE);

    if (n_new != NULL) {
        node_set_next(ll_p, n_new, NULL);       /*No next after the new tail*/
        node_set_prev(ll_p, n_new, ll_p->tail); /*The prev. before new is the old tail*/
        if (ll_p->tail != NULL) {               /*If there is old tail then the new comes after it*/
            node_set_next(ll_p, ll_p->tail, n_new);
        }

        ll_p->tail = n_new;      /*Set the new tail in the dsc.*/
        if (ll_p->head == NULL) { /*If there is no head (1. node) set the head too*/
            ll_p->head = n_new;
        }
    }

    return n_new;
}

/**
 * Remove the node 'node_p' from 'll_p' linked list.
 * It does not free the memory of node.
 * @param ll_p pointer to the linked list of 'node_p'
 * @param node_p pointer to node in 'll_p' linked list
 */
void lv_ll_remove(lv_ll_t *ll_p, void *node_p)
{
    if (ll_p == NULL) {
        return;
    }

    if (lv_ll_get_head(ll_p) == node_p) {
        /*The new head will be the node after 'n_act'*/
        ll_p->head = lv_ll_get_next(ll_p, node_p);
        if (ll_p->head == NULL) {
            ll_p->tail = NULL;
        } else {
            node_set_prev(ll_p, ll_p->head, NULL);
        }
    } else if (lv_ll_get_tail(ll_p) == node_p) {
        /*The new tail will be the node before 'n_act'*/
        ll_p->tail = lv_ll_get_prev(ll_p, node_p);
        if (ll_p->tail == NULL) {
            ll_p->head = NULL;
        } else {
            node_set_next(ll_p, ll_p->tail, NULL);
        }
    } else {
        lv_ll_node_t *n_prev = lv_ll_get_prev(ll_p, node_p);
        lv_ll_node_t *n_next = lv_ll_get_next(ll_p, node_p);

        node_set_next(ll_p, n_prev, n_next);
        node_set_prev(ll_p, n_next, n_prev);
    }
}

/**
 * Remove and free all elements from a linked list. The list remain valid but become empty.
 * @param ll_p pointer to linked list
 */
void lv_ll_clear(lv_ll_t *ll_p)
{
    void *i;
    void *i_next;

    i      = lv_ll_get_head(ll_p);
    i_next = NULL;

    while (i != NULL) {
        i_next = lv_ll_get_next(ll_p, i);

        lv_ll_remove(ll_p, i);
        free(i);

        i = i_next;
    }
}

/**
 * Move a node to a new linked list
 * @param ll_ori_p pointer to the original (old) linked list
 * @param ll_new_p pointer to the new linked list
 * @param node pointer to a node
 * @param head true: be the head in the new list
 *             false be the tail in the new list
 */
void lv_ll_chg_list(lv_ll_t *ll_ori_p, lv_ll_t *ll_new_p, void *node, bool head)
{
    lv_ll_remove(ll_ori_p, node);

    if (head) {
        /*Set node as head*/
        node_set_prev(ll_new_p, node, NULL);
        node_set_next(ll_new_p, node, ll_new_p->head);

        if (ll_new_p->head != NULL) { /*If there is old head then before it goes the new*/
            node_set_prev(ll_new_p, ll_new_p->head, node);
        }

        ll_new_p->head = node;       /*Set the new head in the dsc.*/
        if (ll_new_p->tail == NULL) { /*If there is no tail (first node) set the tail too*/
            ll_new_p->tail = node;
        }
    } else {
        /*Set node as tail*/
        node_set_prev(ll_new_p, node, ll_new_p->tail);
        node_set_next(ll_new_p, node, NULL);

        if (ll_new_p->tail != NULL) { /*If there is old tail then after it goes the new*/
            node_set_next(ll_new_p, ll_new_p->tail, node);
        }

        ll_new_p->tail = node;       /*Set the new tail in the dsc.*/
        if (ll_new_p->head == NULL) { /*If there is no head (first node) set the head too*/
            ll_new_p->head = node;
        }
    }
}

/**
 * Return with head node of the linked list
 * @param ll_p pointer to linked list
 * @return pointer to the head of 'll_p'
 */
void *lv_ll_get_head(const lv_ll_t *ll_p)
{
    if (ll_p == NULL) {
        return NULL;
    }
    return ll_p->head;
}

/**
 * Return with tail node of the linked list
 * @param ll_p pointer to linked list
 * @return pointer to the tail of 'll_p'
 */
void *lv_ll_get_tail(const lv_ll_t *ll_p)
{
    if (ll_p == NULL) {
        return NULL;
    }
    return ll_p->tail;
}

/**
 * Return with the pointer of the next node after 'n_act'
 * @param ll_p pointer to linked list
 * @param n_act pointer a node
 * @return pointer to the next node
 */
void *lv_ll_get_next(const lv_ll_t *ll_p, const void *n_act)
{
    /*Pointer to the next node is stored in the end of this node.
     *Go there and return the address found there*/
    const lv_ll_node_t *n_act_d = n_act;
    n_act_d += LL_NEXT_P_OFFSET(ll_p);
    return *((lv_ll_node_t **)n_act_d);
}

/**
 * Return with the pointer of the previous node before 'n_act'
 * @param ll_p pointer to linked list
 * @param n_act pointer a node
 * @return pointer to the previous node
 */
void *lv_ll_get_prev(const lv_ll_t *ll_p, const void *n_act)
{
    /*Pointer to the prev. node is stored in the end of this node.
     *Go there and return the address found there*/
    const lv_ll_node_t *n_act_d = n_act;
    n_act_d += LL_PREV_P_OFFSET(ll_p);
    return *((lv_ll_node_t **)n_act_d);
}

/**
 * Return the length of the linked list.
 * @param ll_p pointer to linked list
 * @return length of the linked list
 */
uint32_t lv_ll_get_len(const lv_ll_t *ll_p)
{
    uint32_t len = 0;
    void *node;

    for (node = lv_ll_get_head(ll_p); node != NULL; node = lv_ll_get_next(ll_p, node)) {
        len++;
    }

    return len;
}

/**
 * Move a node before an other node in the same linked list
 * @param ll_p pointer to a linked list
 * @param n_act pointer to node to move
 * @param n_after pointer to a node which should be after `n_act`
 */
void lv_ll_move_before(lv_ll_t *ll_p, void *n_act, void *n_after)
{
    if (n_act == n_after) {
        return;    /*Can't move before itself*/
    }

    void *n_before;
    if (n_after != NULL) {
        n_before = lv_ll_get_prev(ll_p, n_after);
    } else {
        n_before = lv_ll_get_tail(ll_p);    /*if `n_after` is NULL `n_act` should be the new tail*/
    }

    if (n_act == n_before) {
        return;    /*Already before `n_after`*/
    }

    /*It's much easier to remove from the list and add again*/
    lv_ll_remove(ll_p, n_act);

    /*Add again by setting the prev. and next nodes*/
    node_set_next(ll_p, n_before, n_act);
    node_set_prev(ll_p, n_act, n_before);
    node_set_prev(ll_p, n_after, n_act);
    node_set_next(ll_p, n_act, n_after);

    /*If `n_act` was moved before NULL then it become the new tail*/
    if (n_after == NULL) {
        ll_p->tail = n_act;
    }

    /*If `n_act` was moved before `NULL` then it's the new head*/
    if (n_before == NULL) {
        ll_p->head = n_act;
    }
}

/**
 * Check if a linked list is empty
 * @param ll_p pointer to a linked list
 * @return true: the linked list is empty; false: not empty
 */
bool lv_ll_is_empty(lv_ll_t *ll_p)
{
    if (ll_p == NULL) {
        return true;
    }

    if (ll_p->head == NULL && ll_p->tail == NULL) {
        return true;
    }

    return false;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

/**
 * Set the previous node pointer of a node
 * @param ll_p pointer to linked list
 * @param act pointer to a node which prev. node pointer should be set
 * @param prev pointer to a node which should be the previous node before 'act'
 */
static void node_set_prev(lv_ll_t *ll_p, lv_ll_node_t *act, lv_ll_node_t *prev)
{
    if (act == NULL) {
        return;    /*Can't set the prev node of `NULL`*/
    }

    uint8_t *act8 = (uint8_t *)act;

    act8 += LL_PREV_P_OFFSET(ll_p);

    lv_ll_node_t **act_node_p = (lv_ll_node_t **) act8;
    lv_ll_node_t **prev_node_p = (lv_ll_node_t **) &prev;

    *act_node_p = *prev_node_p;
}

/**
 * Set the 'next node pointer' of a node
 * @param ll_p pointer to linked list
 * @param act pointer to a node which next node pointer should be set
 * @param next pointer to a node which should be the next node before 'act'
 */
static void node_set_next(lv_ll_t *ll_p, lv_ll_node_t *act, lv_ll_node_t *next)
{
    if (act == NULL) {
        return;    /*Can't set the next node of `NULL`*/
    }
    uint8_t *act8 = (uint8_t *)act;

    act8 += LL_NEXT_P_OFFSET(ll_p);
    lv_ll_node_t **act_node_p = (lv_ll_node_t **) act8;
    lv_ll_node_t **next_node_p = (lv_ll_node_t **) &next;

    *act_node_p = *next_node_p;
}
c 复制代码
/**
 * @file lv_ll.h
 * Handle linked lists. The nodes are dynamically allocated by the 'lv_mem' module.
 */

#ifndef LV_LL_H
#define LV_LL_H

#ifdef __cplusplus
extern "C" {
#endif

/*********************
 *      INCLUDES
 *********************/
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>

/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

/** Dummy type to make handling easier*/
typedef uint8_t lv_ll_node_t;

/** Description of a linked list*/
typedef struct {
    uint32_t n_size;
    lv_ll_node_t * head;
    lv_ll_node_t * tail;
} lv_ll_t;

/**********************
 * GLOBAL PROTOTYPES
 **********************/

/**
 * Initialize linked list
 * @param ll_p pointer to lv_ll_t variable
 * @param node_size the size of 1 node in bytes
 */
void lv_ll_init(lv_ll_t * ll_p, uint32_t node_size);

/**
 * Add a new head to a linked list
 * @param ll_p pointer to linked list
 * @return pointer to the new head
 */
void * lv_ll_ins_head(lv_ll_t * ll_p);

/**
 * Insert a new node in front of the n_act node
 * @param ll_p pointer to linked list
 * @param n_act pointer a node
 * @return pointer to the new node
 */
void * lv_ll_ins_prev(lv_ll_t * ll_p, void * n_act);

/**
 * Add a new tail to a linked list
 * @param ll_p pointer to linked list
 * @return pointer to the new tail
 */
void * lv_ll_ins_tail(lv_ll_t * ll_p);

/**
 * Remove the node 'node_p' from 'll_p' linked list.
 * It does not free the memory of node.
 * @param ll_p pointer to the linked list of 'node_p'
 * @param node_p pointer to node in 'll_p' linked list
 */
void lv_ll_remove(lv_ll_t * ll_p, void * node_p);

/**
 * Remove and free all elements from a linked list. The list remain valid but become empty.
 * @param ll_p pointer to linked list
 */
void lv_ll_clear(lv_ll_t * ll_p);

/**
 * Move a node to a new linked list
 * @param ll_ori_p pointer to the original (old) linked list
 * @param ll_new_p pointer to the new linked list
 * @param node pointer to a node
 * @param head true: be the head in the new list
 *             false be the tail in the new list
 */
void lv_ll_chg_list(lv_ll_t * ll_ori_p, lv_ll_t * ll_new_p, void * node, bool head);

/**
 * Return with head node of the linked list
 * @param ll_p pointer to linked list
 * @return pointer to the head of 'll_p'
 */
void * lv_ll_get_head(const lv_ll_t * ll_p);

/**
 * Return with tail node of the linked list
 * @param ll_p pointer to linked list
 * @return pointer to the tail of 'll_p'
 */
void * lv_ll_get_tail(const lv_ll_t * ll_p);

/**
 * Return with the pointer of the next node after 'n_act'
 * @param ll_p pointer to linked list
 * @param n_act pointer a node
 * @return pointer to the next node
 */
void * lv_ll_get_next(const lv_ll_t * ll_p, const void * n_act);

/**
 * Return with the pointer of the previous node after 'n_act'
 * @param ll_p pointer to linked list
 * @param n_act pointer a node
 * @return pointer to the previous node
 */
void * lv_ll_get_prev(const lv_ll_t * ll_p, const void * n_act);

/**
 * Return the length of the linked list.
 * @param ll_p pointer to linked list
 * @return length of the linked list
 */
uint32_t lv_ll_get_len(const lv_ll_t * ll_p);

/**
 * TODO
 * @param ll_p
 * @param n1_p
 * @param n2_p
void lv_ll_swap(lv_ll_t * ll_p, void * n1_p, void * n2_p);
 */

/**
 * Move a node before an other node in the same linked list
 * @param ll_p pointer to a linked list
 * @param n_act pointer to node to move
 * @param n_after pointer to a node which should be after `n_act`
 */
void lv_ll_move_before(lv_ll_t * ll_p, void * n_act, void * n_after);

/**
 * Check if a linked list is empty
 * @param ll_p pointer to a linked list
 * @return true: the linked list is empty; false: not empty
 */
bool lv_ll_is_empty(lv_ll_t * ll_p);

/**********************
 *      MACROS
 **********************/

#define lv_LL_READ(list, i) for(i = lv_ll_get_head(list); i != NULL; i = lv_ll_get_next(list, i))

#define lv_LL_READ_BACK(list, i) for(i = lv_ll_get_tail(list); i != NULL; i = lv_ll_get_prev(list, i))

#ifdef __cplusplus
} /*extern "C"*/
#endif

#endif

尾记

大家喜欢哪一种链表呢?还是说大家有更方便的链表?评论区欢迎您的讨论

相关推荐
trueEve26 分钟前
SQL,力扣题目1369,获取最近第二次的活动
算法·leetcode·职场和发展
天若有情67330 分钟前
c++框架设计展示---提高开发效率!
java·c++·算法
ahadee37 分钟前
蓝桥杯每日真题 - 第19天
c语言·vscode·算法·蓝桥杯
Theliars1 小时前
C语言之字符串
c语言·开发语言
Root_Smile1 小时前
【C++】类和对象
开发语言·c++
Reese_Cool1 小时前
【数据结构与算法】排序
java·c语言·开发语言·数据结构·c++·算法·排序算法
加密新世界1 小时前
优化 Solana 程序
人工智能·算法·计算机视觉
djk88881 小时前
.net将List<实体1>的数据转到List<实体2>
数据结构·list·.net
不爱说话郭德纲2 小时前
探索LLM前沿,共话科技未来
人工智能·算法·llm
搬砖的小码农_Sky2 小时前
C语言:结构体
c语言·数据结构