这两天偶遇无线驱动中对链表节点删除的问题,刚开始修改代码的时候并没有很在意,把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. PI膜热作用机理

    一.热分析法: 二.研究成果 1.PI膜热老化机理 实验条件:8根500w的碘钨灯加热,200倍光学显微镜观察,PI膜的技术指标 实验概述:本研究分别以150 ℃ ,  175 ℃ , 200 ℃ , ...

  2. 第五周课程总结&试验报告(三)

    第五周课程总结&试验报告(三) 实验三 String类的应用 实验目的 掌握类String类的使用: 学会使用JDK帮助文档: 实验内容 ###1.已知字符串:"this is a ...

  3. ubuntu 16.04主题美化

    目录 numix图标 Flatabulous主题 参考: Unity-tweak-tool插件 numix图标 sudo apt-add-repository ppa:numix/ppa sudo a ...

  4. 关于tk.mybatis.spring.mapper.SpringBootBindUtil$SpringBoot2Bind.bind(SpringBootBindUtil.java:129) ~[mapper-spring-boot-autoconfigure-1.2.3.jar:na]的问题

    错误如下: java.lang.RuntimeException: java.lang.reflect.InvocationTargetException at tk.mybatis.spring.m ...

  5. LEN()和DATALENGTH()的区别

    原文:LEN()和DATALENGTH()的区别 版权声明:本文为博主原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.n ...

  6. 项目四:Java秒杀系统方案优化-高性能高并发实战

    技术栈 前端:Thymeleaf.Bootstrap.JQuery 后端:SpringBoot.JSR303.MyBatis 中间件:RabbitMQ.Redis.Druid 功能模块 分布式会话,商 ...

  7. 【记录】看见的一些很好的博客x存一下

    [字符串] AC自动机:https://www.cnblogs.com/cjyyb/p/7196308.html

  8. spark内核篇-task数与并行度

    每一个 spark job 根据 shuffle 划分 stage,每个 stage 形成一个或者多个 taskSet,了解了每个 stage 需要运行多少个 task,有助于我们优化 spark 运 ...

  9. nginx + uwsgi 部署django项目

    因项目需求,需要部署django项目,这里是基础的nginx配合uwsgi部署django,后续会采用docker部署的方式 环境: centos7 python3.5.4 django2.1.4 u ...

  10. tensorflow零起点快速入门(1)

    导入: 其中的TF_CPP_MIN_LOG_LEVEL据说是为了忽略警告,但是我这里没有意义(numpy的一些警告) import tensorflow as tf import os os.envi ...