LVGL双向链表学习笔记
LVGL双向链表学习笔记
1、LVGL链表数据类型分析
对于LVGL双向链表的使用,我们需要关注lv_ll.h和lv_ll.c两个文件,其中lv_ll.h里面包含了链表结构类型定义,以及相关API的声明,首先介绍链表的结构类,如下图所示:

一开始看到这个类型声明我是懵的,怎么链表的一个结点的类型是uint8_t,那是不是LVGL这个双向链表只能用于uint8_t类型的数据?可是转念一想LVGL内部的定时器、任务都是基于这个双向链表实现的,肯定有我没有理解到的地方,uint8_t是啥,不就是一个字节吗?在计算机内存中的基本单位,也是硬件所能访问的最小单位,这里我们可以联想到任何数据类型都可以字节进行访问,那么怎么访问呢?答案:指针。可以看出链表类型中头尾指针都是lv_ll_node_t *也就是uint8_t *,这样就可以通过head和tail对任意类型的结点进行访问。
2、LVGL链表实现原理
上一节已经对LVGL双向链表的数据类型进行分析,接下来开始分析其实现原理。
2.1、双向链表初始化
双向链表初始化_lv_ll_init()函数,其定义如下:

该函数主要用于初始化一个双向链表,并通过传入参数lv_ll_ *ll_p返回已经初始化的双向链表句柄,这里要重点关注第二个参数node_size,顾名思义该参数表示的是结点所占字节的大小,但要特别说明一下这个node_size表示的只是结点的数据域大小,并没有包含next、prv指针域,这一点在后面分析结点插入时会详细说明。在函数内部对node_size进行了8字节或4字节的内存对齐,具体是8字节对齐还是4字节对齐跟具体的系统位数相关了(比如WIN32就是4字节,WIN64就是8字节)。
2.2、插入结点
通过分析LVGL插入一个结点我们才能真正理解其双向链表的实现原理以及巧妙之处,这里以尾插法的实现进行分析,即_lv_ll_ins_tail()函数,其定义如下图所示:

① 创建一个新的结点
使用lv_mem_alloc()进行动态申请,注意这里申请的内存大小是ll_p->n
_size + LL_NODE_META_SIZE,其中ll_p->n_size就是之前在初始化时传入的node_size,那么我们来看看LL_NODE_META_SIZE是多大,转到定义可以看到:

哈哈,果然是两个指针的大小,如果熟悉双向链表马上就可以推测出这两个指针对应的就是next、prev指针,那么我们可以得到下面的结点内存模型:

还有个问题就是为什么可以确定prev在前,next在后呢?答案可以在下面这两个宏定义中找到:

其中LL_PREV_P_OFFSET表示prev指针相对域结点首地址的偏移,同理LL_NEXT_P_OFFSET表示的是next指针相对于结点首地址的偏移。通过这个偏移地址应该可以很清楚的看出prev在next前面的位置吧。
然后我们可以得到如下的双向链表模型:

② 设置新结点的next
使用node_set_next()函数设置结点的next,因为尾插法,所以新结点的next为空,这里重点分析node_set_next()函数的实现,其定义如下所示:

可以看出该函数内部都是指针的操作,对于指针操作来说,使用内存变化来理解是最好不过了,该函数的内存变化过程如下:

最终内存0x00000024的值为NULL,这也符合我们的预期:尾插法新结点的next为NULL。这里也值得思考一下:为什么设置next指针为什么需要如此复杂?因为LVGL双向链表的结点数据域是由外部决定的,我们只能通过地址这个信息来访问,同样的结点类型为uint8_t,我们也是只能通过地址信息来进行访问,不通过结构体成员的方式来访问数据域、prev指针以及next指针。同时函数内部中出现了两个二级指针,他们的作用就是用来访问地址,如果我们直接对传入act、next这两个一级指针进行操作,只能改这两个指针变量保存的地址值,并不能对传入地址进行访问,所以需要借助二级指针来对传入地址进行访问。
③ 设置新结点的prev
使用node_set_prev()函数设置结点的next,同样这里重点分析node_set_prev()函数的实现,其定义如下所示:

可以看出node_set_prev()和node_set_next()内部实现几乎是一模一样的只是act8获取的prev指针的偏移,就这个差异。其内存变化如下:

最终内存0x00000040(n_new的prev指针)的值为0x00000008(n_prev)。
④ 设置链表尾结点的next
⑤ 更新链尾为新结点
实际上LVGL实现原理的核心就是对node_set_next()和node_set_prev两个函数的理解,掌握了这两个函数的实现剩下的就是对双向链表的理解了,相信学习过数据结构理解双向链表应该是小菜一碟了吧。所以剩下的头插法、删除结点这些就不再赘述。
3、LVGL链表应用实例
点击查看代码
#define STD_NAME_LEN_MAX 15
//学生信息类型
typedef struct StudentInfo StudentInfo_t;
struct StudentInfo
{
char name[STD_NAME_LEN_MAX];
int age;
int sex;
};
//学生信息表
StudentInfo_t std_table[] = {
{"ZhangSan", 23, 1},
{"LiSi", 25, 0},
{"WangWu", 26, 1},
};
void lv_ll_test(void)
{
StudentInfo_t* std;
lv_ll_t std_ll;
_lv_ll_init(&std_ll, sizeof(StudentInfo_t)); // 初始化std_ll链表
/* 遍历学生信息表,将学生信息添加到std_ll中 */
for (int i = 0; i < (sizeof(std_table) / sizeof(std_table[0])); i++)
{
std = (StudentInfo_t*)_lv_ll_ins_tail(&std_ll);
lv_snprintf(std->name, sizeof(std->name), std_table[i].name);
std->age = std_table[i].age;
std->sex = std_table[i].sex;
}
/* 遍历std_ll,验证学生信息是否正确添加到std_ll中 */
std = (StudentInfo_t*)_lv_ll_get_head(&std_ll);
while (std)
{
printf("name:%s age:%d sex:%d \n", std->name, std->age, std->sex);
std = (StudentInfo_t*)_lv_ll_get_next(&std_ll, std);
}
}
LVGL双向链表学习笔记的更多相关文章
- KPROCESS IDT PEB Ldr 《寒江独钓》内核学习笔记(3)
继续上一篇(2)未完成的研究,我们接下来学习 KPROCESS这个数据结构. 1. 相关阅读材料 <深入理解计算机系统(原书第2版)> 二. KPROCESS KPROCESS,也叫内核进 ...
- 《C++ Primer Plus》学习笔记10
<C++ Primer Plus>学习笔记10 <<<<<<<<<<<<<<<<<&l ...
- Java学习笔记——浅谈数据结构与Java集合框架(第一篇、List)
横看成岭侧成峰,远近高低各不同.不识庐山真面目,只缘身在此山中. --苏轼 这一块儿学的是云里雾里,咱们先从简单的入手.逐渐的拨开迷雾见太阳.本次先做List集合的三个实现类的学习笔记 List特点: ...
- Redis学习笔记(三)Redis支持的5种数据类型的总结
继续Redis学习笔记(二)来说说剩余的三种数据类型. 三.列表类型(List) 1.介绍 列表类型可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的一段片段.列表类型内部是 ...
- MOOS学习笔记1——HelloWorld
MOOS学习笔记1--HelloWorld 例程 /* * @功能:通讯客户端的最简单程序,向MOOSDB发送名为"Greeting" * 数据"Hello", ...
- 学习笔记 07 --- JUC集合
学习笔记 07 --- JUC集合 在讲JUC集合之前我们先总结一下Java的集合框架,主要包含Collection集合和Map类.Collection集合又能够划分为LIst和Set. 1. Lis ...
- (转)live555学习笔记-UsageEnvironment和TaskScheduler
2011-12-6阅读1264 评论1 一直想学习流媒体服务器的设计,这几天有点时间,看了一下live555的源代码.live555是一个开源的跨平台流媒体服务器,使用编程语言是C++.将现阶段学习笔 ...
- [Golang学习笔记] 08 链表
链表(Linked list)是一种常见数据结构,但并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针. 由于不必须按顺序存储,链表在插入的时候可以达到O(1),比顺序表快得多,但是查 ...
- <<C++标准程序库>>中的STL简单学习笔记
0. 内容为个人学习笔记, 仅供参考, 如有错漏, 欢迎指正! 1. STL中的所有组件都是由模板构成的, 所以其元素可以是任意型别的. 组件有: - 容器: 管理某类对象的集合. 不同的容器有各自的 ...
- java集合类学习笔记之LinkedHashMap
1.简述 LinkedHashMap是HashMap的子类,他们最大的不同是,HashMap内部维护的是一个单向的链表数组,而LinkedHashMap内部维护的是一个双向的链表数组.HashMap是 ...
随机推荐
- CF1770F Koxia and Sequence
一步都没想到,一定是状态不好吧,一定吧一定吧? 加训数数! 题意 给定 \(n, x, y\),定义好的序列 \(\{a_i\}_{i = 1}^n\) 满足 \(\sum\limits_{i = 1 ...
- 国标平台视频流抓取及rtp包解析
最近遇到国标平台无法播放视频流的问题.需要检查视频接收端和播放端视频的收包和发包是否正常.但是只看pcap文件不是很直观,所以将pcap文件中的rtp包解析出来,用播放器将视频播放出来,以更直观的判断 ...
- 把jar包打成docker镜像并推送到Docker Hub
1.准备需要的jar包并复制到服务器某个目录下 2.在此目录下,创建Dockerfile的文本文件,并将以下内容添加到文件中: # 基础镜像 FROM openjdk:8-jre # author(可 ...
- C# - ConcurrentDictionary 并发场景使用注意事项
1 自身作为 Enumerable 的遍历 自身作为可遍历对象,键值对为元素进行遍历,是线程安全的,但不提供快照,遍历过程中集合产生变更会直接反馈至此次遍历过程中.但并不一定能够保障获取数据的过程中, ...
- PowerBuilder从入坑到放弃(二)编码规范
前言 上一篇我们从0到1用pb开发了一个helloworld程序,并成功将开发的程序编译打包并且制作了安装包. 程序员最讨厌的莫过于写文档和别人不写注释. 不知道大家会不会和我一样,在找bug时,有段 ...
- .NET写一个自己的Lambda表达式与表达式树
LambdaExpression继承Expression Expression又继承LambdaExpressio 所以,Expression与 Expression的区别在于:泛型类以静态类型的方法 ...
- ChatGPT的探索与实践-业务应用篇
本篇文章主要介绍在实际的开发过程当中,如何使用GPT帮助开发,优化流程,恰逢今年京东20周年庆,文末会介绍如何与618大促实际的业务相结合,来提升应用价值.全是干货,且本文所有代码和脚本都是利用GPT ...
- nacos适配达梦、瀚高、人大金仓数据库及部分源码探究
一.插件实现 1.插件目录结构 2.pom依赖 <dependency> <groupId>com.alibaba.nacos</groupId> <arti ...
- golang channel 未关闭导致的内存泄漏
现象 某一个周末我们的服务 oom了,一个比较重要的job 没有跑完,需要重跑,以为是偶然,重跑成功,因为是周末没有去定位原因 又一个工作日,它又oom了,重跑成功,持续观察,job 在oom之前竟然 ...
- Unity Shader编辑器工具类ShaderUtil 常用函数和用法
Unity Shader编辑器工具类ShaderUtil 常用函数和用法 Unity的Shader编辑器工具类ShaderUtil提供了一系列函数,用于编译.导入和管理着色器.本文将介绍ShaderU ...