在Linux下链表使用介绍二:list_add/list_add_tail、list_for_each/list_for_each_safe等

     在上一篇我们介绍了list_head;并定义了结构体节点,如何把该类型的结构体节点串成一个链表呢?

1、必须专门定义一个专有链表头,并初始化:

struct list_head ListHead;
INIT_LIST_HEAD(&ListHead);

其中INIT_LIST_HEAD的定义为

#define INIT_LIST_HEAD(ptr) do { \
       (ptr)->next = (ptr); \
       (ptr)->prev = (ptr); \
     } while (0)

2、用list_add/list_add_tail 来添加链表节点

list_add(&entry, &ListHead);

其中list_add,list_add_tail定义为

static inline void list_add(struct list_head *new, struct list_head *head)

{

  __list_add(new, head, head->next);

}

static inline void __list_add( struct list_head *new, struct list_head *prev, struct list_head *next)

{

  next->prev = new;

  new->next = next;

  new->prev = prev;

  prev->next = new;

}

void list_add_tail(struct list_head *new, struct list_head *head);
static __inline__ void list_add_tail(struct list_head *_new, struct list_head *head)
{
    __list_add(_new, head->prev, head);
}
 
/*
 * Insert a new 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,
                  struct list_head * prev,
                  struct list_head * next)
{
    next->prev = _new;
    _new->next = next;
    _new->prev = prev;
    prev->next = _new;
}
3、遍历链表list_for_each/list_for_each_safe
我们知道list_for_each_entry会用到list_entry,而list_entry用到container_of,而container_of中有offsetof,所以从offsetof讲起。
offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
    理解offsetof的关键在于&((TYPE *)0)->MEMBER,几乎可以说只要理解了这一部分,后面的几个函数都能够解决,那么我们看看这一部分究竟完成了怎样的工作。根据优先级的顺序,最里面的小括号优先级最高,TYPE *将整型常量0强制转换为TYPE型的指针,且这个指针指向的地址为0,也就是将地址0开始的一块存储空间映射为TYPE型的对象,接下来再对结构体中MEMBER成员进行取址,而整个TYPE结构体的首地址是0, 这里获得的地址就是MEMBER成员在TYPE中的相对偏移量 。再将这个偏移量强制转换成size_t型数据(无符号整型)。 
 
接下来讲讲container_of
/*
* 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.
*
*/
#define container_of(ptr, type, member) ({                      /
     const typeof( ((type *)0)->member ) *__mptr = (ptr);    /
     (type *)( (char *)__mptr - offsetof(type,member) );}) 
    首先可以看出container_of被预定义成一个函数,函数的第一句话,通过((type *)0)->member定义一个MEMBER型的指针__mptr,这个指针指向ptr,所以第一句话获取到了我们要求的结构体,它的成员member的地址,接下来我们用这个地址减去成员member在结构体中的相对偏移量,就可以获取到所求结构体的地址, (char *)__mptr - offsetof(type,member)就实现了这个过程,最后再把这个地址强制转换成type型指针, 就获取到了所求结构体指针, define预定义返回最后一句话的值, 将所求结构体指针返回。  
    所以整个container_of的功能 就是通过指向结构体成员member的指针ptr获取指向整个结构体的指针 。container_of清楚了,那list_entry就更是一目了然了。
 
/*list_entry                                                                                                                                                                                                                                                                                      /**
* 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)     
功能等同于container_of。接下来分析我们最终想要知道的list_for_each_entry的实现过程。
 
/**
* 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_entry的基础上分析list_for_each_entry本来是一件比较轻松的事情,但在这里还是要强调一下双向链表及链表头的概念,否则对list_for_each_entry的理解还是一知半解。建立一个双向链表通常有一个独立的用于管理链表的链表头,链表头一般是不含有实体数据的,必须用INIT_LIST_HEAD()进行初始化,表头建立以后,就可以将带有数据结构的实体链表成员加入到链表中,链表头和链表的关系如图所示:

    list_for_each_entry 和 list_for_each_entry_safe函数分析
    链表头和链表的关系清楚了,我们才能完全理解list_for_each_entry。list_for_each_entry被预定义成一个for循环语句,for循环的第一句话获取(head)->next指向的member成员的数据结构指针,也就是将pos初始化为除链表头之外的第一个实体链表成员,for的第三句话通过pos->member.next指针遍历整个实体链表,当pos->member.next再次指向我们的链表头的时候跳出for循环。整个过程没有对链表头进行遍历(不需要被遍历),所以使用list_for_each_entry遍历链表必须从链表头开始。 因此可以看出, list_for_each_entry的功能就是遍历以head为链表头的实体链表,对实体链表中的数据结构进行处理
 
接下来看看list_for_each_entry_safe
/**
* 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,list_for_each_entry_safe用指针n对链表的下一个数据结构进行了临时存储,所以如果在遍历链表的时候可能要删除链表中的当前项,用list_for_each_entry_safe可以安全的删除,而不会影响接下来的遍历过程(用n指针可以继续完成接下来的遍历, 而list_for_each_entry则无法继续遍历)。
 
end!!

 

 

 

  • 21
    点赞
  • 103
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
list_for_each_entry_safeLinux链表遍历的一个函数,用于遍历一个双向链表,并在遍历过程中安全地删除、插入或替换节点。它的语法如下: ``` list_for_each_entry_safe(pos, n, head, member) { // 执行操作 } ``` 其中,pos和n是指向链表节点的指针,head是链表头指针,member是链表节点中指向下一个节点的指针成员名。这个函数会遍历链表中的所有节点,并在每次遍历时执行操作,直到遍历完整个链表。在执行操作时,可以安全地删除、插入或替换节点,因为list_for_each_entry_safe会在遍历过程中保证节点的正确性。 例如,下面的代码演示了如何使用list_for_each_entry_safe来遍历一个链表,并删除其中的某些节点: ``` struct my_node { int data; struct list_head list; }; struct list_head my_list; // 初始化链表 INIT_LIST_HEAD(&my_list); // 添加节点 struct my_node *node1 = kmalloc(sizeof(struct my_node), GFP_KERNEL); node1->data = 1; list_add_tail(&node1->list, &my_list); struct my_node *node2 = kmalloc(sizeof(struct my_node), GFP_KERNEL); node2->data = 2; list_add_tail(&node2->list, &my_list); struct my_node *node3 = kmalloc(sizeof(struct my_node), GFP_KERNEL); node3->data = 3; list_add_tail(&node3->list, &my_list); // 遍历链表并删除节点 struct my_node *pos, *n; list_for_each_entry_safe(pos, n, &my_list, list) { if (pos->data == 2) { list_del(&pos->list); kfree(pos); } } ``` 在这个例子中,我们首先创建了一个链表,并向其中添加了三个节点。然后使用list_for_each_entry_safe遍历链表,并在每次遍历时判断节点中的数据是否等于2。如果是,则使用list_del删除节点,并释放节点内存。最终,链表中只剩下两个节点。
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

咸鱼弟

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

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

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

打赏作者

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

抵扣说明:

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

余额充值