RT-Thread版本:4.0.5
MCU型号:STM32F103RCT6(ARM Cortex-M3 内核)
rt-thread中的链表源码在rtservice.h
中,以宏或者内联函数的形式给出,其中,list
为双向链表,slist
为单向链表。rt-thread链表主要是为了连接管理内核对象,因此并无数据域,仅有指针域。
Single List structure
定义在rtdef.h
中:
struct rt_slist_node
{
struct rt_slist_node *next;
};
typedef struct rt_slist_node rt_slist_t;
链表是一种递归定义的结构体,即自已定义自已,需要注意只能定义指向自己的指针,因为指针大小可以确定,如果直接定义,编译器不知道具体大小会报错。
初始化有两种方法:
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};
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
:待插入的节点指针
注意:不能直接用头指针遍历链表,否则头指针最后将指向表尾,链表无法再使用。
rt_inline void rt_slist_insert(rt_slist_t *l, rt_slist_t *n)
{
// 新节点指针域指向首元节点(第一个节点)
n->next = l->next;
// 将头节点指针域指向新节点,新节点成为首元节点
l->next = n;
}
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
修饰指向表头结点的指针,表明求表长时不能修改原链表。
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; // 返回修改后的链表
}
rt_inline rt_slist_t *rt_slist_first(rt_slist_t *l)
{
return l->next;
}
rt_inline rt_slist_t *rt_slist_tail(rt_slist_t *l)
{
while (l->next) l = l->next;
return l;
}
rt_inline rt_slist_t *rt_slist_next(rt_slist_t *n)
{
return n->next;
}
rt_inline int rt_slist_isempty(rt_slist_t *l)
{
return l->next == RT_NULL; // 带头节点的单链表表空判断方法
}
关于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)
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)。
rt_inline void rt_list_init(rt_list_t *l)
{
l->next = l->prev = l;
}
双向非循环链表初始化是将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};
/**
* @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
)。
/**
* @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指针指向当前节点
}
图中
Node1
为当前节点,Node3
当前节点的前一个节点(Node1
的prev
指向它,这是一个双向循环链表,表尾next
要指向表头,表头prev
要指向表尾)
同时需要修改两个方向上的指针:当前节点l
的prev
指针域(l->prev
),当前节点前一个节点的next
指针域(l->prev->next
)。
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;
}
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.被删结点置空
}
同时需要修改两个方向上的指针:被删节点的后一个节点的prev
指针域(n->next->prev
),被删节点的前一个节点的next
指针域(n->prev->next
)。
rt_inline int rt_list_isempty(const rt_list_t *l)
{
return l->next == l;
}
/**
* @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)
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;
}
END