当前位置: 首页 > article >正文

图解C语言侵入式双向循环链表与 container_of 宏底层原理

一、侵入式链表在了解侵入式链表之前先回顾之前的非侵入式链表形式如下structNode{intdata;// 数据structNode*next;};在非侵入式链表的这种设计中拿到一个Node顺便也就拿到了它的data。而在 Linux 内核中ListNode本身纯粹只是一个“串联工具”存放在业务结构体中。例如下面代码中的ListNode node嵌入在MyTimer业务结构体中typedefstructMyTimer{uint32_ttimeout;void(*cb)(void);// ... 业务数据 ...structListNodenode;// 链表节点侵入在对象内部}MyTimer;下面将结合具体的代码探讨下侵入式链表。二、如何实现侵入式链表需要注意本文使用双向循环链表实现侵入式链表。在开启内容之前先说明该链表的一般结构梳理下重要节点之间的关系head.nextnode1 head.prevnode3即链表尾节点 head.prev2.1 定义句柄与节点#includestdint.h#includestdbool.h#includestddef.h// 1. 定义节点 (Node)// 仅包含前驱、后继指针不包含数据 datatypedefstructListNode{structListNode*prev;structListNode*next;}ListNode;// 2. 定义链表管理器 (List)typedefstruct{ListNode head;// 可加 size 用以记录数量}List;// 定义句柄typedefList*ListHandle;typedefListNode*ListNodeHandle;使用句柄抽象指针提升可读性便于后期拓展。需要注意的是在上面这段代码中链表管理器List中包含了一个实体头节点head用于表示链表的入口。此时链表结构如下List └──head(哨兵节点)↓ node1 ↔ node2 ↔ node32.2 操作函数2.2.1 初始化头节点的prev和next都指向自己代表空voidList_Init(ListHandle list){list-head.nextlist-head;list-head.prevlist-head;}需要注意此处的list-head是一个实实在在的结构体变量对象本身它占用着 8 字节或 16 字节的内存空间。为正确理解上述代码给出一个简单版本的示例inta5;int*p;pa;上面简单代码中可分为以下3步创建一个 整型变量a实体在内存中占有一定空间可类比list-head创建一个 指针变量p指针可类比list-head.next和list-head.prev将a的地址存入p指向该实体即最后的 头节点的prev和next都指向自己2.2.2 插入节点该函数是我们后续插入操作的核心函数该函数把new_node插入到prev和next两个节点之间// 插入节点插在 prev 和 next 中间staticvoid__List_Add(ListNodeHandle new_node,ListNodeHandle prev,ListNodeHandle next){next-prevnew_node;new_node-nextnext;new_node-prevprev;prev-nextnew_node;}可以参考之前文章中的“先连新后断旧”图示2.2.3 尾插直接调用我们已经写好的插入API函数// 尾部插入通常用于通过 Event 队列voidList_AddTail(ListHandle list,ListNodeHandle new_node){__List_Add(new_node,list-head.prev,list-head);}重点理解下__List_Add(new_node, list-head.prev, list-head);首先明确下__List_Add的作用把new_node插入到prev和next两个节点之间;在尾插法当中待插入的节点new_node的前驱节点为原链表中的尾节点后继节点为头节点head(可以想象这是个环状结构)list为我们的链表管理器其内部存放头节点。在双向循环链表结构中list-head.prev指向尾节点如下图中的node3可视化下尾插法的操作逻辑假设此时的链表结构如下未插入前的链表结构2. 尾部插入将new_node插入在原链表的尾节点node3和 头节点head中 3. 最终链表结构2.2.4 删除节点// 删除节点// 注意: 这体现了侵入式的优势删除不需要 ListHandle只要有 NodeHandle 即可voidList_Del(ListNodeHandle node){node-next-prevnode-prev;node-prev-nextnode-next;// 将 node 的指针清空防止野指针node-nextNULL;node-prevNULL;}图示2.2.5 链表是否为空// 检查链表是否为空boolList_IsEmpty(ListHandle list){returnlist-head.nextlist-head;}检查“头节点的下一个节点是不是头节点本身”注意下空链表的初始形态头节点的next和prev指针都指向它自己。三、核心宏container_of// 计算成员 member 在结构体 type 中的字节偏移量// 标准库 stddef.h 已包含 offsetof这里为了理解列出原理// #define offsetof(type, member) ((size_t)((type *)0)-member)// ★★★ 核心宏 ★★★// ptr: 指向结构体成员的指针即 timer-node// type: 外层结构体的类型即 struct MyTimer// member: 结构体中该成员的名字即 node#definecontainer_of(ptr,type,member)\((type*)((char*)(ptr)-offsetof(type,member)))// 为了方便我们可以封装遍历宏#defineList_ForEach(pos,list)\for(pos(list)-head.next;pos!(list)-head;pospos-next)明确container_of的作用 “已知结构体里某个成员的指针反推出整个结构体的首地址。”结构体首地址 成员的当前内存地址 - 成员在结构体内部的相对偏移量假设存在下面的业务结构体该结构体嵌套了ListNode node;typedefstruct{intid;// 偏移量假设为 0charname[20];// 偏移量假设为 4ListNode node;// 偏移量假设为 16}Student;内存简图如下内存地址 Student 结构体内部0x1000---[intid](占4字节)0x1004---[charname[20]](占20字节)0x1024---[ListNode node](占16字节包含 prev 和 next)此处的0x1000是整个Student结构体的起始地址。而0x1024是内部成员node的起始地址。它们之间相差了 24 个字节的偏移量。当遍历链表时得到的其实是指向node的指针假设地址是0x1024。但需要的是Student这个结构体的地址这样才能读出学生的id和name。怎么从 0x1024 退回到 Student 的起始地址 0x1000 呢此时就需要这个宏#definecontainer_of(ptr,type,member)\((type*)((char*)(ptr)-offsetof(type,member)))ptr指向结构体成员的指针例如 指向 node 的指针type:外层结构体的类型例如Studentmember结构体中该成员的名字例如nodeoffsetof(type, member)通过这个宏计算node这个成员在Student结构体里距离开头有多远假设是 24 个字节。这个值在编译时就确定.(char *)(ptr)将node指针强转成char *字符指针。在C语言中指针加减的单位是它所指向的数据类型的大小。转为char *大小为1字节保证了后面减去的偏移量24均按字节计算的而不是减去 24 个node的大小。((char *)(ptr) - offsetof(type, member))当前地址0x1024减去偏移量24刚好等于结构体的起始首地址0x1000。四、实例演示结构体定义typedefstruct{intid;// 业务数据任务IDconstchar*name;// 业务数据任务名称// 【关键】嵌入链表节点ListNode node;// “挂钩”}MyTask;初始化并挂载List taskList;// 全局任务链表MyTask task1,task2;voidSystem_Init(){// 1. 初始化空链表List_Init(taskList);// 2. 初始化任务数据task1.id1;task1.nameSensor;task2.id2;task2.nameMotor;// 3. 【关键】把衣服的挂钩挂到绳子上List_AddTail(taskList,task1.node);List_AddTail(taskList,task2.node);}遍历通过宏进行遍历pos可以类比于我们之前设定的“起跑线”#defineList_ForEach(pos,list)\for(pos(list)-head.next;pos!(list)-head;pospos-next)voidSystem_Process(){ListNodeHandle pos;MyTask*task;// 遍历链表的宏底层就是一个 for 循环List_ForEach(pos,taskList){// 【关键】从 node 指针还原出 MyTask 指针taskcontainer_of(pos,MyTask,node);printf(Processing Task: %s\n,task-name);}}

相关文章:

图解C语言侵入式双向循环链表与 container_of 宏底层原理

一、侵入式链表 在了解侵入式链表之前,先回顾之前的非侵入式链表,形式如下: struct Node {int data; // 数据struct Node* next; };在非侵入式链表的这种设计中,拿到一个 Node,顺便也就拿到了它的 data。 …...

java从头开始-苍穹外卖-day11-数据统计与展示

营业额统计用户统计订单统计销量排名top10这个其实要多表联查,菜品是在订单详情表,但是这个表没有订单完成状态,因此需要多表连查...

别让Service层“越界”:为何Java中Service层不该直接返回Result对象?

别让Service层“越界”:为何Java中Service层不该直接返回Result对象? 引入:一次代码审查引发的思考 昨天在进行代码审查的时候,我发现同事在 Service 层直接返回了 Result 对象。当时我就指出了这个问题,可同事一脸疑惑…...

基于Spring Boot的校园二手物品置换系统设计与实践

第一章:系统设计目标与需求拆解 在高校倡导绿色低碳理念与学生闲置物品处理需求增长的背景下,基于Spring Boot的校园二手物品置换系统,核心目标是构建“以物换物”的非货币交易平台,解决传统校园二手交易中“价格博弈繁琐、闲置物…...

基于SpringBoot+Vue的旅游信息咨询网站

第一章:网站设计背景与核心定位 在旅游消费升级的趋势下,用户对旅游信息的需求从“基础查询”转向“精准化、个性化、一站式”服务,传统旅游信息平台存在信息碎片化、更新滞后、互动性弱等问题——用户需在多个平台切换查询景点、住宿、交通信…...

大学C语言搜题app推荐,助你从小白变编程大牛

不少自学C语言的同学都碰到过这般困境,看书之际觉着自己懂了,然而一敲代码便两眼一抹黑,碰到报错也不清楚如何解决。实际上,要想切实掌握这门底层语言,仅仅啃书本远远不足够,借助手机上的工具随时开展练习、…...

C语言特点及应用领域介绍,面向过程语言的相关知识

拥有50年历史的老牌编程语言C语言,直至如今在嵌入式开发领域依旧稳稳占据着霸主位置,每年毕业的程序员数量成千上万,然而真正能够把C语言运用到关键之处的却并不多。它具备简单直接的面向过程特性,在资源受到限制的单片机上面&…...

MCP、RAG与AI智能体对比图文笔记:收藏这份入门指南,轻松掌握大模型核心技术方向!

核心概念:各司其职的技术方向当前AI领域最火的三个概念(MCP、RAG、AI智能体),本质上解决的是不同层面的问题,并非互斥竞争关系。以下是它们的定位差异:技术方向核心能力解决的核心问题MCP定义LLM如何使用外…...

技术深度:模型预测控制(MPC)储能控制策略与多目标哈里斯鹰(MOHHO)算法储能容量配置研究

模型预测控制(MPC)储能控制策略 多目标哈里斯鹰(MOHHO)算法储能容量配置 matlab 研究内容:控制策略为双层控制模型,上层储能补偿风电预测误差,下层储能利用MPC平抑风电功率波动。 配置模型嵌入了上述控制策略&#xf…...

Docker 核心知识点

一、Docker 是什么Docker 把应用 依赖 环境一起打包,放到一个轻量、隔离、可移植的容器里,在哪都能跑。二、3 个核心概念1. 镜像(Image)- 只读模板 - 相当于「安装包」「系统盘」- 例:nginx、centos、tomcat2. 容器…...

什么是 SMD 封装?是不是都不带引脚?

SMD Surface Mounted Device中文:表面贴装器件,就是直接贴在 PCB 板表面焊接的元器件,不是从孔里穿过去焊的那种。1. 是不是都不带引脚?不是绝对 “没有引脚”,而是没有长直插引脚。SMD 有两种典型结构:无…...

C++——数组类模板

1.模板参数可以是数值型参数&#xff08;非类型参数&#xff09;模板参数是在编译阶段被处理的单元&#xff0c;所以在编译阶段必须准确无误的唯一确定变量、浮点数、类对象不能作为模板参数示例&#xff1a;使用模板参数计算12...N#include <iostream> #include<stri…...

来晚了,最全openClaw 本地部署安装方式!(Mac 和 windows)

大家好&#xff0c;我是阿陆&#xff01; 最近哥们不是在面试嘛。面试都面到老板面了&#xff0c;结果老板问了一句&#xff0c;你有玩过openClaw嘛&#xff0c;我说没有。好家伙&#xff0c;这一句话一出来当场变脸。 后续不出所料&#xff0c;老板面没有通过。 我心里想着吃一…...

Dying Gasp IC 详解:定义、功能、选型参数与应用场景

引言在通信设备&#xff08;如 GPON ONU、xDSL Modem、工业网关&#xff09;的实际应用中&#xff0c;突然掉电可能导致设备状态丢失、网管无法定位故障等问题。Dying Gasp&#xff08;临终之息&#xff09;技术正是为解决这一痛点而生&#xff0c;而Dying Gasp IC作为该技术的…...

变异检测算法解析:GATK、Samtools、DeepVariant的原理与性能对比

点击 “AladdinEdu&#xff0c;你的AI学习实践工作坊”&#xff0c;注册即送-H卡级别算力&#xff0c;沉浸式云原生集成开发环境&#xff0c;80G大显存多卡并行&#xff0c;按量弹性计费&#xff0c;教育用户更享超低价。 摘要&#xff1a;变异检测是全基因组/全外显子组测序数…...

从对话到协作:深度解析 WebMCP —— 开启浏览器端的 AI 智能体新时代

在 2024 年底&#xff0c;Anthropic 推出了 MCP (Model Context Protocol)&#xff0c;试图为 AI 模型与外部数据源之间构建一条“通用数据总线”。然而&#xff0c;对于广大的前端开发者和 Web 生态来说&#xff0c;传统的 MCP 更多是在后端或桌面端发力。 2025 年初&#xf…...

java基础面试知识点

java基础 1. Java面试核心概念 Java三大特点 &#xff1a;平台无关性、面向对象、内存管理。 平台无关性&#xff1a;通过JVM&#xff08;Java虚拟机&#xff09;实现。源代码编译成字节码&#xff08;.class文件&#xff09;&#xff0c;可在任何安装了相应JVM的操作系统上运行…...

安装虚拟机详细教程!!!

...

【ASP.NET CORE】 11. SignalR

本系列专栏基于杨中科老师的《ASP.NET Core技术内幕与项目 实战》&#xff0c;本人记录梳理的学习笔记&#xff0c;有部分的增补和省略。更全面系统的讲解&#xff0c;请看杨老师的视频课&#xff1a;【.NET教程&#xff0c;.Net Core视频教程&#xff0c;杨中科主讲】。 一、…...

openclaw本地部署实践复盘 openclaw本地部署案例分享 openclaw本地部署亲测记录 openclaw本地部署实践分享 openclaw本地部署经验复盘 openc

在 openclaw本地部署 的实际项目推进中&#xff0c;环境依赖复杂、权限控制难统一以及插件生态缺乏标准化管理&#xff0c;往往成为工程团队普遍面临的技术挑战。围绕这一问题&#xff0c;行业中通常会通过更系统化的架构设计与部署流程来降低不确定性&#xff0c;北京万维速达…...

mac M芯片安装pytorch

用 conda 创建一个 arm64 的 Python 环境 在这个 conda 环境里用 pip 安装 PyTorch 用 MPS 验证是否启用了 Apple GPU 这是因为 PyTorch 官方当前在 macOS 上推荐的包管理方式是 pip&#xff0c;并注明最新稳定版要求 Python 3.10&#xff1b;macOS 安装页也直接给出了 pip3 in…...

灭菌柜集中监控管理平台解决方案

某工厂要求对研发实验室的多个灭菌柜进行集中监控与在线管理&#xff0c;需要监控的数据量包括干燥开始时间/结束时间、灭菌开始时间/结束时间、升温开始时间/结束时间等&#xff0c;便于统计实验结果与设备性能。对此&#xff0c;通过本地部署数据中台&#xff0c;能够实现多个…...

重置root密码!

mountmount -o remount,rw /sysroot&#xff08;这里需要注意空格哦&#xff01;&#xff01;&#xff01;&#xff09;chroot /sysrootpasswd输入新密码&#xff0c;输入的时候没有提示的哦再次输入设置的新密码touch /.autorelableexitexit注意哦&#xff0c;小编改的是root用…...

JavaScript性能优化实战迸礁

JavaScript性能优化实战技术文章大纲 性能优化的核心原则 减少代码执行时间 降低内存占用 优化网络请求 提升用户体验 代码层面的优化 避免全局变量污染&#xff0c;使用模块化或闭包 减少DOM操作&#xff0c;批量更新或使用文档片段 使用事件委托减少事件监听器数量 优化循环结…...

JavaScript性能优化实战枚徽

JavaScript性能优化实战技术文章大纲 性能优化的核心原则 减少代码执行时间 降低内存占用 优化网络请求 提升用户体验 代码层面的优化 避免全局变量污染&#xff0c;使用模块化或闭包 减少DOM操作&#xff0c;批量更新或使用文档片段 使用事件委托减少事件监听器数量 优化循环结…...

第3章 矩阵:系统、变换与结构的表达

底层数学四部曲第四部 线性代数&#xff1a;入门与全领域展开 第3章 矩阵&#xff1a;系统、变换与结构的表达 矩阵的本质&#xff0c;是线性关系的“容器”&#xff0c;是向量变换的“规则”&#xff0c;是复杂系统的“浓缩表达”。 上一章我们掌握了向量——线性世界的基本单…...

Thinkphp和Laravel框架都支持基于小程序的民宿预订系统-web pc 手机端

目录ThinkPHP与Laravel框架实现多端民宿预订系统的方案技术选型与架构设计核心功能模块实现数据库设计要点性能优化策略部署与监控跨端适配方案安全防护措施项目技术支持可定制开发之功能创新亮点源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可…...

《C++实战项目-高并发内存池》8. 最终性能优化与测试

&#x1f4a1;Yupureki:个人主页 ✨个人专栏:《C》 《算法》《Linux系统编程》《高并发内存池》 &#x1f338;Yupureki&#x1f338;的简介: 目录 1. 使用基数树进行优化 2. 性能测试 完整项目链接https://github.com/Yupureki-code/ConcurrentMemoryPool 1. 使用基数树进行…...

网络安全--Windows操作系统

&#xff08;新手小白入门网络安全&#xff0c;学习Windows操作系统基础知识&#xff0c;如有错误&#xff0c;欢迎批评指正&#xff01;&#xff09;一、初识操作系统操作系统&#xff08;Operating System&#xff0c;简称OS&#xff09;是管理和控制计算机硬件与软件资源的计…...

刷招聘软件时的迟疑?AI大模型才是程序员的新底气

刷着招聘APP的你&#xff0c;是否也曾突然陷入迟疑&#xff1f; 屏幕上密密麻麻的“大模型工程师”“AIGC应用开发工程师”岗位&#xff0c;技能要求写得愈发细致具体&#xff0c;从模型微调、Prompt工程到落地部署&#xff0c;条条直指AI领域。反观自己简历上引以为傲的“微服…...