zoukankan      html  css  js  c++  java
  • 【转载】玩转C链表

    ZZ:http://www.cnblogs.com/wwang/archive/2010/11/28/1889281.html

    玩转C链表

    2010-11-28 20:50 by wwang, 4397 visits, 收藏, 编辑

    链表是C语言编程中常用的数据结构,比如我们要建一个整数链表,一般可能这么定义:

    1
    2
    3
    4
    struct int_node {
            int val;
            struct int_node *next;
    };

    为了实现链表的插入、删除、遍历等功能,另外要再实现一系列函数,比如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void insert_node(struct int_node **head, int val);
      
    void delete_node(struct int_node *head, struct int_node *current);
      
    void access_node(struct int_node *head)
    {
            struct int_node *node;
            for (node = head; node != NULL; node = node->next) {
                    // do something here
            }
    }

    如果我们的代码里只有这么一个数据结构的话,这样做当然没有问题,但是当代码的规模足够大,需要管理很多种链表,难道需要为每一种链表都要实现一套插入、删除、遍历等功能函数吗?

    熟悉C++的同学可能会说,我们可以用标准模板库啊,但是,我们这里谈的是C,在C语言里有没有比较好的方法呢?

    Mr.Dave在他的博客里介绍了自己的实现,这个实现是个很好的方案,各位不妨可以参考一下。在本文中,我们把目光投向当今开源界最大的C项目--Linux Kernel,看看Linux内核如何解决这个问题。

    Linux内核中一般使用双向链表,声明为struct list_head,这个结构体是在include/linux/types.h中定义的,链表的访问是以宏或者内联函数的形式在include/linux/list.h中定义。

    1
    2
    3
    struct list_head {
        struct list_head *next, *prev;
    };

    Linux内核为链表提供了一致的访问接口。

    1
    2
    3
    4
    5
    void INIT_LIST_HEAD(struct list_head *list);
    void list_add(struct list_head *new, struct list_head *head);
    void list_add_tail(struct list_head *new, struct list_head *head);
    void list_del(struct list_head *entry);
    int list_empty(const struct list_head *head);

    以上只是从Linux内核里摘选的几个常用接口,更多的定义请参考Linux内核源代码

    我们先通过一个简单的实作来对Linux内核如何处理链表建立一个感性的认识。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #include <stdio.h>
    #include "list.h"
      
    struct int_node {
            int val;
            struct list_head list;
    };
      
    int main()
    {
            struct list_head head, *plist;
            struct int_node a, b;
      
            a.val = 2;
            b.val = 3;
      
            INIT_LIST_HEAD(&head);
            list_add(&a.list, &head);
            list_add(&b.list, &head);
      
            list_for_each(plist, &head) {
                    struct int_node *node = list_entry(plist, struct int_node, list);
                    printf("val = %d\n", node->val);
            }
      
            return 0;
    }

    看完这个实作,是不是觉得在C代码里管理一个链表也很简单呢?

    代码中包含的头文件list.h是我从Linux内核里抽取出来并做了一点修改的链表处理代码,现附在这里给大家参考,使用的时候只要把这个头文件包含到自己的工程里即可。

    代码
    #ifndef __C_LIST_H
    #define __C_LIST_H

    typedef unsigned
    char u8;
    typedef unsigned
    short u16;
    typedef unsigned
    int u32;
    typedef unsigned
    long size_t;

    #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

    /**
    * container_of - cast a member of a structure out to the containing structure
    * @ptr: the pointer to the member.
    * @type: the type of the container struct this is embedded in.
    * @member: the name of the member within the struct.
    *
    */
    #define container_of(ptr, type, member) (type *)((char *)ptr -offsetof(type,member))

    /*
    * These are non-NULL pointers that will result in page faults
    * under normal circumstances, used to verify that nobody uses
    * non-initialized list entries.
    */
    #define LIST_POISON1 ((void *) 0x00100100)
    #define LIST_POISON2 ((void *) 0x00200200)

    struct list_head {
    struct list_head *next, *prev;
    };

    /**
    * list_entry - get the struct for this entry
    * @ptr: the &struct list_head pointer.
    * @type: the type of the struct this is embedded in.
    * @member: the name of the list_struct within the struct.
    */
    #define list_entry(ptr, type, member) \
    container_of(ptr, type, member)


    #define LIST_HEAD_INIT(name) { &(name), &(name) }

    #define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)

    static inline void INIT_LIST_HEAD(struct list_head *list)
    {
    list
    ->next = list;
    list
    ->prev = list;
    }

    /**
    * list_for_each - iterate over a list
    * @pos: the &struct list_head to use as a loop counter.
    * @head: the head for your list.
    */
    #define list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)

    /**
    * list_for_each_r - iterate over a list reversely
    * @pos: the &struct list_head to use as a loop counter.
    * @head: the head for your list.
    */
    #define list_for_each_r(pos, head) \
    for (pos = (head)->prev; pos != (head); pos = pos->prev)

    /*
    * Insert a new entry between two known consecutive entries.
    *
    * This is only for internal list manipulation where we know
    * the prev/next entries already!
    */
    static inline void __list_add(struct list_head *new,
    struct list_head *prev,
    struct list_head *next)
    {
    next
    ->prev = new;
    new->next = next;
    new->prev = prev;
    prev
    ->next = new;
    }

    /**
    * list_add - add a new entry
    * @new: new entry to be added
    * @head: list head to add it after
    *
    * Insert a new entry after the specified head.
    * This is good for implementing stacks.
    */
    static inline void list_add(struct list_head *new, struct list_head *head)
    {
    __list_add(
    new, head, head->next);
    }

    /**
    * list_add_tail - add a new entry
    * @new: new entry to be added
    * @head: list head to add it before
    *
    * Insert a new entry before the specified head.
    * This is useful for implementing queues.
    */
    static inline void list_add_tail(struct list_head *new, struct list_head *head)
    {
    __list_add(
    new, head->prev, head);
    }

    /*
    * Delete a list entry by making the prev/next entries
    * point to each other.
    *
    * This is only for internal list manipulation where we know
    * the prev/next entries already!
    */
    static inline void __list_del(struct list_head * prev, struct list_head * next)
    {
    next
    ->prev = prev;
    prev
    ->next = next;
    }

    /**
    * list_del - deletes entry from list.
    * @entry: the element to delete from the list.
    * Note: list_empty on entry does not return true after this, the entry is
    * in an undefined state.
    */
    static inline void list_del(struct list_head *entry)
    {
    __list_del(entry
    ->prev, entry->next);
    entry
    ->next = LIST_POISON1;
    entry
    ->prev = LIST_POISON2;
    }

    /**
    * list_empty - tests whether a list is empty
    * @head: the list to test.
    */
    static inline int list_empty(const struct list_head *head)
    {
    return head->next == head;
    }

    static inline void __list_splice(struct list_head *list,
    struct list_head *head)
    {
    struct list_head *first = list->next;
    struct list_head *last = list->prev;
    struct list_head *at = head->next;

    first
    ->prev = head;
    head
    ->next = first;

    last
    ->next = at;
    at
    ->prev = last;
    }

    /**
    * list_splice - join two lists
    * @list: the new list to add.
    * @head: the place to add it in the first list.
    */
    static inline void list_splice(struct list_head *list, struct list_head *head)
    {
    if (!list_empty(list))
    __list_splice(list, head);
    }

    #endif // __C_LIST_H

    list_head通常是嵌在数据结构内使用,在上文的实作中我们还是以整数链表为例,int_node的定义如下:

    1
    2
    3
    4
    struct int_node {
            int val;
            struct list_head list;
    };

    使用list_head组织的链表的结构如下图所示:

    遍历链表是用宏list_for_each来完成。

    1
    2
    3
    #define list_for_each(pos, head) \
        for (pos = (head)->next; prefetch(pos->next), pos != (head); \
                pos = pos->next)

    在这里,pos和head均是struct list_head。在遍历的过程中如果需要访问节点,可以用list_entry来取得这个节点的基址。

    1
    2
    #define list_entry(ptr, type, member) \
        container_of(ptr, type, member)

    我们来看看container_of是如何实现的。如下图所示,我们已经知道TYPE结构中MEMBER的地址,如果要得到这个结构体的地址,只需要知道MEMBER在结构体中的偏移量就可以了。如何得到这个偏移量地址呢?这里用到C语言的一个小技巧,我们不妨把结构体投影到地址为0的地方,那么成员的绝对地址就是偏移量。得到偏移量之后,再根据ptr指针指向的地址,就可以很容易的计算出结构体的地址。

    list_entry就是通过上面的方法从ptr指针得到我们需要的type结构体。

    Linux内核代码博大精深,陈莉君老师曾把它形容为“覆压三百余里,隔离天日”(摘自《阿房宫赋》),可见其内容之丰富、结构之庞杂。内核里有着众多重要的数据结构,具有相关性的数据结构之间很多都是用本文介绍的链表组织在一起,看来list_head结构虽小,作用可真不小。

    Linux内核是个伟大的工程,其源代码里还有很多精妙之处,值得C/C++程序员认真去阅读,即使我们不去做内核相关的工作,阅读精彩的代码对程序员自我修养的提高也是大有裨益的。

  • 相关阅读:
    ArrayList用法
    MessageBox
    将文本文件导入Sql数据库
    在桌面和菜单中添加快捷方式
    泡沫排序
    Making use of localized variables in javascript.
    Remove double empty lines in Visual Studio 2012
    Using Operations Manager Connectors
    Clear SharePoint Designer cache
    Programmatically set navigation settings in SharePoint 2013
  • 原文地址:https://www.cnblogs.com/hengfeng/p/2311860.html
Copyright © 2011-2022 走看看