设为首页 加入收藏

TOP

C语言的通用链表
2015-11-19 23:06:48 来源: 作者: 【 】 浏览:5
Tags:语言 通用

在操作系统编程中, 往往是使用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测试。
?

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇反汇编一个简单的C程序 下一篇C函数调用原理

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: