在 linux 内核中,有大量的数据结构需要用到双向链表,例如进程、文件、模块、页面等。 若采用双向链表的传统实现方式,需要为这些数据结构维护各自的链表,并且为每个链表都 要设计插入、删除等操作函数。因为用来维持链表的 next 和 prev 指针指向对应类型的对 象,因此一种数据结构的链表操作函数不能用于操作其它数据结构的链表。 比如,我们需要分别定义星星和 web 服务器超时的链表结构 :
//一. web 服务器超时的链表结构
typedef struct {
int fd;
time_t timeout; // 使用超时时刻的时间戳表示
}ConnTimeout;
struct Link_Node
{
ConnTimeout conn;
struct Link_Node* next;
}
typedef struct {
int x;
int y;
enum STATUS stat;
unsigned radius;
int step;
int color;
}STAR;
struct Link_Node {
STAR star;
struct Link_Node* next;
}
有没有一种方式,可以让多个链表共享同一套链表的操作?请看下图:
typedef struct _DoubleLinkNode {
struct _DoubleLinkNode* next; //下一个节点的指针域
struct _DoubleLinkNode *prev; //上一个结点的指针域
}DbLinkNode;
typedef struct {
int fd;
time_t timeout; // 使用超时时刻的时间戳表示
DbLinkNode node; // 双向链表节点“挂件”
}ConnTimeout;
如何访问挂载的数据,原理如下
实现要点
使用 offsetof 可以根据链表节点在结构体中的地址逆推出结构体变量的位置.
typedef struct {
int fd ;
time_t timeout; // 使用超时时刻的时间戳表示
DbLinkNode node; // 双向链表节点“挂件”
}ConnTimeout;
通过节点访问承载数据
//通过节点访问到节点承载的数据
ConnTimeout *ct = new ConnTimeout;
DbLinkNode *p = &(ct->node);
cout<<"请输入超时节点对应的 fd: ";
cin>>ct->fd;
cout<<"\n 通过链表中的节点访问节点上承载的数据:"<<endl;
int offset = offsetof(ConnTimeout, node);
ConnTimeout *tmp = (ConnTimeout *)((size_t)p-offset);
printf("offset: %d\n", offset);
printf("通过链表节点 node 访问到的数据:%d\n", tmp->fd);
源码实现
#include<iostream>
#include<string>
#include<stdlib.h>
using namespace std;
typedef struct _DoubleLinkNode {
//int data; //结点的数据域
struct _DoubleLinkNode* next; //下一个节点的指针域
struct _DoubleLinkNode* prev; //上一个结点的指针域
}DbLinkNode, DbLinkList; //LinkList 为指向结构体 LNode 的指针类型
typedef struct {
int fd;
time_t timeout; // 使用超时时刻的时间戳表示
DbLinkNode node; // 双向链表节点“挂件”
}ConnTimeout;
typedef struct {
int x; //星星的 x 坐标
int y; //星星的 y 坐标
enum STATUS stat; //状态
unsigned radius; //星星的半径
int step; //每次跳跃的间隔
int color; //星星的颜色
DbLinkNode node; // 双向链表节点“挂件”
}STAR;
bool DbList_Init(DbLinkList& L)//构造一个空的双向链表 L
{
L.next = NULL; //头结点的 next 指针域置空
L.prev = NULL; //头结点的 prev 指针域置空
return true;
}
//尾插法
bool DbListInsert_back(DbLinkList& L, DbLinkNode& node) {
DbLinkNode* last = NULL;
last = &L;
while (last->next) last = last->next;
node.next = NULL;
last->next = &node;
node.prev = last;
return true;
}
int main(void) {
ConnTimeout* cl = NULL, * s = NULL;
STAR* sl = NULL;
int n = 0;
//1.初始化一个空的双向链表
cl = new ConnTimeout;
cl->fd = -1;
sl = new STAR;
sl->x = -1;
DbList_Init(cl->node);
DbList_Init(sl->node);
//2.使用尾插法插入数据
cout << "尾插法创建双向链表" << endl;
std::cout << "请输入元素个数 n:";
cin >> n;
cout << "\n 请依次输入 n 个元素的文件句柄:" << endl;
while (n > 0) {
s = new ConnTimeout; //生成新节点 s
cin >> s->fd;
printf("s 的地址:%p node: %p\n", s, &(s->node));
DbListInsert_back(cl->node, s->node);
n--;
}
//3.根据链表节点访问数据
DbLinkNode* p = NULL;
p = &(cl->node);
cout << "遍历连接超时链表中的节点:" << endl;
while (p) {
int offset = offsetof(ConnTimeout, node);
ConnTimeout* ct = (ConnTimeout*)((size_t)p - offset);
cout << ct->fd << endl;
p = p->next;
}
//4. 销毁双向链表
p = &(cl->node);
cout << "销毁连接超时链表中的节点:" << endl;
while (p) {
int offset = offsetof(ConnTimeout, node);
ConnTimeout* ct = (ConnTimeout*)((size_t)p - offset);
printf("offset: %u ct: %p p:%p\n", offset, ct, p);
cout << ct->fd << endl;
p = p->next; delete ct;
}
system("pause");
return 0;
}