关于TAILQ链表节点删除问题

这两天偶遇无线驱动中对链表节点删除的问题,刚开始修改代码的时候并没有很在意,把TAILQ链表当成一般的链表来处理,虽然修改以后没有出现段错误,但是后面review代码的时候发现,这样改不对。后面花了点时间好好看了一下TAILQ的相关代码。

首先看一下这个TAILQ链表的结构,TAILQ链表包括两个部分,一个叫HEAD的头部,另外一个就是ENTRY实体部分;下面是两部分的定义:

#define TAILQ_HEAD(name, type)                      \

struct name {                               \

    struct type *tqh_first; /* first element */         \

    struct type **tqh_last; /* addr of last next element */     \

    TRACEBUF                            \

}

#define TAILQ_ENTRY(type)                       \

struct {                                \

    struct type *tqe_next;  /* next element */          \

    struct type **tqe_prev; /* address of previous next element */  \

    TRACEBUF                            \

}

其实HEAD跟ENTRY的定义是相同的,只是后面用户在定义自己数据的时候会将ENTRY放到自己定义的数据的头部;举个例子:

struct ieee80211_probe_entry

{

    /*

     * list element for linking on probe_list

     */

    TAILQ_ENTRY(ieee80211_probe_entry)     ae_list;

    /*

     * list element for linking on probe_hash list

     */

    LIST_ENTRY(ieee80211_probe_entry)      ae_hash;

    u_int8_t                             ae_macaddr[IEEE80211_ADDR_LEN];

    int                                                   ae_5g; //mark 5g request

    int                                                   ae_2g;//mark 2.4g request

    os_timer_t                                            assoc_block_timer;

    int                                                   block_timer_init;

    int                                                   block;

    int                                                   refuse;

    //christian added

    int                                          rssi_sum[RECORD_RSSI_NUM];

    u32                                        rssi_cnt;

    systime_t                                                        last_update;

};

下面看一下这个TAILQ链表结构:

  简单画了三个节点,从上面可以看出,HEAD的tqh_first始终指向第一个元素,而tqh_last始终指向最后一个元素的tqe_next,而tqe_next的地址也是该节点的起始地址,所以tqh_last也可以看做指向最后一个元素的起始地址。这个地方有点纠结,因为struct type *tqh_first;tqh_first是一个一般的指针,而 struct type **tqh_last;tqh_last是一个二重指针,是一个指向指针的指针。当时在这也花了一点时间来想这个问题,可能是因为对二重指针理解不够深入。指针说白了就是一个地址,不管你是几重的,从面图我们就可以清晰的看到,如果将tqe_prev指针进行一个强制转换,其实我们就能够得到上一个元素的起始地址。也许这个就是queue的高明之处。这个是我遇到的一个觉得有点意思的地方,另外一个就是对链表节点删除的时候,也发现了一个比较有意思的问题。

删除节点的时候会用到一个循环,在queue.h里面有两个类似的循环。

       项目中用到的是TAILQ_FOREACH,用这个宏的时候,在删除的时候我需要去找到它的上一个节点的地址,并将其保存起来,这个找上一个节点的地址就用到前面说的,通过tqe_prev来找,通过这种方式查找上一个节点,找到的节点很有可能是头部。

#define    TAILQ_FOREACH(var, head, field)                    \

    for ((var) = TAILQ_FIRST((head));                \

        (var);                            \

        (var) = TAILQ_NEXT((var), field))

             第二种就是TAILQ_FOREACH_SAFE这个宏,用这个宏的好处就不需要去找将要被删除节点的上一个节点。这个宏的做法是在它第一次执行for里面语句的时候就取到了下一个节点,执行完完语句以后就只是简单的进行了一个赋值操作,这样就能够保证for循环能够遍历到真个链表,而且不会像上面那种情况会出现节点为头部的情况。这也是这种写法比较高明之处。

#define    TAILQ_FOREACH_SAFE(var, head, field, tvar)            \

 for ((var) = TAILQ_FIRST((head));                \

        (var) && ((tvar) = TAILQ_NEXT((var), field), 1);        \

        (var) = (tvar))

宏里面的这一句我觉得写的非常不错, (var) && ((tvar) = TAILQ_NEXT((var), field), 1);一般情况下我们只需要写一个var就可以了,但是在这个地方后面加上了((tvar) = TAILQ_NEXT((var), field), 1)这样一句,var为空的时候,for循环结束,var不为NULL的时候,那后面肯定必须为真了,而后面这一句也恰好为真,这一条语句做了两个事情,第一取到下一个节点,第二是整个表达式值为1,使得for循环得以继续。

如果不仔细去看这两个宏的区别,确实不能体会到TAILQ_FOREACH_SAFE,safe在哪里。;

首先我们来看看通过这两个循环来删除节点的代码格式:

TAILQ_FOREACH:

Struct type * temp_var;

TAILQ_FOREACH(var, head, field){

                    If(var ….)//满足我们的条件{

                            Temp_var =(struct type *) var->tqe_prev;//保存上一个节点的地址;

                            TAILQ _FREE(var);//free 掉节点信息;

                    }

                    Var = Temp_var;//从新赋值给var;

}

TAILQ_FOREACH_SAFE:

Struct type * temp_var;

TAILQ_FOREACH(var, head, field,temp_var){

                    If(var ….)//满足我们的条件{

                            TAILQ _FREE(var);//free 掉节点信息;

                    }

}

对比一下两段代码,会发现第二段确实比较好一些。

posted on 2014-07-25 13:43  JJ-GG  阅读(3453)  评论(0编辑  收藏  举报

导航