最近在看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. base64格式文件下载方法

    下载图片时,接口返回的地址是base64格式的文件数据,因为页面需要把base64格式的数据转换为文件,再进行下载: 解决方案: 下载按钮: <el-button type="defa ...

  2. 做支付遇到的HttpClient大坑

    前言 HTTPClient大家应该都很熟悉,一个很好的抓网页,刷投票或者刷浏览量的工具.但是还有一项非常重要的功能就是外部接口调用,比如说发起微信支付,支付宝退款接口调用等:最近我们在这个工具上栽了一 ...

  3. Spring学习总结(19)——Spring概念详解

    Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson创建.简单来说,Spring是一个分层的JavaSE/EEfull-stack(一 ...

  4. 如何在 VMware 上安装 CentOS 6.8

    一.下载 CentOS 6.8  64位 链接:https://pan.baidu.com/s/15qmWGar2m0WzsWxDk4tSSg 密码:wqra 二.安装步骤 1.新建虚拟机 2.自定义 ...

  5. 洛谷 P3014 [USACO11FEB]牛线Cow Line

    P3014 [USACO11FEB]牛线Cow Line 题目背景 征求翻译.如果你能提供翻译或者题意简述,请直接发讨论,感谢你的贡献. 题目描述 The N (1 <= N <= 20) ...

  6. [using_microsoft_infopath_2010]Chapter 11 创建审批流程

    本章概要: 1.为审批者创建一个简单的界面 2.设置表单加载规则切换视图 3.创建多审批小结表单 4.为已经完成的表单创建只读视图 5.创建工作流为每个审批阶段发送EMAIL

  7. oracle实现查询每个部门的员工工资排在前三的员工的基本信息具体举例

    --先删除原先存在的表: drop table emp; --创建表emp create table emp ( deptno number, ename varchar2(20), sal numb ...

  8. spring batch(二):核心部分(1):配置Spring batch

    spring batch(二):核心部分(1):配置Spring batch 博客分类: Spring 经验 java   chapter 3.Batch configuration 1.spring ...

  9. [C#] 怎样分析stackoverflow等clr错误

    有时候由于无限递归调用等代码错误,w3wp.exe会报错退出.原因是clr.exe出错了. 这样的错误比較难分析,由于C#代码抓不住StackOverflowException等异常. 处理方法是:生 ...

  10. gwt学习资料

    学习资料: 2 3