RTOS-F429-HAL-列表的插入和删除(2026/7/29)
目录一FreeRTOS 列表 — 双向环形链表1、为什么不用数组2.列表的结构体原型3、三个成员逐个拆4、画出来5、哨兵存在的意义6列表下标的意义二FreeRTOS 列表项1结构体源码2、五个成员逐个拆3、每一列排队的人身上带了什么4、跟我们之前学的 TCB 连起来三迷你列表项1结构体定义四列表和列表项的关系1、空链表初始状态2、插入列表项 1xItemValue 103、再插入列表项 2xItemValue 204、哨兵的 xItemValue 为什么是 0xFFFFFFFF五FreeRTOS 列表 — 完整梳理1、为什么用链表而不用数组2、三种结构体3、List_t列表— 3 个成员4、ListItem_t列表项— 5 个成员5、MiniListItem_t哨兵— 精简版列表项6、插入操作——四步3 和 4 不能换7、三者关系一张图1pxReadyTasksLists[3].xListEnd.next2列表项 排序六API① vListInitialise — 列表初始化② vListInitialiseItem — 列表项初始化③ vListInsert — 升序插入④ vListInsertEnd — 尾插时间片轮转用⑤ uxListRemove — 摘除使用示例七FreeRTOS 挂起和恢复实验一、实验目的二、API 对比三、注意点一FreeRTOS 列表 — 双向环形链表1、为什么不用数组任务数量不固定——你随时可能调xTaskCreate加一个任务、vTaskDelete删一个任务。数组长度要预先声明最大任务数写死就浪费写少了溢出。链表动态挂接——每创建一个任务就多一个节点删了就摘下来内存用量跟任务数量完全匹配。这正是 RTOS 需要的数据结构。2.列表的结构体原型typedef struct xLIST { listFIRST_LIST_INTEGRITY_CHECK_VALUE /* 校验值 */ volatile UBaseType_t uxNumberOfItems; /* 列表中的列表项数量 */ ListItem_t * configLIST_VOLATILE pxIndex /* 用于遍历列表项的指针 */ MiniListItem_t xListEnd /* ③ 哨兵节点迷你*/ listSECOND_LIST_INTEGRITY_CHECK_VALUE /* 校验值 */ } List_t;两个宏校验值默认不开忽略。3、三个成员逐个拆①uxNumberOfItems— 有几个排队的人记录列表里有几个列表项不含xListEnd它不算排队的。xTaskCreate时 1vTaskDelete时 -1。调度器判断这个优先级有没有任务就看这个值是不是 0。②pxIndex— 一根指针用来遍历调度器看同优先级任务时不能一直挑第一个跑会饿死后面的。pxIndex指向下一个该跑的每次时间片轮转就往后挪一个节点。这就是时间片轮转的实现。③xListEnd— 永远站在末尾的哨兵它是一个迷你列表项——不是真正的任务节点。它的作用标记链表终点也是起点因为环形插入新节点时插在它前面尾插法永远不摘掉、不计入uxNumberOfItems4、画出来List_t就绪列表[2] ┌─────────────────────┐ │ uxNumberOfItems 3 │ │ pxIndex ─────────┐ │ │ xListEnd ← 哨兵 │ │ └──────────────────┼──┘ │ ┌──────────────┘ │ 哨兵 列表项1(TaskA) 列表项2(TaskB) 列表项3(TaskC) ▼ ▲ ▲ │ ▲ │ ▲ │ ├───┤ prev ←─────┤ │ ←────── prev┤ │ ←────── prev┤ │ │哨兵│ │ └──→ next ───┤ └──→ next ───┤ └──→ next ──┐ ├───┤ ├───┤ ├───┤ ├───┤ │ └───┘ └───┘ └───┘ └───┘ │ ↑ │ └──────────────────────────────────────────────────────────────┘ 环形最后一项的 next 回到哨兵 哨兵的 prev 指向最后一项5、哨兵存在的意义没有哨兵的单链表插入头尾要判 NULL环形链表不带哨兵要判断是不是回到了起点。有哨兵之后哨兵 是最后一项哨兵不是排队的人它是一个固定的路标——插在环形链表的第一个人的前面、最后一个人的后面让环变成一个永不 NULL 的闭环。next 指向队首prev 指向队尾 因此 xListEnd.prev 指向队尾 xListEnd.next 指向队首 没有哨兵的单链表插入头尾要判 NULL环形链表不带哨兵要判断是不是回到了起点。有哨兵之后 // 插入新列表项——永远是插在哨兵前面尾插 newItem-prev xListEnd.prev; // 新节点的前驱 现在的尾 newItem-next xListEnd; // 新节点的后继 哨兵 xListEnd.prev-next newItem; // 原来的尾 → 新节点 xListEnd.prev newItem; // 哨兵的前驱 → 新节点 // 不用判空不用判是不是第一个永远一样 哨兵是链表的润滑剂——有它在所有插入删除操作都不需要特殊判断。哨兵是链表的润滑剂——有它在所有插入删除操作都不需要特殊判断告诉新朋友之前的队尾是谁newItem-prev xListEnd.prev告诉新朋友你后面就是哨兵了newItem-next xListEnd告诉原来的队尾你后面不是哨兵了是新来的xListEnd.prev-next newItem告诉哨兵新来的变成队尾了xListEnd.prev newItem先 3 后 4步骤 3 要读xListEnd.prev原来的队尾地址步骤 4 会把它改掉。一旦先改掉步骤 3 拿不到原来的队尾链就断了。6列表下标的意义pxReadyTasksLists[2]是一个数组下标——所有优先级为 2 的任务都串在这条链表里排队。uxNumberOfItems存的是这条链表上有几个真实任务。pxReadyTasksLists[0] → 优先级 0 任务空闲任务的链表 pxReadyTasksLists[1] → 优先级 1 任务的链表 pxReadyTasksLists[2] → 优先级 2 任务的链表 ← task1 在这 pxReadyTasksLists[3] → 优先级 3 任务的链表 ← task2 在这 pxReadyTasksLists[4] → 优先级 4 任务的链表 ← task3 在这 ... pxReadyTasksLists[31]→ 优先级 31 任务的链表数组长度 configMAX_PRIORITIES 32。每个槽一个链表调度器从高到低扫——先看 [31] 有没有人有就挑队首跑。二FreeRTOS 列表项1结构体源码struct xLIST_ITEM { listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /* 用于检测列表项的数据完整性 */ configLIST_VOLATILE TickType_t xItemValue /* 列表项的值 */ struct xLIST_ITEM * configLIST_VOLATILE pxNext /* 下一个列表项 */ struct xLIST_ITEM * configLIST_VOLATILE pxPrevious /* 上一个列表项 */ void * pvOwner /* 列表项的拥有者 */ struct xLIST * configLIST_VOLATILE pxContainer /* 列表项所在列表 */ listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /* 用于检测列表项的数据完整性 */ }; typedef struct xLIST_ITEM ListItem_t; ​ struct xLIST_ITEM { TickType_t xItemValue; /* ① 排序用的值 */ struct xLIST_ITEM *pxNext; /* ② 指向下一个列表项 */ struct xLIST_ITEM *pxPrevious; /* ③ 指向上一个列表项 */ void *pvOwner; /* ④ 谁拥有这个列表项 */ struct xLIST *pxContainer; /* ⑤ 这个列表项挂在哪条链表 */ }; typedef struct xLIST_ITEM ListItem_t;两个校验宏默认关忽略我们只看①~⑤ 几项2、五个成员逐个拆①xItemValue— 排序键// FreeRTOS 内部定义tasks.c static List_t pxReadyTasksLists[configMAX_PRIORITIES]; // 数组32 条链表 static List_t xDelayedTaskList1; // 一条链表 static List_t xDelayedTaskList2; // 一条链表辅助 static List_t xSuspendedTaskList; // 一条链表 ​ // 类型全是 List_t跟 int a, b, c 声明三个 int 一样32 个就绪链表 2 个延时链表 1 个挂起链表全是一个结构体类型。区别只是xItemValue排序方式不同——就绪链表按优先级倒序、延时链表按唤醒时间升序、挂起链表不排序。按这个值升序排列。不同的链表用不同的值排序链表xItemValue 存的是效果pxReadyTasksLists[x]任务优先级高优先级在队尾逆序队首先跑xDelayedTaskList唤醒时间xTickCount delay谁先到期谁排前面xSuspendedTaskList不用排序无意义挂起了排不排无所谓延时列表最直观现在 xTickCount 100 ​ taskA 调 vTaskDelay(50)xItemValue 100 50 150 taskB 调 vTaskDelay(30)xItemValue 100 30 130 taskC 调 vTaskDelay(80)xItemValue 100 80 180 ​ 插入 xDelayedTaskList 后按 xItemValue 升序排队 队首(130) → 队尾(180) ​ taskB(130) → taskA(150) → taskC(180) → 哨兵 最先醒 ↑ ↑ 最后醒SysTick 每 1ms 只看队首队首.xItemValue ≤ xTickCount 了没到了就摘下来插回就绪列表。不用扫整条链表——只有队首到时间了后面的肯定还没到。②③pxNext/pxPrevious— 双向环的指针就是你刚才学的那四条线。next指向后一个节点prev指向前一个。环形——最后一个节点的next指向哨兵哨兵的next指向对头。④pvOwner— 谁拥有这个列表项指向包含这个列表项的对象。对于状态列表项xStateListItempvOwner指向这个任务的TCB。pxReadyTasksLists[2] → 某列表项 → pvOwner → TCB(task1) ├─ pcTaskName task1 ├─ uxPriority 2 └─ pxTopOfStack ...调度器通过列表项找到 TCB再通过 TCB 找到任务栈——全部靠pvOwner这条链。⑤pxContainer— 这个列表项挂在哪个链表指向包含这条列表项的列表List_t。xStateListItem.pxContainer pxReadyTasksLists[2];摘除时用来检查——如果pxContainer NULL说明这个节点已经被摘掉了再次摘除会炸FreeRTOS 用这个做防御。3、每一列排队的人身上带了什么列表项就像每个学生胸前夹着的号码牌 ├─ xItemValue → 号码本身按号排队 ├─ pxNext/prev → 告诉前后的人你在哪 ├─ pvOwner → 指向学生本人TCB| 调度器通过号码牌找到你 └─ pxContainer → 哪个班级的队列 | 防止插错队4、跟我们之前学的 TCB 连起来// TCB 里嵌了列表项——任务到处排队都靠它 typedef struct tskTaskControlBlock { ListItem_t xStateListItem; // 状态排队就绪/阻塞/挂起 ListItem_t xEventListItem; // 事件排队等队列/等信号量 ... } TCB_t; // 调度器扫就绪列表时 ListItem_t *node pxReadyTasksLists[3].xListEnd.next; // 取队首 TCB_t *task (TCB_t *)node-pvOwner; // 通过 pvOwner 找到任务 // 然后从 TCB 读栈指针 → PendSV → 跑这个任务三迷你列表项1结构体定义struct xMINI_LIST_ITEM { listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /* 用于检测数据完整性 */ configLIST_VOLATILE TickType_t xItemValue; /* ①列表项的值哨兵固定填 0xFFFFFFFF */ struct xLIST_ITEM * configLIST_VOLATILE pxNext; /* ②上一个列表项注图中注释可能为笔误实际应为“下一个”需结合双向链表逻辑修正 */ struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /*③ 下一个列表项注图中注释可能为笔误实际应为“上一个”需结合双向链表逻辑修正 */ }; typedef struct xMINI_LIST_ITEM MiniListItem_t;1个校验宏默认关忽略我们看①~③它是 List_t 里的xListEnd——那个永远站岗的哨兵。省掉 pvOwner 和 pxContainer 是因为一个哨兵只需要知道前后是谁不需要知道自己是哪个任务、自己在哪个列表——它就不是任务。省掉一点内存。四列表和列表项的关系1、空链表初始状态List_t 刚创建时里面一个人都没有 uxNumberOfItems 0 pxIndex ──────────────┐ xListEnd │ xItemValue 0xFFFFFFFF ← 比所有真实任务的值都大 pxNext ────┐ │ pxPrevious ─│───┐ │ │ │ │ ▼ ▼ ▼ 哨兵自己指自己自闭环 ┌───────────┐ │ pxNext ───→ 自己 │ pxPrevious ─→ 自己 └───────────┘2、插入列表项 1xItemValue 10插入前哨兵自闭环 插入后 哨兵 列表项1(10) pxNext ────→ 列表项1 pxNext ────→ 哨兵 pxPrev ←──── 列表项1 pxPrev ←──── 哨兵 uxNumberOfItems 1 pxIndex → 列表项1 等价于 哨兵 ←→ 列表项1 ←→ 哨兵3、再插入列表项 2xItemValue 20升序排列2010插在列表项1后面哨兵 列表项1(10) 列表项2(20) pxNext ────────→ 列表项1 pxNext ────→ 列表项2 pxNext ────→ 哨兵 pxPrev ←─────── 列表项2 pxPrev ←── 哨兵 pxPrev ←── 列表项1 遍历顺序哨兵 → 列表项1(10) → 列表项2(20) → 哨兵回到起点 uxNumberOfItems 2 pxIndex → 列表项1 可视化 ┌→ 哨兵 ←─────────────┐ │ ↕ │ │ 列表项1(10) │ │ ↕ │ └── 列表项2(20) ──────┘4、哨兵的 xItemValue 为什么是 0xFFFFFFFF升序排列的列表插入算法是从头往后扫while (新节点的值 当前节点的值) 继续往后走; // 遇到比它大的节点 → 插在这个节点前面哨兵的值是最大值0xFFFFFFFF4294967295任何真实任务的值都比它小。所以循环到了哨兵一定停新节点永远插入在哨兵前面——这正是尾插法。哨兵用一个极大值把自己变成链表的逻辑终点。五FreeRTOS 列表 — 完整梳理1、为什么用链表而不用数组任务数量随时变——创建就加、删除就减。链表动态插拔内存按需分配。数组要预声明长度写死浪费写小溢出。RTOS 核心数据结构必然是链表。2、三种结构体结构体作用实例List_t链表本身带头结点pxReadyTasksLists[32]、xDelayedTaskList、xSuspendedTaskListListItem_t完整列表项嵌在 TCB 里TCB.xStateListItem、TCB.xEventListItemMiniListItem_t精简列表项就是哨兵List_t.xListEnd3、List_t列表— 3 个成员typedef struct xLIST { UBaseType_t uxNumberOfItems; /* ① 有几个真实任务在排队 */ ListItem_t *pxIndex; /* ② 遍历指针指向下一个该跑的 */ MiniListItem_t xListEnd; /* ③ 哨兵永远站末尾 */ } List_t;成员作用uxNumberOfItems不计哨兵。创建 1删除 -1调度器凭此判空pxIndex时间片轮转用——指向下一个该跑的跑完就往后挪xListEnd哨兵永远在环里。next指向队首prev指向队尾4、ListItem_t列表项— 5 个成员struct xLIST_ITEM { TickType_t xItemValue; /* ① 排序键——按此值升序排队 */ struct xLIST_ITEM *pxNext; /* ② 指向下一个 */ struct xLIST_ITEM *pxPrevious; /* ③ 指向上一个 */ void *pvOwner; /* ④ 谁拥有它 → 指向 TCB */ struct xLIST *pxContainer; /* ⑤ 它挂在哪个列表里 */ };pvOwner是调度器的命脉——从列表项反查 TCB再反查任务栈PendSV 切过去。整条调用链全靠这个指针。xItemValue在不同列表里含义不同列表xItemValue排序方式pxReadyTasksLists[x]任务优先级高在前队首先跑xDelayedTaskList唤醒时间xTickCountdelay小在前谁先到期谁排前面xSuspendedTaskList不排序随便放pxContainer是防御——摘除时检查是不是从这条列表摘防二次摘除。5、MiniListItem_t哨兵— 精简版列表项struct xMINI_LIST_ITEM { TickType_t xItemValue; /* 固定 0xFFFFFFFF最大值 */ struct xLIST_ITEM *pxNext; /* 指向队首 */ struct xLIST_ITEM *pxPrevious; /* 指向队尾 */ };没有pvOwner和pxContainer——它不是任务不需要知道主子是谁。xItemValue 0xFFFFFFFF是升序链表的逻辑终点——插入算法从头扫遇到比新节点大的值就停到哨兵必然停因为是最大值。新节点永远插在哨兵前面——天然尾插法。6、插入操作——四步3 和 4 不能换// 尾插永远插在哨兵前面 newItem-prev xListEnd.prev; // ① 新朋友记住之前的队尾 newItem-next xListEnd; // ② 新朋友你后面是哨兵 xListEnd.prev-next newItem; // ③ 告诉原来的队尾你后面换人了 xListEnd.prev newItem; // ④ 哨兵以后队尾是他了步骤 3 要读xListEnd.prev原队尾地址步骤 4 会把它改掉。先 3 后 4不然链断。7、三者关系一张图pxReadyTasksLists[2]List_t ┌──────────────────────┐ │ uxNumberOfItems 2 │ │ pxIndex ──────────┐ │ │ xListEnd哨兵 │ │ │ next ─────────┐ │ │ │ prev ───────┐ │ │ │ └───────────────┼─┼─┼──┘ │ │ │ ┌───────────┘ │ └──────────────┐ ▼ ▼ ▼ 哨兵 列表项1 列表项2 xItemValue10 xItemValue20 pxNext → 项2 pxNext → 哨兵 pxPrev → 哨兵 pxPrev → 项1 pvOwner → TCB_A pvOwner → TCB_B pxContainer → pxContainer → ReadyLists[2] ReadyLists[2]调度器挑任务pxReadyTasksLists[3].xListEnd.next→ 取列表项 →pvOwner→ TCB → 栈指针 → PendSV → 跑。整条路径从列表开始以 TCB 结束。1pxReadyTasksLists[3].xListEnd.nextpxReadyTasksLists[3].xListEnd.next 优先级 3 的链表上排第一个的任务。不是从 31 往下扫。调度器用位图 CLZ 指令O(1) 直达最高优先级// 每个优先级有一个 bit 标记是否有人就绪 // 优先级 3 有人 → bit 3 1 // 优先级 5 有人 → bit 5 1 // 优先级 0~2 没人 → bit 0/1/2 0 // uxTopReadyPriority 位图0b...00101000 // ↑ bit5 是最高的 1 // __clz(位图) → 算出最高那位的编号 5 // 直接取 pxReadyTasksLists[5].xListEnd.next → 队首任务 ✅这就是之前configUSE_PORT_OPTIMISED_TASK_SELECTION 1做的事——用硬件 CLZ 指令一条指令找到最高优先级不循环。[3]是我举的例子实际代码会自动找到当前最高的那个数字。2列表项 排序列表自动帮你排好。插入时按xItemValue升序——谁的值最小谁站前面。// 三个任务优先级都是 3同时进 xDelayedTaskList taskA 延时 50msxItemValue 100 50 150 taskB 延时 30msxItemValue 100 30 130 taskC 延时 80msxItemValue 100 80 180 // 插入函数 vListInsert 逐个比较升序排列 队首: taskB(130) → taskA(150) → taskC(180) :队尾 · 哨兵 最先醒 ↑ ↑ 最后醒SysTick 只盯队首——队首没到时间后面的肯定也没到。B 醒了摘走A 变成新的队首继续判断。就绪列表的同优先级任务特殊处理——新来的插在队尾实现先就绪的先跑FIFO时间片轮转时才往后挪。六API① vListInitialise — 列表初始化void vListInitialise(List_t *pxList);让一个刚定义的List_t变成空环uxNumberOfItems 0pxIndex指向哨兵哨兵自己指自己。调用一次即可后续直接往里插节点。② vListInitialiseItem — 列表项初始化void vListInitialiseItem(ListItem_t *pxItem);把列表项的prev/next/pvOwner/pxContainer/xItemValue全部归零。插入链表前必须调。③ vListInsert — 升序插入void vListInsert(List_t *pxList, ListItem_t *pxNewItem);从头往后扫找到第一个xItemValue大于新节点的位置插在它前面。结果xItemValue 小的在前大的在后。延时队列靠这个排队——谁先到期谁排前面。④ vListInsertEnd — 尾插时间片轮转用void vListInsertEnd(List_t *pxList, ListItem_t *pxNewItem);不管 xItemValue直接插在pxIndex指向的节点后面。同优先级任务就绪时用——新来的排到同优先级队的末尾保证先就绪的先跑FIFO。⑤ uxListRemove — 摘除uint32_t uxListRemove(ListItem_t *pxItem);把列表项从它所属的链表上摘下来前后节点自动重连。返回摘除后链表还剩几个节点。不释放内存——只摘不删。任务从就绪变阻塞时调这个。使用示例List_t TestList; /* 测试列表含哨兵 xListEnd*/ // 创建了一个列表包括哨兵存在 .bss 段 ListItem_t ListItem1; /* 列表项 1xItemValue40 */ // 创建了一个列表项节点存在 .bss 段 ListItem_t ListItem2; /* 列表项 2xItemValue60 */ ListItem_t ListItem3; /* 列表项 3xItemValue50 */ vListInitialise(TestList); vListInitialiseItem(ListItem1); vListInsert(TestList, ListItem1); /* 升序插入 */ vListInsertEnd(TestList, ListItem2); /* 尾插插在 pxIndex 后面 */ uxListRemove(ListItem2); /* 摘除不会释放内存 */ 创建变量介绍 List_t是一个结构体 typedef struct xLIST { listFIRST_LIST_INTEGRITY_CHECK_VALUE /* Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */ configLIST_VOLATILE UBaseType_t uxNumberOfItems; ListItem_t * configLIST_VOLATILE pxIndex; /* Used to walk through the list. Points to the last item returned by a call to listGET_OWNER_OF_NEXT_ENTRY (). */ MiniListItem_t xListEnd; /* List item that contains the maximum possible item value meaning it is always at the end of the list and is therefore used as a marker. */ listSECOND_LIST_INTEGRITY_CHECK_VALUE /* Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */ } List_t; ListItem_t 也是结构体 struct xLIST_ITEM //先定义结构体类型 { listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /* Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */ configLIST_VOLATILE TickType_t xItemValue; /* The value being listed. In most cases this is used to sort the list in descending order. */ struct xLIST_ITEM * configLIST_VOLATILE pxNext; /* Pointer to the next ListItem_t in the list. */ struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /* Pointer to the previous ListItem_t in the list. */ void * pvOwner; /* Pointer to the object (normally a TCB) that contains the list item. There is therefore a two way link between the object containing the list item and the list item itself. */ void * configLIST_VOLATILE pvContainer; /* Pointer to the list in which this list item is placed (if any). */ listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /* Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */ }; typedef struct xLIST_ITEM ListItem_t; // 给这个类型起个别名 ListItem_t七FreeRTOS 挂起和恢复实验一、实验目的掌握 FreeRTOS 内核列表List_t和列表项ListItem_t的操作函数通过串口打印每一步的指针地址验证双向环形链表的插入、删除、尾插操作与理论分析一致。二、API 对比vListInsertvListInsertEnduxListRemove功能按xItemValue升序插入插在pxIndex指向节点前面从链表中摘除列表项参数(List_t*, ListItem_t*)(List_t*, ListItem_t*)(ListItem_t*)排序✅ 自动排序❌ 不排序纯位置插入—返回值无无剩余节点数对 pxIndex 影响不影响不影响仅以它为插入位锚点若 pxIndex 指向被删项则前移时间复杂度O(n)O(1)O(1)典型用途任务插入就绪列表按优先级排序任务加入延时列表尾部任务从当前状态列表移除辅助 APIAPI功能vListInitialise()初始化列表哨兵自指计数器归零vListInitialiseItem()初始化列表项pvContainer NULL三、注意点pxIndex 永远指向“当前遍历认为的最后一个节点也就是哨兵”修改指向以后比如我们实验代码中的 TestList.pxIndex ListItem1;TestList.pxIndex ListItem1; printf(/***** 第八步修改指针指向后尾插项2 *****/\r\n); vListInsertEnd(TestList, ListItem2); printf(pxIndex 0x%p (应为列表项1地址)\r\n, TestList.pxIndex); printf(哨兵.next 0x%p (应为列表项2地址)\r\n, TestList.xListEnd.pxNext); printf(Item1.next 0x%p (应为列表项3地址)\r\n, ListItem1.pxNext); printf(Item2.next 0x%p (应为列表项1地址)\r\n, ListItem2.pxNext); printf(Item3.next 0x%p (应为哨兵)\r\n, ListItem3.pxNext); printf(哨兵.prev 0x%p (应为列表项3地址)\r\n, TestList.xListEnd.pxPrevious); printf(/********* 结束 *********/\r\n\r\n); printf( 实验结束 \r\n);让他指向了列表项1 导致格局变成了xListEnd ⇄ ListItem2(60) ⇄ ListItem1(40) ⇄ ListItem3(50) ⇄ xListEnd 以ListItem1为最后节点在此之前插入ListItem2

相关新闻

五大具身模型详解:VLM、VLA、VLN、VLX、世界模型

五大具身模型详解:VLM、VLA、VLN、VLX、世界模型

2026年再看具身智能:五类模型正在被“吞噬”,理解底层逻辑才有入场券 ——全景架构 相信你不止一次被VLM、VLN、VLA、VLX、世界模型这一串缩写搞得头晕眼花过。 它们分别解决什么问题?彼此之间是什么关系?更重要的是&#xff0…

2026/7/30 3:21:27阅读更多 →
降AI网站哪个好用?2027 推荐榜单

降AI网站哪个好用?2027 推荐榜单

这次按检测免费次数、报告可读性、平台适配、价格四个维度打分,综合得分靠前的是 BunnyScholar,gradu 助研君(gradu.cn)性价比更突出,其余几家客观带过,具体分数看下面的表,不想看长篇的直接扫表就能选。这轮整理是因为身边好几个同学在问&qu…

2026/7/30 3:21:27阅读更多 →
蓝速科技 RISC-V+ 鸿蒙双国产信创终端部署指南

蓝速科技 RISC-V+ 鸿蒙双国产信创终端部署指南

在政企数字化转型的深水区,信息安全早已不再是单纯的“防火墙”概念,而是深入到了芯片指令集与操作系统内核的底层重构。过去几年,我们在推进国产化替代时,常陷入“硬软两张皮”的困境:国产芯片跑着优化不足的通用系统…

2026/7/30 3:19:26阅读更多 →
Android ADB音量控制:从原理到自动化脚本实战

Android ADB音量控制:从原理到自动化脚本实战

1. 从一次深夜调试说起:为什么ADB音量控制如此重要 那天晚上,我正为一个自动化测试脚本焦头烂额。脚本需要在不同场景下,动态调整被测Android设备的媒体音量,以验证音频播放逻辑。我本能地想到用UI自动化工具去模拟点击音量键&…

2026/7/30 4:37:41阅读更多 →
Unity预制体核心机制与高效开发实战指南

Unity预制体核心机制与高效开发实战指南

1. 项目概述:为什么预制体是Unity开发者的“效率倍增器”?如果你在Unity里做过稍微复杂一点的场景,比如一个有几十盏路灯的街道,或者一个有成百上千个相同敌人的关卡,那你一定体会过“复制粘贴”的痛苦。改一个模型&am…

2026/7/30 4:37:41阅读更多 →
STM32按键消抖实战:从硬件RC滤波到软件状态机,打造可靠输入系统

STM32按键消抖实战:从硬件RC滤波到软件状态机,打造可靠输入系统

1. 项目概述:从“抖动”到“可靠”的按键处理哲学 做嵌入式开发,尤其是用STM32这类MCU,按键输入几乎是每个项目都绕不开的基础功能。但就是这个看似简单的“按一下”,却让不少新手甚至老手都栽过跟头。最典型的问题就是“按键抖动…

2026/7/30 4:37:41阅读更多 →
硬件工程师实战指南:电流电压采样电路设计、选型与PCB布局

硬件工程师实战指南:电流电压采样电路设计、选型与PCB布局

1. 项目概述:从“采样”到“感知”的硬件桥梁在嵌入式系统、电源管理、电机驱动乃至智能家居设备中,我们常常需要回答一些最基础的问题:系统现在消耗了多少电流?电池还剩多少电压?电机绕组的实时电流是否正常&#xff…

2026/7/30 4:37:41阅读更多 →
51单片机驱动DS1302实时时钟:从硬件设计到软件驱动的完整指南

51单片机驱动DS1302实时时钟:从硬件设计到软件驱动的完整指南

1. 项目概述:从“51 DS1302”说起,一个经典的时间记录方案如果你玩过51单片机,或者正在学习嵌入式开发,那么“DS1302”这个名字你一定不陌生。这个项目标题“51 DS1302”虽然只有寥寥几个字符,但它背后指向的是一个在单…

2026/7/30 4:37:41阅读更多 →
《城市:天际线2》真实街区规划:从交通流线到公共服务布局

《城市:天际线2》真实街区规划:从交通流线到公共服务布局

如果你是一名《城市:天际线2》的玩家,或者对城市建设模拟游戏感兴趣,那么最近可能被一个词频繁刷屏:"从零打造真实街区"。这听起来像是一个典型的营销口号,但背后其实反映了这款游戏,乃至整个模拟…

2026/7/30 4:35:41阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/29 9:47:45阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/29 7:00:19阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/29 7:58:51阅读更多 →
3分钟解锁iOS应用自由:TrollInstallerX让你的iPhone摆脱安装限制 [特殊字符]

3分钟解锁iOS应用自由:TrollInstallerX让你的iPhone摆脱安装限制 [特殊字符]

3分钟解锁iOS应用自由:TrollInstallerX让你的iPhone摆脱安装限制 🚀 【免费下载链接】TrollInstallerX A TrollStore installer for iOS 14.0 - 16.6.1 项目地址: https://gitcode.com/gh_mirrors/tr/TrollInstallerX 你是否曾经因为iOS系统的严格…

2026/7/30 0:00:58阅读更多 →
[GESP202606 四级] 扫雷

[GESP202606 四级] 扫雷

B4557 [GESP202606 四级] 扫雷 https://www.luogu.com.cn/problem/B4557 中国计算机学会(CCF)2026年6月C四级讲解——扫雷 https://www.bilibili.com/video/BV1MCMg6AEXR/ B4557 [GESP202606 四级] 扫雷 https://www.bilibili.com/video/BV1ZKTj6ZEVh/ 2…

2026/7/30 0:00:58阅读更多 →
Windows驱动存储终极清理工具:DriverStoreExplorer完全指南

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南 【免费下载链接】DriverStoreExplorer Driver Store Explorer 项目地址: https://gitcode.com/gh_mirrors/dr/DriverStoreExplorer 您是否曾因Windows系统盘空间不足而烦恼?是否遇到过设…

2026/7/30 0:00:58阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/30 0:27:26阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/29 4:31:51阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/29 14:26:42阅读更多 →