rt-thread 链表使用详解
一、什么是链表?

1、链表的基本概念
链表是一种线性数据结构,由一系列节点(Node)组成,每个节点包含数据域和指针域。数据域存储实际数据,指针域存储指向下一个节点的地址。链表通过指针实现动态内存分配,无需连续的内存空间。
2、链表的主要类型
-
单链表
每个节点仅包含一个指向后继节点的指针,最后一个节点的指针指向NULL。struct Node { int data; struct Node* next; }; -
双链表
节点包含两个指针,分别指向前驱和后继节点,支持双向遍历。struct Node { int data; struct Node* prev; struct Node* next; }; -
循环链表
尾节点的指针指向头节点,形成闭环。可分为单向循环链表和双向循环链表。
3、链表的操作复杂度
- 插入/删除:时间复杂度为 O ( 1 ) O(1) O(1)(已知节点位置时),无需移动元素。
- 随机访问:时间复杂度为 O ( n ) O(n) O(n),需从头节点遍历。
4、链表的优缺点
- 优点:动态内存分配,插入删除高效。
- 缺点:内存开销较大(需存储指针),不支持快速随机访问。
5、链表与数组的对比
| 特性 | 链表 | 数组 |
|---|---|---|
| 内存连续性 | 非连续 | 连续 |
| 大小调整 | 动态灵活 | 固定或需重新分配 |
| 访问速度 | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) |
| 插入删除速度 | O ( 1 ) O(1) O(1)(已知位置) | O ( n ) O(n) O(n)(需移动元素) |
| 链表是一种线性数据结构,由一系列节点(Node)组成,每个节点包含数据域和指针域。数据域存储实际数据,指针域存储指向下一个节点的地址。链表通过指针实现动态内存分配,无需连续的内存空间。 |
6、链表的主要类型
-
单链表
每个节点仅包含一个指向后继节点的指针,最后一个节点的指针指向NULL。struct Node { int data; struct Node* next; }; -
双链表
节点包含两个指针,分别指向前驱和后继节点,支持双向遍历。struct Node { int data; struct Node* prev; struct Node* next; }; -
循环链表
尾节点的指针指向头节点,形成闭环。可分为单向循环链表和双向循环链表。
7、链表的操作复杂度
- 插入/删除:时间复杂度为 O ( 1 ) O(1) O(1)(已知节点位置时),无需移动元素。
- 随机访问:时间复杂度为 O ( n ) O(n) O(n),需从头节点遍历。
8、链表的优缺点
- 优点:动态内存分配,插入删除高效。
- 缺点:内存开销较大(需存储指针),不支持快速随机访问。
9、链表与数组的对比
| 特性 | 链表 | 数组 |
|---|---|---|
| 内存连续性 | 非连续 | 连续 |
| 大小调整 | 动态灵活 | 固定或需重新分配 |
| 访问速度 | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) |
| 插入删除速度 | O ( 1 ) O(1) O(1)(已知位置) | O ( n ) O(n) O(n)(需移动元素) |
二、RT-Thread 链表
RT-Thread 使用侵入式双向链表作为核心数据结构,广泛应用于线程管理、定时器、设备驱动等模块。以下是完整的使用指南:
1、链表数据结构定义
在 rtdef.h 中定义链表节点:
struct rt_list_node {
struct rt_list_node *next; // 指向后继节点
struct rt_list_node *prev; // 指向前驱节点
};
typedef struct rt_list_node rt_list_t; // 链表类型
2、链表操作 API
1. 初始化链表
// 初始化链表头(创建空链表)
void rt_list_init(rt_list_t *l) {
l->next = l->prev = l;
}
2. 插入节点
// 在节点 l 后插入新节点 n
void rt_list_insert_after(rt_list_t *l, rt_list_t *n) {
l->next->prev = n;
n->next = l->next;
n->prev = l;
l->next = n;
}
// 在节点 l 前插入新节点 n
void rt_list_insert_before(rt_list_t *l, rt_list_t *n) {
l->prev->next = n;
n->prev = l->prev;
n->next = l;
l->prev = n;
}
3. 删除节点
// 从链表中移除节点 n
void rt_list_remove(rt_list_t *n) {
n->next->prev = n->prev;
n->prev->next = n->next;
n->next = n->prev = n; // 指向自身防止误用
}
4. 判断链表状态
// 检查链表是否为空
int rt_list_isempty(const rt_list_t *l) {
return (l->next == l);
}
5.获取链表长度
rt_inline unsigned int rt_list_len(const rt_list_t *l)
{
unsigned int len = 0;
const rt_list_t *p = l;
while (p->next != l)
{
p = p->next;
len ++;
}
return len;
}
3、链表遍历技巧
1、基本遍历(节点级)
rt_list_t *node;
rt_list_for_each(node, &list_head) {
// node 为当前链表节点指针
}
2、 宿主结构体遍历(核心技巧)
// 定义包含链表的宿主结构
struct my_struct {
int data;
rt_list_t list; // 链表成员
};
// 遍历获取宿主结构体指针
struct my_struct *obj;
rt_list_for_each_entry(obj, &list_head, list) {
// 可直接访问 obj->data
}
宏 rt_list_entry 实现原理:
#define rt_list_entry(node, type, member) \
((type *)((char *)(node) - (unsigned long)(&((type *)0)->member)))
4、关键注意事项
-
内存管理
链表操作不涉及内存分配/释放,宿主结构需自行管理内存:struct item *p = rt_malloc(sizeof(struct item)); rt_list_insert_after(&list_head, &p->node); -
线程安全
多线程访问时需加锁:rt_mutex_take(&list_mutex, RT_WAITING_FOREVER); // 链表操作 rt_mutex_release(&list_mutex); -
遍历安全宏
删除节点时使用_safe版本宏:rt_list_for_each_entry_safe(obj, tmp, &list, member)
5、链表在 RT-Thread 中的应用场景
-
线程管理
- 就绪列表
rt_thread_priority_table[] - 挂起列表
rt_thread_defunct
- 就绪列表
-
定时器管理
- 定时器列表
rt_timer_list
- 定时器列表
-
设备驱动
- 设备列表
rt_device_list
- 设备列表
-
IPC 对象
- 信号量/互斥量等待队列
6、调试技巧
-
检查链表完整性:
RT_ASSERT(node->next->prev == node); RT_ASSERT(node->prev->next == node); -
打印链表信息:
rt_kprintf("List: head=%p, next=%p, prev=%p\n", list, list->next, list->prev);
7、常见问题解决方案
问题1:遍历时出现死循环
原因:未正确初始化节点
解决:确保所有节点调用 rt_list_init()
问题2:访问宿主结构体崩溃
原因:rt_list_entry 宏成员名错误
解决:检查宏的第三个参数是否与结构体成员名一致
问题3:链表操作后数据丢失
原因:多线程竞争
解决:添加互斥锁保护临界区
8、总结
RT-Thread 的链表实现具有以下优势:
- 零内存开销:链表节点嵌入宿主结构
- O ( 1 ) O(1) O(1) 时间复杂度:插入/删除操作高效
- 类型安全:通过
rt_list_entry宏实现类型转换 - 线程友好:提供安全遍历宏
三、完整使用示例
#include <rtthread.h>
// 定义宿主结构
struct item {
int value;
rt_list_t node; // 链表节点
};
// 全局链表头
static rt_list_t item_list = RT_LIST_OBJECT_INIT(item_list);
void demo_run(void) {
// 初始化链表
rt_list_init(&item_list);
// 创建三个数据项
struct item items[3];
for (int i = 0; i < 3; i++) {
items[i].value = i * 10;
rt_list_init(&items[i].node);
rt_list_insert_before(&item_list, &items[i].node); // 插入链表
}
// 遍历打印数据
struct item *it;
rt_list_for_each_entry(it, &item_list, node) {
rt_kprintf("Value: %d\n", it->value);
}
// 删除第二个元素
rt_list_remove(&items[1].node);
rt_kprintf("After removal:\n");
// 安全遍历(删除时使用)
struct item *tmp;
rt_list_for_each_entry_safe(it, tmp, &item_list, node) {
rt_kprintf("Value: %d\n", it->value);
}
}

898

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



