这两天偶遇无线驱动中对链表节点删除的问题,刚开始修改代码的时候并没有很在意,把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 掉节点信息;

}

}

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

关于TAILQ链表节点删除问题的更多相关文章

  1. LeetCode OJ:Delete Node in a Linked List(链表节点删除)

    Write a function to delete a node (except the tail) in a singly linked list, given only access to th ...

  2. linux内核之链表操作解析

    本文只是对linux内核中的链表进行分析.内核版本是linux-2.6.32.63.文件在:linux内核/linux-2.6.32.63/include/linux/list.h.本文对list.h ...

  3. [LeetCode 总结帖]: 链表专题

    链表在笔试面试中都是出镜率极高的一种数据结构. 由于链表具有结构简单,代码量较少,变化多,可以较为全面的考察应聘者的逻辑思考能力以及应变能力的特点,而备受面试官青睐. 在本节中,我将Leetcode中 ...

  4. Python—数据结构——链表

    数据结构——链表 一.简介 链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构.由一系列节点组成的元素集合.每个节点包含两部分,数据域item和指向下一个节 ...

  5. JDK1.8 HashMap 源码分析

    一.概述 以键值对的形式存储,是基于Map接口的实现,可以接收null的键值,不保证有序(比如插入顺序),存储着Entry(hash, key, value, next)对象. 二.示例 public ...

  6. C/C++面试题总结

    腾讯阿里面试题总结:1. 多态机制2. 排序算法(快排.堆排)3. 程序内存分配4. unix多线程5. 哈希查找6. oop特点7. 素数(优化)8. 字符串掩膜操作(内存紧凑)9. 多边形相交10 ...

  7. C语言实现penna模型

    一年前写的代码,偶然翻出来.发现自己当时水平还不赖吗. # include <stdio.h> # include <stdlib.h> # include <time. ...

  8. JAVA 链表操作:循环链表

    主要分析示例: 一.循环链表简述 二.单链表循环链表 三.双链表循环链表 一.循环链表简述 循环链表即链表形成了一个循环的结构,尾节点不再指向NULL,而是指向头节点HEAD,此时判定链表的结束是尾节 ...

  9. C语言 Linux内核链表(企业级链表)

    //Linux内核链表(企业级链表) #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> ...

随机推荐

  1. Metasploit-初篇

    Metasploit metasploit 是一款开源的安全漏洞检测工具 渗透测试的操作系统 无需赘言,kali Linux发行版是目前最流行的安全操作系统;基于Debian的操作系统附带了600多个 ...

  2. 17.Azkaban实战

    首先创建一个command.job文件 #command.job type=command command=echo it18zhang 然后打成zip压缩包 上传刚刚打包的zip包 上传完后可以执行 ...

  3. Spring MVCD框架中调用HanLP分词的方法

    项目简要:关于java web的一个项目,用的Spring MVCd 框架.鉴于参与此次项目的人中并不是所人都做的Spring,为了能够提高效率,建议大家是先抛开SPring来写自己负责的模块,最后再 ...

  4. 使用批处理执行 sql cmd

    当 sql脚本文件太大了, 就可以考虑用这个方式来执行 1.准备好要执行的 sql脚本文件并保存为*.sql格式 2.找到 SQLCMD.EXE 文件所在的路径(每个版本sql路径不一样) C:\Pr ...

  5. H2内嵌数据库的使用

    H2内嵌数据库的使用 H2是一个开源的嵌入式数据库引擎,采用java语言编写,不受平台的限制. 同时H2提供了一个十分方便的web控制台用于操作和管理数据库内容. H2还提供兼容模式,可以兼容一些主流 ...

  6. 【LOJ】#3042. 「ZJOI2019」麻将

    LOJ#3042. 「ZJOI2019」麻将 如何判定一个集合牌有没有胡的子集是不是胡的 就用一个\(dp[j][k][0/1]\)表示有j个连续两个的串,有k个连续1个串,有没有对子,再记一下这个集 ...

  7. PAT B1048 数字加密 (20)

    AC代码 #include <cstdio> #include <cstring> #include <algorithm> #include <iostre ...

  8. 二项式反演/minmax容斥初探

    世界是物质的,物质是运动的,运动是有规律的,规律是可以被认识的 二项式反演 \[ g_n=\sum_{i=0}^n \binom{n}if_i\Rightarrow f_n=\sum_{i=0}^n( ...

  9. 定义vue目录别名

  10. 修改mac系统的host文件 (一)

    hosts是本地预先配置的DNS数据,解析域名的时候首先试图从hosts文件获取,没有则从DNS服务器获取. 此文件的三个主要用途: 加快域名解析 方便局域网用户 一般局域网很少假设DNS服务器,访问 ...