最近在看ucore操作系统的实验指导。里面提要一个双向循环链表的数据结构,挺有意思的。

其实这个数据结构本身并不复杂。在普通链表的基础上加一个前向指针,我们就得到了双向链表,再把头尾节点连起来就是双向循环链表了。一般的实现方式如下:

typedef struct link_node {
ele_type element;
link_node *prev, *next;
} link_node; ... // 相关的操作比较简单,这里就不实现了

但是这样有一定的局限性,就是里面的数据域(element)是固定类型的,如果有很多种成员变量都需要这种链表结构,就免不了重复编码。

记得之前看redis代码的时候也有这种类似用C语言实现多态的情况,那里面是用一个void *ptr的指针来指向具体的底层实现。具体实现如下:

typedef struct link_node_r {
void *ptr
link_node_r *prev, *next;
} link_node_r;

然后,在真正使用的时候把这个类型强转一下。


而ucore里面采用了一种不同的实现方式。实现如下:

struct list_entry {
struct list_entry *prev, *next;
};

可以看到,这个链表里面并没有数据域。是的你没有看错,这个数据结构里没有数据域!可是如果没有数据域的话,这个数据结构有什么实用价值呢?这里是把链表域放到需要使用链表的具体结构了,算是逆向思维吧。 具体实现如下:

/* *
* struct Page - Page descriptor structures. Each Page describes one
* physical page. In kern/mm/pmm.h, you can find lots of useful functions
* that convert Page to other data types, such as phyical address.
* */
struct Page {
atomic_t ref; // page frame's reference counter
……
list_entry_t page_link; // free list link
};

可以看到,这里是需要使用双向循环链表的数据类型应用了list_entry_t结构,这样子,链表的结构和操作就不需要变了。

但是,这里有一个问题。当我们查找到链表中某一个节点是,怎么获取到其宿主结构,也就是说,要怎么拿到这个对象的其他部分。正常思维都是从一个结构里拿到它的子结构xx->xxx或者xx.xxx。这里我们要用到一个le2page宏。

le2page宏的使用相当简单:

// convert list entry to page
#define le2page(le, member) \
to_struct((le), struct Page, member)

而相比之下,它的实现用到的to_struct宏和offsetof宏则有一些难懂:

/* Return the offset of 'member' relative to the beginning of a struct type */
#define offsetof(type, member) \
((size_t)(&((type *)0)->member)) /* *
* to_struct - get the struct from a ptr
* @ptr: a struct pointer of member
* @type: the type of the struct this is embedded in
* @member: the name of the member within the struct
* */
#define to_struct(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))

这里采用了一个利用gcc编译器技术的技巧,即先求得数据结构的成员变量在本宿主数据结构中的偏移量,然后根据成员变量的地址反过来得出属主数据结构的变量的地址。

我们首先来看offsetof宏,size_t最终定义与CPU体系结构相关,本实验都采用Intel X86-32 CPU,顾szie_t等价于 unsigned int。 ((type *)0)->member的设计含义是什么?其实这是为了求得数据结构的成员变量在本宿主数据结构中的偏移量。为了达到这个目标,首先将0地址强制"转换"为type数据结构(比如struct Page)的指针,再访问到type数据结构中的member成员(比如page_link)的地址,即是type数据结构中member成员相对于数据结构变量的偏移量。在offsetof宏中,这个member成员的地址(即“&((type *)0)->member)”)实际上就是type数据结构中member成员相对于数据结构变量的偏移量。对于给定一个结构,offsetof(type,member)是一个常量,to_struct宏正是利用这个不变的偏移量来求得链表数据项的变量地址。接下来再分析一下to_struct宏,可以发现 to_struct宏中用到的ptr变量是链表节点的地址,把它减去offsetof宏所获得的数据结构内偏移量,即就得到了包含链表节点的属主数据结构的变量的地址。

一种神奇的双向循环链表C语言实现的更多相关文章

  1. 带头结点的双向循环链表----------C语言

    /***************************************************** Author:Simon_Kly Version:0.1 Date: 20170520 D ...

  2. C语言通用双向循环链表操作函数集

    说明 相比Linux内核链表宿主结构可有多个链表结构的优点,本函数集侧重封装性和易用性,而灵活性和效率有所降低.     可基于该函数集方便地构造栈或队列集.     本函数集暂未考虑并发保护. 一  ...

  3. 双向循环链表(C语言描述)(四)

    下面以一个电子英汉词典程序(以下简称电子词典)为例,应用双向循环链表.分离数据结构,可以使逻辑代码独立于数据结构操作代码,程序结构更清晰,代码更简洁:电子词典的增.删.查.改操作分别对应于链表的插入. ...

  4. 双向循环链表(C语言描述)(一)

    双向循环链表是链表的一种,它的每个节点也包含数据域和指针域.为了方便程序维护,可以单独为数据域定义一种数据类型,这里以整型为例: typedef int LinkedListData; 双向循环链表( ...

  5. c语言双向循环链表

    双向循环链表,先来说说双向链表,双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱.所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继 ...

  6. 数据结构8: 双向链表(双向循环链表)的建立及C语言实现

    之前接触到的链表都只有一个指针,指向直接后继,整个链表只能单方向从表头访问到表尾,这种结构的链表统称为 “单向链表”或“单链表”. 如果算法中需要频繁地找某结点的前趋结点,单链表的解决方式是遍历整个链 ...

  7. 1.Go语言copy函数、sort排序、双向链表、list操作和双向循环链表

    1.1.copy函数 通过copy函数可以把一个切片内容复制到另一个切片中 (1)把长切片拷贝到短切片中 package main import "fmt" func main() ...

  8. 【C语言教程】“双向循环链表”学习总结和C语言代码实现!

    双向循环链表 定义 双向循环链表和它名字的表意一样,就是把双向链表的两头连接,使其成为了一个环状链表.只需要将表中最后一个节点的next指针指向头节点,头节点的prior指针指向尾节点,链表就能成环儿 ...

  9. c语言编程之双向循环链表

    双向循环链表就是形成两个环,注意每个环的首尾相连基本就可以了. 程序中采用尾插法进行添加节点. #include<stdio.h> #include<stdlib.h> #de ...

随机推荐

  1. IP实时传输协议RTP/RTCP详解

    1.简介 目前,在IP网络中实现实时语音.视频通信和应用已经成为网络应用的一个主流技术和发展方向,本文详细介绍IP协议族中用于实时语音.视频数据传输的标准协议RTP( Real-time Transp ...

  2. 绘图-CAD-改快捷键

    CAD的快捷键应该在左手边,左手不离开键盘,右手不离开鼠标,这样的操作才有效率. arc 圆弧命令原命令是ARC Q 圆(CIRCLE) C 原快捷键C被定义为COPY,Q的形状类似圆,只是多了一个尾 ...

  3. 积跬步,聚小流------java信息生成图片

    需求: 是在做证书的时候碰到的这个问题. 当时需求是能够进行在线打印证书,第一次进行的操作是直接打印html,并且已经排好版(用jqprint插件)进行打印.在打印时碰到了兼容的问题,另外因为背景图片 ...

  4. Bitcoin学习篇之---PPS和PPLNS挖矿模式介绍

    PPS和PPLNS挖矿模式介绍 比特币每10分钟产生一个区块,会有千万人竞争.而这个区块终于仅仅归1个人全部.其他人都颗粒无收. 你或许要挖5年才干获得一个区块. 组队挖矿就是.一旦队伍里不论什么人获 ...

  5. BZOJ1492:[NOI2007]货币兑换 (CDQ分治+斜率优化DP | splay动态维护凸包)

    BZOJ1492:[NOI2007]货币兑换 题目传送门 [问题描述] 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和B纪念券(以下简称B券).每个持有金券的 ...

  6. NEU 1006 Intermediary

    1006: Intermediary 时间限制: 1 Sec  内存限制: 128 MB提交: 261  解决: 25[提交][状态][讨论版] 题目描述 It is widely known tha ...

  7. [jzoj 3175] 数树数 解题报告 (树链剖分)

    interlinkage: https://jzoj.net/senior/#main/show/3175 description: 给定一棵N 个节点的树,标号从1~N.每个点有一个权值.要求维护两 ...

  8. Docker+ELK搭建

    换了个运行环境,重新搭建一套公司本地内部的ELK,之前也搭过(可访问:https://yanganlin.com/31.html),最近做什么事情都想用Docker,这次也用Docker,还算顺利,没 ...

  9. c++面向对象程序设计 谭浩强 第二章答案

    类体内定义成员函数 #include <iostream> using namespace std; class Time { public: void set_time(); void ...

  10. kubernetes系列:(二)、kubernetes部署mysql(单节点)

    使用kubeadm搭建好kubernetes集群后就可以动手部署自己的应用了. 本文用例来自官网,如有需要请参看 kubernetes官网 一.环境说明 kubernetes 1.13.1 docke ...