C语言的通用链表

在操作系统编程中, 往往是使用C语言, 但C使用起来极为痛苦, 不像C++有方便的STL模板库使用。

linux内核中,有一套非常神奇的通用链表结构,能够方便的使用,管理各种类型的数据,我们今天就来研究一下,内核中的C数据结构。

本文参考:【深入分析 Linux 内核链表】

首先,我们的目标是构建一个循环双链表结构,为何是双链表,还要循环,当然是从易用性考虑了,双链表能够方便的得知自己的上一个元素,在内核中管理数据更为方便。

其一般结构大概是这样:

实现机制

首先定义一个list_node结构,用来保存链表节点信息:

    /**
     * @brief 链表节点结构
     */
    typedef struct _list_node
    {
        struct _list_node   *next;
        struct _list_node   *prev;
    } list_node;

然后用一个存放数据的结构,比如我们要用int型的链表,如下定义:

    /**
     * @brief 数据存放结构
     */
    typedef struct _Int_List
    {
        list_node   node;
        int         data;
    } Int_List;

这和我们传统的链表实现思路好像不大一样,我们经典的C语言链表当然是在链表中保存数据:

    /**
     * @brief 链表节点结构
     */
    typedef ElementType     int;
    typedef struct _list_node
    {
        struct _list_node   *next;
        struct _list_node   *prev;
        ElementType         data;
    } list_node;

但这样的实现必然会造成链表结构被反复定义,难以实现泛型。另外一种思路可能是将ElementType实现成void*指针,然后在使用时进行强制类型转换,但这样必然会带来反复的类型转换,而且其使用相对不便。

这时要出问题了,如果数据在外层,链表在里层,那么如何从链表找到数据呢?

offsetof宏和container_of宏

为了实现从内层元素找外层元素的功能,我们需要用到这两个系统宏,一眼看上去很复杂,但实际上并不难理解。

/**
 * @brief 确定当前成员变量,在结构体中偏移量的宏
 */
#ifndef offsetof
#define offsetof(type, member) ((size_t) &((type*)0)->member)
#endif
/**
 * @brief 根据成员变量,找包含他的结构体指针
 */
#ifndef container_of
#define container_of(ptr, type, member) ({  \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);     (type *)( (char *)__mptr - offsetof(type,member) );})
#endif

这两个宏较为复杂,首先是offsetof宏,这个宏用0号指针去寻找成员变量,我们试想,如何是普通的一个结构体,C编译器如何查找其一个成员呢?

例如:

    /**
     * @brief 数据存放结构
     */
    typedef struct _Int_List
    {
        list_node   node;
        int         data;
    } Int_List;

假设我们用Int_List A语句,实例化一个结构体,然后取到了A的地址0x00001000

那么node的起始地址是不是也是0x00001000

data的起始地址是不是0x00001000+data的偏移量?

那么如何我想求data的偏移量,不就是如下的代码么:

    (&A)->data - (&A);

如果A的地址为0时,那么也就不用减了,就是我们宏定义中的用法。

而container_of宏也是采用类似的思路,既然找到了当前list_node成员的偏移量,那么减去偏移量,便是外层包围着的结构体的起始地址。

那好,这样我们就能实现这个链表了,但其实也不用这样费事,还有简单的办法,那就是让我们的被包含的list_node成员结构,处于我们数据存储结构的第一个位置,那么其地址就是包围其结构的地址,直接类型转换即可。

这种思路实际上也被用于C语言的继承机制的实现。

Github项目

到此,我们已经讲解了通用链表的实现思路,大家可以尝试自己实现一个独特的通用链表,具体内容就不一一赘述,我将完整的工程已经发布到了Github上面,欢迎大家前来fork测试。

【Clist项目地址】

本人才疏学浅,如有疏漏,还望指正。

时间: 2025-01-10 23:35:24

C语言的通用链表的相关文章

通用链表的实现

一.一般链表的局限性. 在我们学习数据结构时,链表的操作大同小异,虽然数据结构使用抽象数据类型描述算法,但是实现方法的本身特点就造成了链表的基本操作和用户自定义数据类型(ElemType)产生了高度的耦合,数据类型和链表的操作这种"绑定",降级了代码的重用性,每次将链表应用到新的场合时,都要修改源代码来保证链表与新的数据类型"绑定",大量的重复操作,难免会出现各种错误.我们希望有一种具有通用型性的链表,将数据类型与链表操作分离开,这就需要所谓的通用链表(姑且这样命名

使用C语言描述静态链表和动态链表

静态链表和动态链表是线性表链式存储结构的两种不同的表示方式. 静态链表的初始长度一般是固定的,在做插入和删除操作时不需要移动元素,仅需修改指针,故仍具有链式存储结构的主要优点. 动态链表是相对于静态链表而言的,一般地,在描述线性表的链式存储结构时如果没有特别说明即默认描述的是动态链表. 下面给出它们的简单实现,关于线性表更为详尽的C语言的实现,可以参考 http://www.cnblogs.com/choon/p/3876606.html 静态链表 #define _CRT_SECURE_NO_

数据结构C语言实现——线性链表

declaration.h #ifndef DECLARATION_H_INCLUDED #define DECLARATION_H_INCLUDED #define TRUE 1 #define FALSE 0 #define OK 1 #define ERROR 0 #define INFEASIBLE -1 #define OVERFLOW -2 #define ElemType int typedef ElemType* Triplet; typedef int Status; type

C语言实现单链表节点的删除(带头结点)

我在之前一篇博客<C语言实现单链表节点的删除(不带头结点)>中具体实现了怎样在一个不带头结点的单链表的删除一个节点,在这一篇博客中我改成了带头结点的单链表.代码演示样例上传至 https://github.com/chenyufeng1991/DeleteLinkedList_HeadNode.删除类型有两种: (1)删除某个位置pos的节点: (2)推断x值是否在链表中,若存在则删除该节点: 核心代码例如以下: //删除某个位置pos的节点 Node *DeletePosNode(Node

C 封装一个通用链表 和 一个简单字符串开发库

引言 这里需要分享的是一个 简单字符串库和 链表的基库,代码也许用到特定技巧.有时候回想一下, 如果我读书的时候有人告诉我这些关于C开发的积淀, 那么会走的多直啊.刚参加工作的时候做桌面开发, 服务是C++写,界面是C#写.那时候刚进去评级我是中级,因为他问我关于系统锁和信号量都答出来.开发一段 时间,写C#也写的很溜.后面招我那个人让我转行就写C++和php,那时候就开始学习C++有关知识. 后面去四川工作了,开发安卓,用eclipse + java语法 + android jdk,开发前端,

C语言实现单链表的节点插入(带头结点)

我在之前一篇博客<C语言实现单链表(不带头结点)节点的插入>中具体实现了怎样在一个不带头结点的单链表中进行节点的插入.可是在实际应用中,带头结点的链表更为经常使用.更为方便.今天我们就要来使用带头结点的单链表进行节点的插入.演示样例代码上传至 https://github.com/chenyufeng1991/InsertList_HeadNode  . 核心代码例如以下: Node *InsertNode(Node *pNode,int pos,int x){ int i = 0; Node

C语言实现单链表(带头结点)的基本操作

我在之前一篇博客<C语言实现单链表(不带头结点)的基本操作>中具体实现了不带头结点的单链表的11种操作:如计算链表长度.初始化.创建链表.清空链表等等.但是在实际使用中,带头结点的单链表往往比不带头结点的单链表用的更多,使用也更为方便.因为不用单独考虑第一个节点的情况了,第一个节点和其他后续节点的处理全都一样了,简化操作.这篇博客将会来实现带头结点的单链表的11种操作.代码上传至: https://github.com/chenyufeng1991/LinkedList_HeadNode  .

C语言实现单链表-03版

在C语言实现单链表-02版中我们只是简单的更新一下链表的组织方式: 它没有更多的更新功能,因此我们这个版本将要完成如下功能: Problem 1,搜索相关节点: 2,前插节点: 3,后追加节点: 4,一个专门遍历数据的功能: Solution 我们的数据结构体定义如下,和上一个版本稍微有所不同: 例如,我们把人这个结构体添加一个name域,前几个版本没有名字的节点: 我们叫起来很尴尬,都是第几个第几个节点,好啦!现在有名字啦! #include <stdio.h> #include <s

C语言实现单链表-02版

我们在C语言实现单链表-01版中实现的链表非常简单: 但是它对于理解单链表是非常有帮助的,至少我就是这样认为的: 简单的不能再简单的东西没那么实用,所以我们接下来要大规模的修改啦: Problem 1,要是数据很多怎么办,100000个节点,这个main函数得写多长啊... 2,这个连接的方式也太土啦吧!万一某个节点忘记连接下一个怎么办... 3,要是我想知道这个节点到底有多长,难道每次都要从头到尾数一遍嘛... 4,要是我想在尾部添加一个节点,是不是爬也要爬到尾部去啊... 这个是简单版中提出