关于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链表节点删除问题,布布扣,bubuko.com

时间: 2024-11-05 03:34:42

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

课堂练习之链表节点删除与构建堆

课堂练习之链表节点删除 Node x = new Node<Integer>(); Node y = new Node<integer>(); x.data = 6; x.next = y; y.data = 8; // 在此处添加7节点 Node z = new Node<Integer>(7,y); x.next = z; // 删除7节点 Node curr = x; Node prev = x; while(curr.data==7&curr.next!

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 that node. Supposed the linked list is 1 -> 2 -> 3 -> 4 and you are given the third node with value 3, the linked list should become 1 -> 2 ->

单链表二[不带头节点链表]

不带头节点链表 单向链表是链表的一种.单向链表由一系列内存不连续的节点组成,每个节点都包含指向值的域和指向下个节点的next指针.最后一个节点的next域为NULL值,代表链表结束. 链表示意图如下: 一,结构体 1,结构体定义: struct LinkNode {     void *x;      struct LinkNode *next; }; 2,结构体大小: 1)取结构体大小方法:sizeof(struct LinkNode);在64位系统取出结构体大小为:sizeof(struct

C语言实现penna模型

一年前写的代码,偶然翻出来.发现自己当时水平还不赖吗. 1 # include <stdio.h> 2 # include <stdlib.h> 3 # include <time.h> 4 # include <stdbool.h> 5 # include <windows.h> 6 7 # define N0 1000 //初始时刻种群数量为1000 8 # define Nmax 100000 //种群最大数量为100000 9 # def

数据结构与算法 1 :基本概念,线性表顺序结构,线性表链式结构,单向循环链表

[本文谢绝转载] <大纲> 数据结构: 起源: 基本概念 数据结构指数据对象中数据元素之间的关系  逻辑结构 物理结构 数据的运算 算法概念: 概念 算法和数据结构区别 算法特性 算法效率的度量 大O表示法 时间复杂度案例 空间复杂度 时间换空间案例 1)线性表: 线性表初步认识: 线性表顺序结构案例 线性表顺序结构案例,单文件版 线性表的优缺点 企业级线性表链式存储案例:C语言实现 企业级线性表链式存储案例:C语言实现 单文件版 企业级线性表链式存储案例,我的练习  线性表链式存储优点缺点

将一个链表中倒数第n个节点删除

Given a linked list, remove the nth node from the end of list and return its head. For example, Given linked list: 1->2->3->4->5, and n = 2. After removing the second node from the end, the linked list becomes 1->2->3->5. Note:Given n

剑指Offer之在O(1)时间删除链表节点

题目描述 给定单向链表的头指针和一个节点指针,定义一个函数在O(1)时间删除该节点. 解题思路 在单向链表中删除一个节点,最常规的做法无疑是从链表的头结点开始,顺序的遍历查找要删除的节点,并在链表中删除该节点.这种思路由于需要顺序查找,时间复杂度自然就是$O(n)$了. 之所以需要从头开始查找,是因为我们需要得到将删除的节点的前面一个节点.在单向链表中,节点中没有指向前一个节点的指针,所以只好从链表的头结点开始顺序查找.那是不是一定需要得到被删除的节点的前一个节点呢?答案是否定的.我们可以很方便

编程练习 输入一个链表 从尾到头打印节点值

两种方法 1,利用栈的方法实现 将节点里的值按顺序push压入到栈中 再将pop出栈的值按顺序赋值到节点里 2.原链表头节点删除 再 头插入到一个新的链表里 实现反转 1 #!/usr/bin/env python3 2 3 class Stack(object): 4 def __init__(self): 5 self._elems = [] 6 7 def is_empty(self): 8 return self._elems == [] 9 10 def push(self, elem

利用线性链表基本操作完成两个有序线性表的合并

La.Lb线性链表升序排列,将结果放在Lc链表里.之前有文章写过两个有序链表的合并 区别在于,前面的做法是保留La的头节点,free掉Lb的头节点,将余下节点串起来.这种方法是面向过程编程 而现在讨论的做法,是单独建立一个Lc链表,利用一些已经写好的基本操作函数来完成,这种模块化编程做法实际上还简单些.不光模块函数里写不了几行,在调用这些函数时减少了不必要的琐碎过程的思考时间. 该做法的核心思想:将每轮比较过后偏小的那个节点从相应链表中删除(这是头节点的指针不会指向该节点了,但该节点的空间依旧保