8.贝博betball网页数据结构_链表

betball贝博app Linux, 数据结构 252 次浏览 没有评论

  • 链表操作函数
  • list_entry
  • INIT_LIST_HEAD定义一个链表
  • list_add(struct list_head *new,struct list_head *head)
  • list_add_tail(struct list_head *new,struct list_head *head)
  • list_del(struct list_head *entry)
  • list_del_init()
  • list_move(struct list_head *list,struct list_head *head)
  • list_move_tail(struct list_head *list,struct list_head *head)
  • list_empty(struct list_head *head)
  • list_splice(struct list_head *list,struct list_head *head)
  • list_splice_init(struct list_head *list,struct list_head *head)
  • 链表遍历
  • list_for_each(p,list)
  • list_for_each_entry(pos,head,member)
  • list_for_each_entry_reverse(pos,head,member)
  • list_for_each_entry_safe(pos,next,head,member)
  • 链表数据结构

    linux有标准的链表数据结构,在<linux/list.h>中声明

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

    那么定义链表可以通过如下语句

    struct MyStruct{
        int a;
        int b;
        struct list_head list;
    }
    

    内核还提供了一组链表操作例程。

    使用宏container_of(ptr,type,member)可以方便的从链表指针找到父结构中的任何变量,如上面的例子中就是通过list找到MyStruct。函数实现如下:

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

    20190909100204.png
    实现的具体结构如图所示,实现功能为:已知结构体type,且member成员的地址ptr,求type的首地址

    上述代码第三行中offsetof的原型解释

    #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

    首先引入一个特殊的实例代码((int)&((struct test *)0)->k)解释:

    struct test *代表指向该结构体起始地址的指针,将0强制转换为此类型,则令起始指针为0,然后->指向的为成员变量的指针,那么(struct test *)0)->k就代表起始指针为0的test结构体中,k元素的指针,那么也就是代表k元素与结构体起始地址的偏移量。

    那么offsetof(type,member)代表的则是size

    然后通过__mptr-size,就是type的首地址,那么第二行的作用是??为什么要把原生的ptr,转换为一个_mptr呢?

    首先来看_mptr的定义,类型为 typeof( ((type )0)->member ),也就是member成员的类型指针,ptr也是member成员类型指针。

    在开发过程中,如果输入的参数错误,ptr和member类型不匹配,那么编译时的赋值操作两端编译不一样就会报错!!(所以编译时候的warnning还是要看的!)

    链表操作函数

    list_entry

    获取链表的入口点,提供了更加标准化的接口,其实就是container_of的直接调用

    #define list_entry(ptr,type,member) container_of(ptr,type,member)
    

    INIT_LIST_HEAD定义一个链表

    list_add(struct list_head *new,struct list_head *head)

    list_add_tail(struct list_head *new,struct list_head *head)

    list_del(struct list_head *entry)

    list_del_init()

    list_move(struct list_head *list,struct list_head *head)

    list_move_tail(struct list_head *list,struct list_head *head)

    list_empty(struct list_head *head)

    list_splice(struct list_head *list,struct list_head *head)

    list_splice_init(struct list_head *list,struct list_head *head)

    上述函数均为外部链表函数,其中还需要去获取list_head中的prev和next指针,如果已经拿到了prev和next指针,可以直接调用内部函数,如__list_del(prev,next)

    链表遍历

    list_for_each(p,list)

  • p为预先定义的临时变量,用来存储当前所遍历的元素。
  • list为整个链表的list_head,用来表示整个链表。
  • 仅仅获取到list_head往往是无意义的,真正的遍历过程中,需要通过list_entry来获取链表的真正结构体

    struct list_head *p;
    struct MyStruct *f;
    
    list_for_each(p,&MyList){
        f = list_entry(p,MyStruct,MyList);
    }
    

    内核当然也提供了更直接的遍历方法

    list_for_each_entry(pos,head,member)

    list_for_each_entry实例-内核文件系统的更新通知机制

    list_for_each_entry_reverse(pos,head,member)

    通过prev进行遍历,与上述的方法基本相同

    list_for_each_entry_safe(pos,next,head,member)

    使用带safe的方法可以实现稳定的遍历,遍历的过程中,可以删除链表中的某一项而无需担心链表断开,因为safe的实现过程中,每访问一个链表元素都已经将该元素进行暂存,访问完毕后直接再读出即可。
    看实现代码上的区别
    链表遍历的safe与否的区别

    发表评论

    邮箱地址不会被公开。

    Go