[原创]Linux内核中定义的双向循环链表在C++中的使用

    Linux内核中定义的双向循环链表在C++中的使用

作者:默难 ( monnand at gmail dot com )

0    引言

文章的题目可能会令人困惑, 在此做个简单的解释:
Linux内核中定义了一种非常好用的双向循环链表 ( 具体代码参见 $KERNEL/include/linux/list.h ), 以及相关的操作函数和宏. 这些是用C语言编写, 但是最近的一个C++的项目需要用到双向循环链表, 因此我对内核的代码进行了小的调整, 并编写了一些比较方便的宏. 至于为何不使用STL中的链表, 我是出于以下几个考虑: 1. 据说 ( 完全是道听途说, 我没有亲自测试过 ) STL中的链表不是非常稳定, 当向链表中插入大量元素后, 或进行大规模操作时可能会倒是进程崩溃. 虽然没有亲自证实过这种说法, 但是本着安全起见的精神, 我还是决定暂时不用STL的链表. 2. Linux内核中定义的链表结构操作简单, 高效, 效率上应该不会低于STL的代码.

1    一般做法

Linux内核中定义的双向循环链表结构如下:
struct list_head {
    struct list_head *next, *prev;
};
相关的描述和操作参看[1].

如果C++中使用, 那么可以定义如下的类:
class list_node
{
public:
    struct list_head list;
    list_node() { ... }
    virtual ~list_node() { ... }
    int foo();
    int bar();
private:
    ...
};
之后, 利用相关函数进行操作. 附录中包含一个精简后的内核双向循环链表的代码. 下文将使用这个精简过的双向循环链表(dlist_head_t).

但是这样的代码存在几个问题:
1 需要有一个public的成员, 用以存储双向循环链表的指针域. 而这就带来了一些访问权限上的问题, 既然使用了OOL, 就尽量要控制访问权限. 把一个成员声明为public之前最好考虑清楚.
2 如果想要建立一个list_node的链表, 则需要生命一个链表头, 而按照一般做法, 链表头的类型则要声明为struct list_head. 其中并包含链表中成员类型的信息---其实, 以struct list_head作为头的链表中, 可以存储不同类型的成员. 而一般情况下, 一个链表内存储同一类成员. 这放在C里面也许不算什么 ( 如果写C代码, 也许反倒是会反对这些限制 ), 但是放在C++里面, 对类型的要求则要严格些了.
3 内核代码中的那些函数和宏操作, 与C++的风格不太相同, 如果能使用面向对象的方式去对链表进行管理, 则会让很多C++程序员感到很习惯.
虽然都不是什么大问题, 但是如果存在更好的解决方案, 为什么不去尝试一下呢?

2    一种更好的解决方案

虽然宏操作在C++中是不被提倡的, 但是如果能把宏使用好, 也同样可以写出漂亮的代码. qmail的代码中就使用了大量的宏来充当函数模板的功能. 当然, 这也意味着调试和测试成本的增加. 至于说我为什么不使用C++的模板, 我想一来, 我写了很久的C程序, 对C语言的会多少有些舍不得. 二来, 我希望构建一个轻巧灵便的链表, 而使用模板的话则需要很大程度上依赖编译器的优化能力.

代码会在下文中给出. 首先对代码做个笼统的介绍.

整个代码包含两个文件, 分别为: dlist.h dlist_util.h
dlist.h是一个精简过的Linux内核双向循环链表的代码. 之所以叫dlist而非list, 主要是觉得list这个词太多人会用. 而dlist则表示doubly linked list.
dlist_util.h中定义了两个宏. 分别是DLIST_DECLARE和dlist_for_each_entry. 具体用法参见下面的示例代码.

示例代码如下:

#include "dlist_util.h"
#include "dlist.h"
#include <iostream>

class test_node
{
    DLIST_DECLARE(test_node);
public:
    test_node() { member = 0; }
    virtual ~test_node() { del(); }
    void set(int i) { member = i; }
    int get() { return member; }
private:
    int member;
};

int main()
{
    test_node::head head;
    test_node node[5];
    test_node *ptr;
    for(int i=0;i<5;i++) {
        node[i].set(i);
        head.add(node[i]);
    }
    dlist_for_each_entry(ptr,&head) {
        std::cout << ptr->get() << std::endl;
    }
}

将示例代码与dlist.h dlist_util.h放在同一个目录下编译, 执行结果如下:

$ g++ 1.cpp
1.cpp: In member function ‘test_node* test_node::next(test_node::head&)’:
1.cpp:7: warning: invalid access to non-static data member ‘test_node::__type_list’ of NULL object
1.cpp:7: warning: (perhaps the ‘offsetof’ macro was used incorrectly)
1.cpp: In member function ‘test_node* test_node::prev(test_node::head&)’:
1.cpp:7: warning: invalid access to non-static data member ‘test_node::__type_list’ of NULL object
1.cpp:7: warning: (perhaps the ‘offsetof’ macro was used incorrectly)
1.cpp: In member function ‘test_node* test_node::head::next(test_node::head&)’:
1.cpp:7: warning: invalid access to non-static data member ‘test_node::__type_list’ of NULL object
1.cpp:7: warning: (perhaps the ‘offsetof’ macro was used incorrectly)
1.cpp: In member function ‘test_node* test_node::head::prev(test_node::head&)’:
1.cpp:7: warning: invalid access to non-static data member ‘test_node::__type_list’ of NULL object
1.cpp:7: warning: (perhaps the ‘offsetof’ macro was used incorrectly)
$ ./a.out
4
3
2
1
0
编译时会出现一些警告. 因为使用了0地址来计算偏移.

3    附录1: dlist.h

以下是dlist.h的代码:

#ifndef __VOILA_DOUBLY_LINKED_LIST_H
#define __VOILA_DOUBLY_LINKED_LIST_H

#include <stdio.h>

/**
 * @file dlist.h
 * @brif Simple doubly linked list implementation
 * @author monnand ( monnand at gmail dot com )
 *
 * Nearly an exactly copy from Linux kernel.
 * Read Linux kernel's code: $KERNEL/include/linux/list.h for details
 */

typedef struct dlist_head {
    struct dlist_head *next, *prev;
}dlist_head_t;

/**
 * Initialise a list head
 */
static inline void dlist_head_init(dlist_head_t *list)
{
    list->next = list;
    list->prev = list;
}

/*
 * Insert a new entry between two know consecutive entries.
 */
static inline void __dlist_add(dlist_head_t *item,
            dlist_head_t *prev,
            dlist_head_t *next)
{
    next->prev = item;
    item->next = next;
    item->prev = prev;
    prev->next = item;
}

/**
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 * @param item entry to be added
 * @param head list head to add it after
 */
static inline void dlist_add(dlist_head_t *item,dlist_head_t *head)
{
    __dlist_add(item,head,head->next);
}

/**
 * Insert a new entry before the specified head.
 * This is useful for implementing queues.
 * @param item new entry to be added
 * @param head list head to add it before
 */
static inline void dlist_add_tail(dlist_head_t *item,dlist_head_t *head)
{
    __dlist_add(item,head->prev,head);
}

/**
 * Delete a list entry by making the prev/next entries
 * point to each other.
 */
static inline void __dlist_del(dlist_head_t *prev,dlist_head_t *next)
{
    next->prev = prev;
    prev->next = next;
}

/**
 * Deletes entry from list.
 * @param entry the element to delete from the list.
 */
static inline void dlist_del(dlist_head_t *entry)
{
    __dlist_del(entry->prev,entry->next);
//    entry->next = entry->prev = NULL;
}

static inline int dlist_is_empty(dlist_head_t *head)
{
    return head->next == head;
}

#ifndef dlist_entry
#define    dlist_entry(ptr,type,member)    /
    ((type *)((char *)(ptr) - (char *)(&((type *)0)->member)))
#endif /* dlist_entry */

#ifndef dlist_for_each
#define    dlist_for_each(pos,head)    /
    for((pos)=((head)->next);(pos)!=(head);(pos)=((pos)->next))
#endif /* dlist_for_each */

#endif /* __VOILA_DOUBLY_LINKED_LIST_H */

4    附录2: dlist_util.h


以下是dlist_util.h的代码:

#ifndef    __VOILA_DLIST_UTIL_H
#define __VOILA_DLIST_UTIL_H

/**
 * @file dlist_util.h
 * @brif Some utilities used in class of C++ for dlist
 * @author monnand ( monnand at gmail dot com )
 *
 * @see dlist.h
 * Here is some useful macros used to declare a doubly linked list
 */

#include "dlist.h"
#include <stdio.h>

#ifndef DLIST_DECLARE
#define    DLIST_DECLARE(type)    /
public:    /
    class head; /
    inline void add(type &entry) {    /
        dlist_add(&(entry.__type##_list),&__type##_list);    /
    }    /
    inline void add_tail(type &entry) {    /
        dlist_add_tail(&(entry.__type##_list),&__type##_list);    /
    }    /
    inline void del() {    /
        dlist_del(&__type##_list);    /
    }    /
    inline type *next(head &h) {    /
        if(__type##_list.next == &(h.__type##_list)) return NULL;    /
        return dlist_entry(__type##_list.next,type,__type##_list);    /
    }    /
    inline type *prev(head &h) {    /
        if(__type##_list.prev== &(h.__type##_list)) return NULL;    /
        return dlist_entry(__type##_list.prev,type,__type##_list);    /
    }    /
    class head {    /
    public:    /
        inline head() { dlist_head_init(&__type##_list); }    /
        inline void add(type &entry) {    /
            dlist_add(&(entry.__type##_list),&__type##_list);    /
        }    /
        inline void add_tail(type &entry) {    /
            dlist_add_tail(&(entry.__type##_list),&__type##_list);    /
        }    /
        inline type *next(head &h) {    /
            if(__type##_list.next == __type##_list.prev) return NULL;    /
            return dlist_entry(__type##_list.next,type,__type##_list); /
        }    /
        inline type *prev(head &h) {    /
            if(__type##_list.next == __type##_list.prev) return NULL;    /
            return dlist_entry(__type##_list.prev,type,__type##_list); /
        }    /
        inline bool is_empty() {    /
            return dlist_is_empty(&__type##_list);    /
        }    /
        friend type *type::next(head &h);    /
        friend type *type::prev(head &h);    /
    private:    /
        dlist_head_t __type##_list;    /
    };    /
private:    /
    dlist_head_t __type##_list
#endif /* DLIST_DECLARE */

#ifndef dlist_for_each_entry
#define    dlist_for_each_entry(pos,head)    /
    for((pos)=(head)->next(*head); NULL!=(pos);(pos)=(pos)->next(*head))
#endif /* dlist_for_each_entry */

#endif /* __VOILA_DLIST_UTIL_H */

5    后记

本文没有对代码做过多的解释. 因为我觉得这些代码没有什么难懂的地方. 总之, 灵巧的代码未必晦涩难懂, 但是绝对是简单好用的.

参考文献

[1] 杨沙洲, 深入分析 Linux 内核链表, 2004 年 8 月.
http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/index.html
 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值