最近在看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. Linux设备驱动--块设备(三)之程序设计(转)

    http://blog.csdn.net/jianchi88/article/details/7212701 块设备驱动注册与注销 块设备驱动中的第1个工作通常是注册它们自己到内核,完成这个任务的函数 ...

  2. 2019-03-25 SQL SET ANSI_NULLS /SET QUOTED_IDENTIFIER /SET NOCOUNT ON

    use database go /**当 SET ANSI_NULLS 为 ON 时,即使 column_name 中包含空值,使用 WHERE column_name = NULL 的 SELECT ...

  3. nyoj 20水

    #include<stdio.h> #include<string.h> #define N 110000 struct node { int u,v,next; }bian[ ...

  4. Hadoop Word Count程序

    Hadoop Word Count程序 pom.xml文件: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns ...

  5. MySQL 5.7.10最新版本号源码安装具体过程

    ,重置密码 利用mysqladmin重置密码 [root@wgq_idc_mon_1_12 mysql5710]#./bin/mysqladmin -h localhost -uroot passwo ...

  6. hdoj 5092 Seam Carving 【树塔DP变形 + 路径输出】 【简单题】

    Seam Carving Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Tot ...

  7. Android调用第三方App

    private List<Map<String, Object>> list = null; private PackageManager mPackageManager; p ...

  8. SGU 531 - Bonnie and Clyde 预处理+二分

    Bonnie and Clyde Description Bonnie and Clyde are into robbing banks. This time their target is a to ...

  9. bzoj1503: [NOI2004]郁闷的出纳员(伸展树)

    1503: [NOI2004]郁闷的出纳员 题目:传送门 题解: 修改操作一共不超过100 直接暴力在伸展树上修改 代码: #include<cstdio> #include<cst ...

  10. ThinkPHP是什么

    ThinkPHP是什么 ThinkPHP是为了简化企业级应用开发和敏捷WEB应用开发而诞生的.最早诞生于2006年初,2007年元旦正式更名为ThinkPHP,并且遵循Apache2开源协议发布.Th ...