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
作者:默难 ( 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