关于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 掉节点信息;
}
}
对比一下两段代码,会发现第二段确实比较好一些。
关于TAILQ链表节点删除问题的更多相关文章
- 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 ...
- linux内核之链表操作解析
本文只是对linux内核中的链表进行分析.内核版本是linux-2.6.32.63.文件在:linux内核/linux-2.6.32.63/include/linux/list.h.本文对list.h ...
- [LeetCode 总结帖]: 链表专题
链表在笔试面试中都是出镜率极高的一种数据结构. 由于链表具有结构简单,代码量较少,变化多,可以较为全面的考察应聘者的逻辑思考能力以及应变能力的特点,而备受面试官青睐. 在本节中,我将Leetcode中 ...
- Python—数据结构——链表
数据结构——链表 一.简介 链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构.由一系列节点组成的元素集合.每个节点包含两部分,数据域item和指向下一个节 ...
- JDK1.8 HashMap 源码分析
一.概述 以键值对的形式存储,是基于Map接口的实现,可以接收null的键值,不保证有序(比如插入顺序),存储着Entry(hash, key, value, next)对象. 二.示例 public ...
- C/C++面试题总结
腾讯阿里面试题总结:1. 多态机制2. 排序算法(快排.堆排)3. 程序内存分配4. unix多线程5. 哈希查找6. oop特点7. 素数(优化)8. 字符串掩膜操作(内存紧凑)9. 多边形相交10 ...
- C语言实现penna模型
一年前写的代码,偶然翻出来.发现自己当时水平还不赖吗. # include <stdio.h> # include <stdlib.h> # include <time. ...
- JAVA 链表操作:循环链表
主要分析示例: 一.循环链表简述 二.单链表循环链表 三.双链表循环链表 一.循环链表简述 循环链表即链表形成了一个循环的结构,尾节点不再指向NULL,而是指向头节点HEAD,此时判定链表的结束是尾节 ...
- C语言 Linux内核链表(企业级链表)
//Linux内核链表(企业级链表) #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> ...
随机推荐
- Linux下部署FastDFS
FastDFS的安装 首先需要准备的资源如下: libfastcommon-master:https://github.com/happyfish100/libfastcommon FastD ...
- P1494 小Z的袜子 【普通莫队】
我的第二道莫队题,对莫队又有了自己的看法. 在第一题的基础上之上,觉得莫队有个很关键的地方在于 莫队所维护的值是什么,怎么推出维护的公式来. 这道题就是这样,一开始还没自己推出公式来,也有几个坑点. ...
- Linux下测试ZLAN 5800
今天师兄让帮忙测试ZLAN 5800八串口通信模块,windows下的测试按照手册来已经搞定,接下来是Linux下的测试. 因为厂家不提供Linux下的相关资料,所以需要在windows下设置好后直接 ...
- poj3122 Pie (二分)
题目链接:https://vjudge.net/problem/POJ-3122 题意:有n块饼,m+1个人,将饼均分成m+1块,求每块最大的大小. 思路:水二分,显然每块的大小与可以给多少人吃具有单 ...
- 2019牛客暑期多校训练营(第七场)-E Find the median (线段树+离散化 区间为点)
题目链接:https://ac.nowcoder.com/acm/contest/887/E 题意:给出L[i],R[i],每次添加L[i]...R[i],求出此时的中位数. 思路:因为添加的数范围为 ...
- ActiveMQ 即时通讯服务 入門指南及淺析
转:http://www.cnblogs.com/hoojo/p/active_mq_jms_apache_activeMQ.html 一. 概述与介绍 ActiveMQ 是Apache出品,最流行的 ...
- SQL SERVER GETDATE() 函数
定义: GETDATE() 函数从 SQL Server 返回当前的时间和日期. 语法: GETDATE() 返回值: datetime型数据 例: 声明:本文是本人查阅网上及书籍等各种资料,再加上 ...
- mybatis-plus代码生成器两版(全部生成+部分生成)
mybatis-plus代码生成器两版(全部生成+部分生成) 一次性生成全部文件 package com.layuicms.erp.utils; import java.util.List; impo ...
- Linux系列:进阶之tomcat安装
思路:作者是在Windows上从Apache官网下载的tomcat,之后将tomcat文件放到我的ftp站点中,在Linux访问ftp站点下载tomcat文件 ,将tomcat放在我自己的安装目录中, ...
- java持续添加内容至本地文件
package com.lcc.commons; import com.lcc.commons.dto.FileLogDTO; import java.io.*; import java.util.A ...