什么是链表?
链表可以想象成就是一条锁链,每一个环上面都可以刻些信息在上面,有新的信息要记录就拿出新的一环把信息刻上去然后打开锁链一头加上去,要使用上面的某一环取下来即可。把这些信息串起来方便管理(排序、增减、查找)
该怎么实现链表?
比如说把一个班的学生用链表的形式连接起来,可以是这样:
但是以上的将next指向下一个节点(直接指向下一个student)的设计方法用起来没什么毛病,但是考虑下每设计一种链表,都要再实现一套增删改的方法,太费时间!
那该怎么去设计一个通用的链表?
在linux中,链表被抽象成了list_head结构,里面就两个成员一目了然,特别的是,next和prev都是list_head的指针,这就是linux链表的优雅之处:
struct list_head {
struct list_head *next;
struct list_head *prev;
};
要用的时候把它装进某个结构就可以。就像下图:
可以看到,遍历的时候,只需要在list_head之间遍历即可,list.h提供的方法如下:
/**
* 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_head的遍历方法访问next成员直达next不等于自己的地址为止。这里不得不提一下list_head的构造和初始化方法了:
#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)
{
WRITE_ONCE(list->next, list);
list->prev = list;
}
代码不复杂,即让next和prev都指向自己,个人理解就是将其初始化成一个只有一个节点的双向链表。如果list_head不进行初始化,使用时会出问题。
到这里又有了新的问题,只拿到一个list_head指针,怎么获得它所嵌入的结构体?例如拿到了指向二哈包含的list_head指针之后,怎么才能得到指向二哈的指针呢?方法如下:
/* 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_head within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
C语言相较其他高级语言有对内存操作的先天优势,container_of就是一个例子:
假设当前情形是这样的:
有一个链表里面包含菜刀、哈士奇、奥特曼三个节点(其他节点已经去掉),通过遍历链表list_for_each现在pos指向了哈士奇包含的my_list,进一步使用:
struct *二哈 = container_of(pos, struct 哈士奇, my_list);
即可获得指向这只二哈的指针。
container_of(pos, struct 哈士奇, my_list)的原理是什么?
container_of的实现:
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \
!__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)(__mptr - offsetof(type, member))); })
直接上图:现在假设struct 哈士奇里的所有成员都只占4个字节,那么其内存分布以及获得哈士奇结构指针的方法大概是这样的:
可能offsetof的实现方法和上图不一样,但是原理基本就是这样的。
list.h中有很多API,基本都是上面提到函数封装出来的。