5分钟快速上手ViMax:用AI一键创作专业级视频的完整指南
2026/8/7 17:43:29
**侵入式链表(Intrusive Linked List)**是一种特殊的链表实现方式,它的特点是:链表节点直接嵌入到数据结构内部,而不是通过指针指向独立的数据节点。
在侵入式链表中,链表节点(list_head)是数据结构的一个成员,而不是独立存在的。这种设计使得链表操作更加高效,并且不需要额外的内存分配。
传统链表通常采用以下结构:
// 传统链表节点结构structlist_node{void*data;// 指向实际数据structlist_node*next;// 指向下一个节点structlist_node*prev;// 指向前一个节点};特点:
说明:list_node中的data字段是一个指针,指向实际的数据对象,数据对象和链表节点在内存中是分离的。
还有一种常见的链表实现方式,数据直接嵌入在链表节点中:
// 自包含链表节点结构structlist_node{inta;// 数据直接嵌入在节点中structlist_node*next;// 指向下一个节点structlist_node*prev;// 指向前一个节点};特点:
说明:数据字段(如int a)直接嵌入在链表节点结构体中,链表节点本身就是数据容器。
适用场景:
侵入式链表的结构:
// 侵入式链表节点结构structlist_head{structlist_head*next;// 指向下一个节点structlist_head*prev;// 指向前一个节点};// 数据结构中包含list_headstructmy_data{intvalue;charname[32];structlist_headlist;// 链表节点嵌入在数据结构中};特点:
container_of宏从节点指针获取完整数据结构说明:list_head直接嵌入在my_data结构体中,数据对象和链表节点在内存中是连续的,通过list成员连接。
| 特性 | 传统链表 | 自包含链表 | 侵入式链表 |
|---|---|---|---|
| 数据存储 | 外部对象(通过指针) | 节点内部 | 数据结构内部 |
| 灵活性 | 高(可存储任意类型) | 低(固定类型) | 高(任意类型) |
| 内存开销 | 中等(需要指针) | 低 | 最低 |
| 适用场景 | 通用场景 | 简单固定类型 | 系统编程、高性能场景 |
list_head成员Linux内核在include/linux/list.h中实现了完整的侵入式双向链表。这是经过多年优化的工业级实现。
linux-4.14.7/include/linux/list.hstructlist_head{structlist_head*next;// 指向下一个节点structlist_head*prev;// 指向前一个节点};这是侵入式链表的核心数据结构,只包含两个指针,非常简洁。
// 初始化宏定义#defineLIST_HEAD_INIT(name){&(name),&(name)}// 声明并初始化链表头#defineLIST_HEAD(name)\structlist_headname=LIST_HEAD_INIT(name)// 初始化函数staticinlinevoidINIT_LIST_HEAD(structlist_head*list){WRITE_ONCE(list->next,list);list->prev=list;}初始化后的状态:
next和prev都指向自身/** * list_add - 在链表头部添加新节点 * @new: 要添加的新节点 * @head: 链表头 * * 在head之后插入new节点,适合实现栈(LIFO) */staticinlinevoidlist_add(structlist_head*new,structlist_head*head){__list_add(new,head,head->next);}/** * list_add_tail - 在链表尾部添加新节点 * @new: 要添加的新节点 * @head: 链表头 * * 在head之前插入new节点,适合实现队列(FIFO) */staticinlinevoidlist_add_tail(structlist_head*new,structlist_head*head){__list_add(new,head->prev,head);}/** * __list_add - 在两个已知节点之间插入新节点 * @new: 新节点 * @prev: 前一个节点 * @next: 后一个节点 */staticinlinevoid__list_add(structlist_head*new,structlist_head*prev,structlist_head*next){if(!__list_add_valid(new,prev,next))return;next->prev=new;new->next=next;new->prev=prev;WRITE_ONCE(prev->next,new);}/** * list_del - 从链表中删除节点 * @entry: 要删除的节点 * * 注意:删除后节点处于未定义状态 */staticinlinevoidlist_del(structlist_head*entry){__list_del_entry(entry);entry->next=LIST_POISON1;// 设置为毒药指针,便于调试entry->prev=LIST_POISON2;}staticinlinevoid__list_del_entry(structlist_head*entry){if(!__list_del_entry_valid(entry))return;__list_del(entry->prev,entry->next);}staticinlinevoid__list_del(structlist_head*prev,structlist_head*next){next->prev=prev;WRITE_ONCE(prev->next,next);}/** * list_del_init - 删除节点并重新初始化 * @entry: 要删除的节点 * * 删除后节点可以重新使用 */staticinlinevoidlist_del_init(structlist_head*entry){__list_del_entry(entry);INIT_LIST_HEAD(entry);}/** * list_for_each - 遍历链表 * @pos: 用作循环游标的list_head指针 * @head: 链表头 */#definelist_for_each(pos,head)\for(pos=(head)->next;pos!=(head);pos=pos->next)/** * list_for_each_entry - 遍历链表中的数据项 * @pos: 用作循环游标的数据结构指针 * @head: 链表头 * @member: list_head在数据结构中的成员名 */#definelist_for_each_entry(pos,head,member)\for(pos=list_first_entry(head,typeof(*pos),member);\&pos->member!=(head);\pos=list_next_entry(pos,member))/** * list_for_each_entry_safe - 安全遍历,允许在遍历时删除节点 * @pos: 用作循环游标的数据结构指针 * @n: 另一个数据结构指针,用作临时存储 * @head: 链表头 * @member: list_head在数据结构中的成员名 */#definelist_for_each_entry_safe(pos,n,head,member)\for(pos=list_first_entry(head,typeof(*pos),member),\n=list_next_entry(pos,member);\&pos->member!=(head);\pos=n,n=list_next_entry(n,member))/** * list_empty - 判断链表是否为空 * @head: 链表头 */staticinlineintlist_empty(conststructlist_head*head){returnREAD_ONCE(head->next)==head;}/** * list_move - 将节点从一个链表移动到另一个链表头部 * @list: 要移动的节点 * @head: 目标链表头 */staticinlinevoidlist_move(structlist_head*list,structlist_head*head){__list_del_entry(list);list_add(list,head);}container_of宏是侵入式链表的灵魂,它能够从结构体成员的指针获取整个结构体的指针。
/** * container_of - 从成员指针获取包含它的结构体指针 * @ptr: 成员指针 * @type: 结构体类型 * @member: 成员在结构体中的名称 */#definecontainer_of(ptr,type,member)({\consttypeof(((type*)0)->member)*__mptr=(ptr);\(type*)((char*)__mptr-offsetof(type,member));})#defineoffsetof(TYPE,MEMBER)((size_t)&((TYPE*)0)->MEMBER)原理:
0强制转换为TYPE*类型MEMBER成员,得到成员相对于结构体起始地址的偏移量&((TYPE *)0)->MEMBER就是偏移量示例:
structmy_data{intvalue;// 偏移量: 0charname[32];// 偏移量: 4structlist_headlist;// 偏移量: 36 (假设)};// offsetof(struct my_data, list) = 36假设我们有:
structmy_data{intvalue;structlist_headlist;};structmy_data*data;structlist_head*list_ptr=&data->list;现在要从list_ptr获取data:
// 步骤1: 获取list成员的类型并验证consttypeof(((structmy_data*)0)->list)*__mptr=list_ptr;// 步骤2: 将指针转换为char*以便进行字节级运算char*__mptr_char=(char*)__mptr;// 步骤3: 减去偏移量得到结构体起始地址structmy_data*result=(structmy_data*)(__mptr_char-offsetof(structmy_data,list));内存布局示意:
地址: 0x1000 0x1024 [my_data] [list] ^ ^ | | data list_ptr offsetof = 0x1024 - 0x1000 = 0x24 从list_ptr获取data: data = list_ptr - offsetof = 0x1024 - 0x24 = 0x1000 ✓typeof用于类型检查,确保传入的ptr确实是member类型的指针,提高代码安全性。
#include<stdio.h>#include<stdlib.h>#include"list.h"// 假设包含了list.h// 定义数据结构structstudent{intid;charname[32];intage;structlist_headlist;// 链表节点};intmain(void){// 初始化链表头LIST_HEAD(student_list);// 创建学生数据structstudent*s1=malloc(sizeof(structstudent));s1->id=1;strcpy(s1->name,"Alice");s1->age=20;INIT_LIST_HEAD(&s1->list);structstudent*s2=malloc(sizeof(structstudent));s2->id=2;strcpy(s2->name,"Bob");s2->age=21;INIT_LIST_HEAD(&s2->list);// 添加到链表list_add(&s1->list,&student_list);list_add(&s2->list,&student_list);// 遍历链表structstudent*pos;list_for_each_entry(pos,&student_list,list){printf("ID: %d, Name: %s, Age: %d\n",pos->id,pos->name,pos->age);}// 清理list_for_each_entry_safe(pos,n,&student_list,list){list_del(&pos->list);free(pos);}return0;}// 一个对象可以同时属于多个链表structtask{intpid;charname[32];structlist_headrun_list;// 运行队列structlist_headwait_list;// 等待队列structlist_headall_tasks;// 所有任务列表};// 初始化structtask*t=malloc(sizeof(structtask));INIT_LIST_HEAD(&t->run_list);INIT_LIST_HEAD(&t->wait_list);INIT_LIST_HEAD(&t->all_tasks);// 添加到不同链表list_add(&t->run_list,&run_queue);list_add(&t->all_tasks,&task_list);// 使用list_add_tail实现FIFO队列LIST_HEAD(queue);voidenqueue(structlist_head*item){list_add_tail(item,&queue);}structlist_head*dequeue(void){if(list_empty(&queue))returnNULL;structlist_head*item=queue.next;list_del(item);returnitem;}// 使用list_add实现LIFO栈LIST_HEAD(stack);voidpush(structlist_head*item){list_add(item,&stack);}structlist_head*pop(void){if(list_empty(&stack))returnNULL;structlist_head*item=stack.next;list_del(item);returnitem;}Linux内核中广泛使用侵入式链表:
当一个对象需要同时属于多个链表时,侵入式链表特别有用:
structprocess{intpid;structlist_headrunq;// 运行队列structlist_headwaitq;// 等待队列structlist_headchildren;// 子进程列表structlist_headsiblings;// 兄弟进程列表};list_head成员container_of宏的理解需要一定时间container_of宏:这是侵入式链表的精髓