开发中接触Linux越来越多,休息放松之余,免不了翻看翻看神秘的Linux的内核。看到双向链表时,觉得挺有意思的,此文记下。
  作为众多基础数据结构中的一员,双向循环链表在各种“教科书”中的实现是相当的标准和一致的。
  大概是下面这个样子:
  1 typedef struct node_tag{
  2     //T data;
  3     struct node_tag *prev;
  4     struct node_tag *next;
  5 }node;
  当你需要某种类型的链表时,把数据成员之类的往节点里塞是了。比如菜谱链表,里面可以有宫爆鸡丁,酸辣粉,地三鲜,水煮鱼,麻辣鸡翅。。。
  嗯,当你需要另外一种链表时,接着如法炮制,只要功夫深,几十上百也不是问题。有一部分人善于解决这类问题,它们叫做CP程序员(Copy-Paste),
  不要问我为什么知道。C++模板在这点上能实现通用特性,但不在本次内容之列了。
  有着极客精神的Linux,在内核中肯定不会像上面这么做的。内核中有大量的数据结构需要使用双向链表,诸如进程、模块、文件。
  难道要人去维护各种类型的双向链表?而且还是不能复用的链表。我想没多少人愿意把时间花在这种事情上吧。维护一种通用的不好了。
  链表节点,作为一个“连接件”,本质的内容是把一些对象链接起来,至于对象内部存储的数据,是可以不用知道的。
  在include/linux/list.h文件中,有使用这样的一个"连接件“:
  struct list_head {
  struct list_head *next, *prev;
  };
  和node_tag相比,少了数据部分。
  list_head作为独立变量时,充当的是链表头的角色;如果作为结构体成员时,则是“连接件”的角色。
  在这样的实现方式下,要获得某种类型的链表,只需在宿主结构中声明一个list_head成员,还可以任意的取名;
  关键是,链表操作只需以list_head为对象进行实现;剩下的问题是,在遍历链表时,该如何获取宿主结构的首地址?
  毕竟链表是用来装内容用的。这里利用编译器的一个小技巧可以算出地址偏移
  #define offsetof(s,m)   (size_t)&(((s *)0)->m)
  有了list_head相对宿主结构首地址的偏移,和自身地址来个加减可以得到宿主的首地址,接下该怎么操作怎么操作了。
  个人觉得这里面有面向对象的意思。抽取出共同的“连接件” list_head,链表操作也以list_head为对象进行设计,
  除了要具体访问操作宿主结构之外,全部都是共性的东西。