RT-Thread版本:4.0.5
MCU型号:STM32F103RCT6(ARM Cortex-M3 内核)
rt-thread中的链表源码在rtservice.h中,以宏或者内联函数的形式给出,其中,list为双向链表,slist为单向链表。rt-thread链表主要是为了连接管理内核对象,因此并无数据域,仅有指针域。
1 单向链表
1.1 单向链表节点结构体
Single List structure定义在rtdef.h中:
struct rt_slist_node
{
struct rt_slist_node *next;
};
typedef struct rt_slist_node rt_slist_t;
链表是一种递归定义的结构体,即自已定义自已,需要注意只能定义指向自己的指针,因为指针大小可以确定,如果直接定义,编译器不知道具体大小会报错。
1.2 初始化
初始化有两种方法:
- 内联函数
rt_inline void rt_slist_init(rt_slist_t *l)
{
l->next = RT_NULL;
}
显然,这是带头结点的单链表初始化方式,将头结点指针域置空。
- 宏
#define RT_SLIST_OBJECT_INIT(object) { RT_NULL }
使用示例:
rt_slist_t sl_node = RT_SLIST_OBJECT_INIT(sl_node);
// 宏展开:
sl_node = {RT_NULL};
// 即:
sl_node = {.next = RT_NULL};
1.3 尾插
rt_inline void rt_slist_append(rt_slist_t *l, rt_slist_t *n)
{
struct rt_slist_node *node;
node = l;
// 遍历链表至表尾节点
while (node->next) node = node->next;
// 表尾节点指针域指向新插入节点
node->next = n;
// 新节点指针域置空
n->next = RT_NULL;
}
l:链表头指针
n:待插入的节点指针
注意:不能直接用头指针遍历链表,否则头指针最后将指向表尾,链表无法再使用。
1.4 头插
rt_inline void rt_slist_insert(rt_slist_t *l, rt_slist_t *n)
{
// 新节点指针域指向首元节点(第一个节点)
n->next = l->next;
// 将头节点指针域指向新节点,新节点成为首元节点
l->next = n;
}
1.5 求表长
rt_inline unsigned int rt_slist_len(const rt_slist_t *l)
{
unsigned int len = 0;
// l->next为第一个节点
const rt_slist_t *list = l->next;
while (list != RT_NULL)
{
list = list->next;
len ++;
}
return len;
}
const修饰指向表头结点的指针,表明求表长时不能修改原链表。
1.6 删除指定节点
rt_inline rt_slist_t *rt_slist_remove(rt_slist_t *l, rt_slist_t *n)
{
struct rt_slist_node *node = l;
// 遍历链表找到要删除节点的位置
while (node->next && node->next != n) node = node->next;
// 如果没有遍历到最后一个节点, 说明找到删除节点的位置, 即当前节点的下一节点node->next
if (node->next != (rt_slist_t *)0)
node->next = node->next->next; // 将当前节点指针域指向删除节点的指针域, 即跳过删除节点
return l; // 返回修改后的链表
}
1.7 获取首元节点
rt_inline rt_slist_t *rt_slist_first(rt_slist_t *l)
{
return l->next;
}
1.8 获取尾节点
rt_inline rt_slist_t *rt_slist_tail(rt_slist_t *l)
{
while (l->next) l = l->next;
return l;
}
1.9 获取当前节点下一个节点
rt_inline rt_slist_t *rt_slist_next(rt_slist_t *n)
{
return n->next;
}
1.10 判断表空
rt_inline int rt_slist_isempty(rt_slist_t *l)
{
return l->next == RT_NULL; // 带头节点的单链表表空判断方法
}
1.11 单向链表相关宏
关于rt_container_of具体分析可参考:rt_list_entry函数
/**
* @brief 根据单链表节点的地址,获取其所在type类型结构体的地址
* @param node 入口节点地址
* @param type 节点所在结构体的类型
* @param member 链表所在结构体中的链表变量的名称
*/
#define rt_slist_entry(node, type, member) \
rt_container_of(node, type, member)
/**
* 遍历单链表
* @pos: for循环中迭代变量(rt_slist_t *)
* @head: slist链表头结点
*/
#define rt_slist_for_each(pos, head) \
for (pos = (head)->next; pos != RT_NULL; pos = pos->next)
/**
* 遍历获取每个单链表节点所在type类型结构体的地址
* typeof 运算符把类型信息作为字符串返回, typeof(*pos)即为当前节点指向的结构体类型的字符
* @pos: 指向宿主结构的指针, 在for循环中是一个迭代变量(type*类型)
* @head: slist表头指针
* @member: 链表所在结构体中的链表变量的名称
*/
#define rt_slist_for_each_entry(pos, head, member) \
for (pos = rt_slist_entry((head)->next, typeof(*pos), member); \
&pos->member != (RT_NULL); \
pos = rt_slist_entry(pos->member.next, typeof(*pos), member))
/**
* 获取表头元素所在结构体地址
* @ptr: slist表头指针
* @type: 链表所在结构体的类型
* @member: 链表所在结构体中的链表变量的名称
* 注意:链表不能为空
*/
#define rt_slist_first_entry(ptr, type, member) \
rt_slist_entry((ptr)->next, type, member)
/**
* 获取表尾元素所在结构体地址
* @ptr: slist表头指针
* @type: 链表所在结构体的类型
* @member: 链表所在结构体中的链表变量的名称
* 注意:链表不能为空
*/
#define rt_slist_tail_entry(ptr, type, member) \
rt_slist_entry(rt_slist_tail(ptr), type, member)
2 双向链表
2.1 双向链表节点结构体
Single List structure定义在rtdef.h中:
struct rt_list_node
{
struct rt_list_node *next;
struct rt_list_node *prev;
};
typedef struct rt_list_node rt_list_t;
双向链表有两个指针域,next指向下一个节点,prev指向上一个节点。因为多了一个prev指针域,相对于单链表,其获取前驱结点比较方便(l->prev),时间复杂度为O(1);而单链表则需要遍历查找,时间复杂度为O(n)。
2.2 初始化
- 内联函数
rt_inline void rt_list_init(rt_list_t *l)
{
l->next = l->prev = l;
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ijUaZ9TG-1645286572274)(img/image-20220219214836118.png)]](/https://i-blog.csdnimg.cn/blog_migrate/ba7564fe77a8b49b42dd990d18a66c3a.png)
双向非循环链表初始化是将l->next = l->prev = NULL;,而这种是双向循环链表初始化方法,即将头结点的前驱指针与后继指针均指向自身。
- 宏
#define RT_LIST_OBJECT_INIT(object) { &(object), &(object) }
用法示例:
rt_list_t l_node = RT_LIST_OBJECT_INIT(l_node);
// 宏展开:
l_node = {&l_node, &l_node};
// 即:
l_node = {.next = &l_node, .prev = &l_node};
2.3 指定节点后插入
/**
* @param l list to insert it
* @param n new node to be inserted
*/
rt_inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n)
{
// 与当前节点的后一个节点连接
l->next->prev = n; // 1.将当前节点的后一个节点的prev指针指向新节点
n->next = l->next; // 2.将新节点的next指针指向当前节点的后一个节点
// 与当前节点连接
l->next = n; // 3.将当前节点的next指针指向新节点
n->prev = l; // 4.将新节点的prev指针指向当前节点
}

图中
Node1为当前节点,Node2当前节点的后一个节点
l为当前节点,n为插入在l后的新节点。双向链表插入相对单链表,需要修改两个方向上的指针,即当前节点l的next指针域(l->next),当前节点后一个节点的prev指针域(l->next->prev)。
2.4 指定节点前插入
/**
* @param l list to insert it
* @param n new node to be inserted
*/
rt_inline void rt_list_insert_before(rt_list_t *l, rt_list_t *n)
{
// 与当前节点的前一个节点连接
l->prev->next = n; // 1.将当前节点的前一个节点的next指针指向新节点
n->prev = l->prev; // 2.将新节点的prev指针指向当前节点的前一个节点
// 与当前节点连接
l->prev = n; // 3.将当前节点的prev指针指向新节点
n->next = l; // 4.将新节点的next指针指向当前节点
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c9L4Xevp-1645286572281)(img/image-20220219222209588.png)]](/https://i-blog.csdnimg.cn/blog_migrate/4b9cc5033dbff8c882abaaf8e70984d8.png)
图中
Node1为当前节点,Node3当前节点的前一个节点(Node1的prev指向它,这是一个双向循环链表,表尾next要指向表头,表头prev要指向表尾)
同时需要修改两个方向上的指针:当前节点l的prev指针域(l->prev),当前节点前一个节点的next指针域(l->prev->next)。
2.5 求表长
rt_inline unsigned int rt_list_len(const rt_list_t *l)
{
unsigned int len = 0;
const rt_list_t *p = l;
// 循环链表可以从任意节点开始遍历求表长, 当当前节点next指针自身时,结束遍历
while (p->next != l)
{
p = p->next;
len ++;
}
return len;
}
2.6 删除指定节点
rt_inline void rt_list_remove(rt_list_t *n)
{
n->next->prev = n->prev; // 1.被删节点的后一个节点的prev指向被删结点的前一个结点
n->prev->next = n->next; // 2.被删节点的前一个节点next指向被删结点的后一个结点
n->next = n->prev = n; // 3.被删结点置空
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2gmC7TOh-1645286572283)(img/image-20220219220201507.png)]](/https://i-blog.csdnimg.cn/blog_migrate/eab4a5ef5aa065cdb1a5343b9f927e72.png)
同时需要修改两个方向上的指针:被删节点的后一个节点的prev指针域(n->next->prev),被删节点的前一个节点的next指针域(n->prev->next)。
2.7 判断表空
rt_inline int rt_list_isempty(const rt_list_t *l)
{
return l->next == l;
}
2.8 双向链表相关宏
/**
* @brief 根据双向链表节点的地址,获取其所在type类型结构体的地址
* @param node 入口节点地址
* @param type 节点所在结构体的类型
* @param member 节点在该结构体中的成员名称
*/
#define rt_list_entry(node, type, member) \
rt_container_of(node, type, member)
/**
* 遍历双向循环链表
* @pos: for循环中迭代变量(rt_list_t *类型)
* @head: 表头
*/
#define rt_list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
/**
* 安全地遍历一个链表,防止遍历过程中删除链表元素引发的异常
* @pos: for循环中迭代变量(rt_list_t *类型)
* @n: 临时存储节点pos变量(rt_list_t *类型)
* @head: 表头
*/
#define rt_list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)
/**
* 遍历获取每个双向链表节点所在type类型结构体的地址
* typeof 运算符把类型信息作为字符串返回, typeof(*pos)即为当前节点指向的结构体类型的字符
* @pos: 指向宿主结构的指针, 在for循环中是一个迭代变量
* @head: 表头指针
* @member: 链表所在结构体中的链表变量的名称
*/
#define rt_list_for_each_entry(pos, head, member) \
for (pos = rt_list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = rt_list_entry(pos->member.next, typeof(*pos), member))
/**
* 安全遍历获取每个双向链表节点所在type类型结构体的地址
* typeof 运算符把类型信息作为字符串返回, typeof(*pos)即为当前节点指向的结构体类型的字符
* @pos: 指向宿主结构的指针, 在for循环中是一个迭代变量(type*类型)
* @n: 临时存储pos变量(type*类型)
* @head: 表头指针
* @member: 链表所在结构体中的链表变量的名称
*/
#define rt_list_for_each_entry_safe(pos, n, head, member) \
for (pos = rt_list_entry((head)->next, typeof(*pos), member), \
n = rt_list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = rt_list_entry(n->member.next, typeof(*n), member))
/**
* 获取表头元素所在结构体地址
* @ptr: 表头指针
* @type: 链表所在结构体的类型
* @member: 链表所在结构体中的链表变量的名称
* 注意:链表不能为空
*/
#define rt_list_first_entry(ptr, type, member) \
rt_list_entry((ptr)->next, type, member)
3 list_for_each与list_for_each_safe
list_for_each遍历链表循环程序中,如果将当前pos指向的节点内存释放,那么该节点的前后指针将变为野指针。此外,rt_list_init(pos)和rt_list_remove(pos)将让pos指向的节点的前后指针指向自身,导致死循环。list_for_each_safe被称为安全遍历,它的做法是:先将pos后继指针pos->next缓存到n,下次遍历时再将n赋给pos,同时让n = pos->next,这样避免了直接使用pos = pos->next引发的异常。
注意:这种安全遍历也仅对错误操作当前遍历的pos指针的节点有效,如果误操作其后面还未遍历到的节点,依然会出错。
测试代码:
rt_list_t l = RT_LIST_OBJECT_INIT(l);
rt_list_t l_node1 = RT_LIST_OBJECT_INIT(l_node1);
rt_list_t* l_node2 = new rt_list_t;
rt_list_t l_node3 = RT_LIST_OBJECT_INIT(l_node3);
int main(int* arac, char** argv)
{
rt_list_init(l_node2);
rt_list_insert_after(&l, &l_node1);
rt_list_insert_after(&l_node1, l_node2);
rt_list_insert_after(l_node2, &l_node3);
cout << "l_node1:" << &l_node1 << endl;
cout << "l_node2:" << l_node2 << endl;
cout << "l_node3:" << &l_node3 << endl;
cout << "-------------------" << endl;
rt_list_t* node_ptr = &l_node1;
rt_list_t* l_ptr = &l;
rt_list_t* n_ptr;
rt_list_for_each_safe(node_ptr, n_ptr, l_ptr)
// rt_list_for_each(node_ptr, l_ptr)
{
cout << node_ptr << endl;
// 非安全遍历会死循环
// rt_list_remove(node_ptr);
// rt_list_init(node_ptr);
// 野指针 程序崩溃
if (node_ptr == l_node2 && node_ptr != NULL) {
delete node_ptr;
node_ptr = NULL;
}
}
system("pause");
return 0;
}
- 死循环情况:
rt_list_for_each:

rt_list_for_each_safe:

- 野指针情况:
rt_list_for_each:

rt_list_for_each_safe:

END
本文详细介绍了RT-Thread中单向链表(SingleList)和双向链表(RT_List)的节点结构、初始化、插入、删除、判断表空等操作,并重点讲解了如何安全地遍历链表。通过实例展示了list_for_each与list_for_each_safe的区别。

2167

被折叠的 条评论
为什么被折叠?



