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

嵌入式C++工程实践——第13篇:第一次重构 —— enum class取代宏,类型安全的开始

嵌入式C工程实践——第13篇第一次重构 —— enum class取代宏类型安全的开始仓库已经开源仍然在持续建设中喜欢的话点个⭐相关的链接如下https://github.com/Awesome-Embedded-Learning-Studio/Tutorial_AwesomeModernCPP静态网页直接阅览https://awesome-embedded-learning-studio.github.io/Tutorial_AwesomeModernCPP/PS: 嵌入式Linux的部分笔者还在研究驱动如何讲是合适的可能imx-forge的相关内容不得不托更几天确保质量准确这里特别给所有关心相关内容的朋友说明一下承接上一篇C宏方案能跑但有问题——类型不安全、端口和时钟没有强制关联、代码无法复用。现在我们迈出C重构的第一步用enum class替代宏定义。为什么要替换宏上一篇的C宏LED驱动看起来还不错——宏定义集中了硬件参数函数封装了操作逻辑。但问题出在宏本身#define LED_PORT GPIOC展开后就是((GPIO_TypeDef *)0x40011000UL)——一个裸的整数地址。编译器不会帮你检查这个值是否合理也不会阻止你把一个随机的整数赋给期望GPIO_TypeDef*的函数。enum class是C11引入的特性它把我们从宏的海洋带入了类型安全的世界。用enum class重新定义GPIO参数后编译器会在编译时就帮你检查类型——你不可能把一个模式值传给期望上下拉参数的函数也不可能把端口A的地址传给期望端口C的操作。GpioPort枚举——类型安全的端口地址在device/gpio/gpio.hpp中端口是这样定义的enumclassGpioPort:uintptr_t{AGPIOA_BASE,// 0x40010800BGPIOB_BASE,// 0x40010C00CGPIOC_BASE,// 0x40011000DGPIOD_BASE,// 0x40011400EGPIOE_BASE,// 0x40011800};这里有几个设计决策需要解释。首先为什么底层类型是uintptr_t而不是uint32_t因为枚举值是内存地址uintptr_t是C标准定义的足以容纳指针的无符号整数类型——在32位ARM上它就是uint32_t但在64位平台上它会自动变成64位。用uintptr_t比uint32_t更能表达这是一个地址的语义也使代码在理论上有更好的可移植性。其次为什么用GPIOA_BASE而不是GPIOAGPIOA是CMSIS定义的指针常量——它已经被 cast 成了GPIO_TypeDef*类型。而枚举值必须是整数常量表达式不能是指针。GPIOA_BASE是纯整数地址可以作为枚举值。后面我们会看到constexpr native_port()如何把这个整数地址转回GPIO_TypeDef*指针。最后为什么用enum class而不是普通enum原因是作用域隔离。普通enum的成员会泄漏到外部作用域——如果你定义了两个普通枚举enum Color { Red, Green }和enum Pull { PullUp, PullDown }编译器不一定报错但如果你在两个枚举中都定义了同名的成员就会产生冲突。enum class的成员必须通过GpioPort::A这种完整限定名来访问不同的enum class之间绝不会冲突。Mode、PullPush、Speed——枚举化的HAL常量GPIO的三个核心配置参数也被重新定义为enum classenumclassMode:uint32_t{InputGPIO_MODE_INPUT,OutputPPGPIO_MODE_OUTPUT_PP,OutputODGPIO_MODE_OUTPUT_OD,AfPPGPIO_MODE_AF_PP,AfODGPIO_MODE_AF_OD,AnalogGPIO_MODE_ANALOG,ItRisingGPIO_MODE_IT_RISING,ItFallingGPIO_MODE_IT_FALLING,// ... 更多模式};enumclassPullPush:uint32_t{NoPullGPIO_NOPULL,PullUpGPIO_PULLUP,PullDownGPIO_PULLDOWN,};enumclassSpeed:uint32_t{LowGPIO_SPEED_FREQ_LOW,MediumGPIO_SPEED_FREQ_MEDIUM,HighGPIO_SPEED_FREQ_HIGH,};这里有一个贯穿始终的设计原则底层类型uint32_t与HAL库的字段类型一一对应。GPIO_InitTypeDef的Mode、Pull、Speed字段都是uint32_t类型我们的枚举底层类型也用uint32_t这样static_cast提取底层值时是零开销的——没有任何类型转换的开销编译器只是把存储的整数值当作另一个类型来使用。现在想象一下如果你写代码时不小心把模式值传给了期望上下拉参数的函数// C宏风格编译通过运行时LED行为异常g.PullGPIO_MODE_OUTPUT_PP;// 错了但编译器不会警告// enum class风格编译直接报错setup(Mode::OutputPP,Mode::OutputPP);// 编译错误第二个参数期望PullPush类型enum class的类型安全在这里体现得淋漓尽致Mode和PullPush是完全不同的类型编译器会阻止你混用它们。而在C宏的世界里GPIO_MODE_OUTPUT_PP和GPIO_PULLUP都是uint32_t的宏编译器看不到任何区别。static_cast——从枚举到HAL的桥梁enum class的值不能隐式转换为整数——这是安全特性但HAL库只认uint32_t。所以我们用static_cast做显式转换voidsetup(Mode gpio_mode,PullPush pull_pushPullPush::NoPull,Speed speedSpeed::High){GPIO_InitTypeDef init_types{};init_types.PinPIN;init_types.Modestatic_castuint32_t(gpio_mode);init_types.Pullstatic_castuint32_t(pull_push);init_types.Speedstatic_castuint32_t(speed);HAL_GPIO_Init(native_port(),init_types);}static_castuint32_t(gpio_mode)在编译时解析——如果gpio_mode是Mode::OutputPP底层值0x01那么static_cast的结果就是0x01。这个过程不产生任何运行时代码它就是从枚举中取出底层存储的整数。对比C风格的隐式转换// C风格宏展开后是裸整数类型信息完全丢失g.ModeGPIO_MODE_OUTPUT_PP;// 等价于 g.Mode 0x01;// C风格枚举类型在编译时验证然后零开销地提取底层值init_types.Modestatic_castuint32_t(gpio_mode);// gpio_mode必须是Mode类型不过static_cast的这种零开销安全性有一个值得注意的边界。虽然它不会在运行时检查值的合法性——如果你在enum class Mode中添加了一个新的枚举值但忘记在HAL库对应的宏中定义它static_cast不会报错它只是忠实地把底层值传过去。这就是为什么我们的枚举值必须与HAL宏一一对应这份对应关系需要开发者自己维护。ActiveLevel——应用层概念的枚举enumclassActiveLevel{Low,High};注意这个枚举没有指定底层类型——它的默认底层类型是int。这是有意为之的。Low和High不是HAL宏的值而是我们自己定义的应用层概念——它表达的是这个LED电路是低电平有效还是高电平有效。这个概念跟HAL库完全无关是LED驱动层面的抽象。enum class的默认底层类型是int在C中这没什么问题——嵌入式环境也完全支持int类型。如果你想要更精确地控制大小可以显式指定enum class ActiveLevel : uint8_t但对只有两个值的枚举来说这点存储优化完全不值得增加代码复杂度。State枚举——封装引脚状态enumclassState{SetGPIO_PIN_SET,UnSetGPIO_PIN_RESET};GPIO_PIN_SET的值是1GPIO_PIN_RESET的值是0。Set表示引脚为高电平UnSet表示引脚为低电平。这个枚举把HAL的GPIO_PinState类型包装成了类型安全的版本——跟前面的Mode和PullPush一样你不可能把State::Set传给期望Mode参数的函数。C23的 std::to_underlying —— 未来的优雅替代我们当前代码中使用static_castuint32_t(value)从枚举提取底层值。C23引入了一个更优雅的工具函数std::to_underlying(enum_value)它是static_caststd::underlying_type_tE(e)的简写// 当前写法C11兼容init_types.Modestatic_castuint32_t(gpio_mode);// C23的std::to_underlying写法未来目标init_types.Modestd::to_underlying(gpio_mode);std::to_underlying更简洁也不需要你手动写出底层类型——编译器会自动推导。但我们的代码目前没有使用它原因是arm-none-eabi-g搭配newlib-nano标准库可能还没有完整支持C23的utility头文件。static_cast是C11就有的特性兼容性更好。当你确认你的工具链支持C23的完整标准库后可以安全地把所有static_castuint32_t(xxx)替换为std::to_underlying(xxx)。这是一个纯机械式的替换不涉及任何逻辑变更。重构到这里的效果经过enum class重构后我们的GPIO配置代码已经比纯C宏版本安全了很多。端口只能是GpioPort::A到GpioPort::E之一不可能传入无效地址。模式只能是Mode枚举的成员不可能传入随机的uint32_t。而且Mode和PullPush是不同的类型编译器会阻止你混用。但还有问题没有解决端口和引脚仍然是运行时传递的参数不是编译时绑定的常量。时钟使能仍然是手动的——你得记得调用__HAL_RCC_GPIOx_CLK_ENABLE()。这些问题要等到引入模板才能解决——那就是下一篇的主题了。⚠️ 注意虽然enum class解决了类型安全问题但它也带来了一个新问题——不能隐式转换为整数。每次传递给HAL API都需要static_castuint32_t(value)。如果你觉得这个转换写起来繁琐C23提供了std::to_underlying(enum_value)作为更优雅的替代——但由于我们的arm-none-eabi工具链可能不支持完整的C23标准库所以暂时使用static_cast是最稳妥的选择。我们回头看这一篇我们做了三件事用enum class替代#define获得类型安全用static_cast在枚举和HAL之间做零开销转换用ActiveLevel表达应用层概念。这些都是为后续的模板重构做准备——模板参数需要编译时常量而enum class的成员恰好就是编译时常量表达式。下一篇我们将引入C模板的核心武器——非类型模板参数NTTP把端口和引脚从运行时参数变成编译时类型的一部分。这是整个系列中最重要的重构步骤。相关阅读入门 · 环境搭建 · 00 · Qt6 安装踩坑指南 - 相似度 100%现代Qt开发——0.1——如何在IDE中配置Qt环境 - 相似度 100%现代Qt开发教程新手篇1.3——字符串与编码 - 相似度 100%

相关文章:

嵌入式C++工程实践——第13篇:第一次重构 —— enum class取代宏,类型安全的开始

嵌入式C工程实践——第13篇:第一次重构 —— enum class取代宏,类型安全的开始 仓库已经开源!仍然在持续建设中,喜欢的话点个⭐!相关的链接如下: https://github.com/Awesome-Embedded-Learning-Studio/Tut…...

Qwen2.5-72B-Instruct-GPTQ-Int4开源镜像实操:资源用量监控与成本核算模板

Qwen2.5-72B-Instruct-GPTQ-Int4开源镜像实操:资源用量监控与成本核算模板 1. 模型简介与部署准备 Qwen2.5-72B-Instruct-GPTQ-Int4是通义千问大模型系列的最新版本,经过4-bit GPTQ量化处理后的72B参数指令调优模型。这个版本在多个方面都有显著提升&a…...

零基础玩转李慕婉AI绘画:手把手教你用Z-Turbo镜像生成仙逆同人图

零基础玩转李慕婉AI绘画:手把手教你用Z-Turbo镜像生成仙逆同人图 1. 为什么你需要试试这个镜像?从想法到画面的距离,可能只有几秒钟 如果你和我一样,是《仙逆》的读者或观众,心里一定有过这样的念头:要是…...

DownKyi视频下载工具:从网络限制到自由收藏的完整解决方案

DownKyi视频下载工具:从网络限制到自由收藏的完整解决方案 【免费下载链接】downkyi 哔哩下载姬downkyi,哔哩哔哩网站视频下载工具,支持批量下载,支持8K、HDR、杜比视界,提供工具箱(音视频提取、去水印等&a…...

微信聊天记录解密:三步找回你的数字记忆宝藏

微信聊天记录解密:三步找回你的数字记忆宝藏 【免费下载链接】WechatDecrypt 微信消息解密工具 项目地址: https://gitcode.com/gh_mirrors/we/WechatDecrypt 你是否曾因手机损坏而焦虑那些珍贵的微信聊天记录?那些包含工作重要信息、家庭温馨对话…...

Rust的闭包中的互操作性

Rust的闭包以其灵活性和高效性闻名,但在与其他语言或系统交互时,互操作性成为关键挑战。闭包作为一等公民,既能捕获环境变量,又能作为参数传递,但在跨语言调用或与C接口交互时,其实现机制可能引发兼容性问题…...

06. Python函数基础:从基础封装到高阶应用与算法实战

温故知新:从字符到函数的进阶之路在上一节的学习旅程中,我们深入探索了Python中字符串的奇妙世界。我们不仅掌握了字符串的索引与切片操作,学会了如何像手术刀一样精准地提取数据,还熟悉了各种实用的内置方法,如大小写…...

Qwen2.5-72B-GPTQ-Int4惊艳效果:多轮数学证明生成+中间步骤可追溯展示

Qwen2.5-72B-GPTQ-Int4惊艳效果:多轮数学证明生成中间步骤可追溯展示 1. 引言:当大模型遇上数学推理 如果你尝试过让AI帮你解决数学问题,可能会遇到这样的困扰:它要么直接给出一个最终答案,让你摸不着头脑&#xff1…...

ClearerVoice-Studio语音处理全流程:电话/直播/会议多采样率适配方案

ClearerVoice-Studio语音处理全流程:电话/直播/会议多采样率适配方案 1. 开箱即用的语音处理利器 你是否遇到过这样的困扰:重要的会议录音背景噪音太大,直播时环境嘈杂影响效果,或者需要从多人对话中提取特定人物的声音&#xf…...

OpenClaw vs Hermes Agent:2026 年最强 AIAgent 框架深度对比,谁更适合你?

OpenClaw vs Hermes Agent:2026 年最强 AI Agent 框架深度对比 摘要:随着 AI Agent 技术的爆发式增长,OpenClaw 和 Hermes Agent 成为了 2026 年最受关注的两大开源框架。本文将从架构设计、技能系统、记忆机制、安全性、适用场景等维度进行深度对比,帮助你选择最适合的 AI…...

终极指南:3分钟免费重置JetBrains IDE试用期,轻松突破30天限制 [特殊字符]

终极指南:3分钟免费重置JetBrains IDE试用期,轻松突破30天限制 🚀 【免费下载链接】ide-eval-resetter 项目地址: https://gitcode.com/gh_mirrors/id/ide-eval-resetter 还在为JetBrains IDE试用期到期而烦恼吗?ide-eval…...

2006-2023年各省工业机器人安装密度数据

2006-2023年各省工业机器人安装密度数据 1、时间:2006-2023年 2、来源:IFR国际机器人联合会 3、指标:年份、省份代码、省份、所属地域、工业机器人安装密度_台 4、范围:31省 5、说明:根据IRF联盟公布的中国各行业…...

分布式系统最佳实践

分布式系统最佳实践:构建高可用的现代架构 在当今数字化时代,分布式系统已成为支撑大规模应用的核心技术。无论是电商平台、金融系统还是云计算服务,分布式架构的高可用性、可扩展性和容错能力都是关键需求。分布式系统的复杂性也带来了诸多…...

记忆与上下文管理:短期会话、长期记忆与检索边界怎么设计(含分层策略与实现要点)

专栏第 9 篇:解决 Agent 项目中“记不住、记太多、记错了”的三大问题。一、问题描述:为什么记忆系统总在“要么失忆,要么混乱” 随着 Agent 使用时长增加,典型问题会出现: 对话一长就丢上下文;什么都往长期…...

突破《原神》60FPS限制:从卡顿到流畅的完整实战指南

突破《原神》60FPS限制:从卡顿到流畅的完整实战指南 【免费下载链接】genshin-fps-unlock unlocks the 60 fps cap 项目地址: https://gitcode.com/gh_mirrors/ge/genshin-fps-unlock 你是否曾因《原神》的60FPS限制而感到束手无策?当你拥有高性能…...

质检主管的自述:职业天花板的叹息,难道只是永远的低声下气?

技术背景介绍:AI智能体视觉检测系统(TVA,全称为“Transformer-based Vision Agent”),即基于Transformer架构以及“因式智能体”创新理论的高精度视觉智能体,并非传统机器视觉软件或者早期AI视觉技术&#…...

Meshlab新手必看:STL模型中心化与Poisson采样点云化完整流程

Meshlab新手必看:STL模型中心化与Poisson采样点云化完整流程 刚接触三维建模时,我总被各种专业软件的操作界面吓退——直到遇见Meshlab这款开源神器。它就像一位耐心的导师,用简洁的界面承载着强大的三维处理能力。特别是在处理3D扫描获得的S…...

SBTI 人格测试源码分析:一个完整的纯前端心理测试项目(附源码)

SBTI 人格测试源码分析:一个完整的纯前端心理测试项目项目来源:52IIS 在线工具箱作者整理开源 开源地址:https://github.com/52IIS/52IIS_Tools/tree/main/public/SBTI 52IIS部署:https://www.52iis.com/SBTI/index.html最近看到一…...

《SRE:Google 运维解密》读书笔记06: 少琐事 - SRE的隐形敌人

作者: andylin02 学习章节:第5章 减少琐事(Eliminating Toil) 关键词:琐事、Toil、自动化、50%规则、工程工作、职业发展 一、引言:琐事——SRE的隐形敌人 在日常运维工作中,总有一些反复出现、消耗大量精…...

新手蓝队入门:用D盾和日志分析实战Web应急响应(知攻善防靶场复盘)

新手蓝队入门:用D盾和日志分析实战Web应急响应 刚接触网络安全运营的新人,面对突发安全事件时往往手足无措。本文将以知攻善防实验室的Web靶场为例,带你从零开始构建应急响应思维框架。不同于简单的题解记录,我们将重点拆解工具使…...

5分钟掌握Blender 3MF插件:免费开源3D打印格式完美解决方案

5分钟掌握Blender 3MF插件:免费开源3D打印格式完美解决方案 【免费下载链接】Blender3mfFormat Blender add-on to import/export 3MF files 项目地址: https://gitcode.com/gh_mirrors/bl/Blender3mfFormat 想要在Blender中轻松处理3D打印文件吗&#xff1f…...

新手必看:用Wireshark从BUUCTF MISC流量包中找Flag的保姆级实战(附HTTP过滤技巧)

零基础通关BUUCTF流量分析:Wireshark实战技巧与Flag挖掘指南 第一次打开Wireshark时,满屏跳动的数据包就像天书般令人望而生畏。作为CTF竞赛中最常见的题型之一,流量分析看似门槛极高,实则掌握几个核心技巧就能快速入门。本文将带…...

计算机网络深度解析:篡改(Tampering)是否属于主动攻击?——从安全模型到实战攻防的万字全解

🌐 计算机网络深度解析:篡改(Tampering)是否属于主动攻击?——从安全模型到实战攻防的万字全解 作者:培风图南以星河揽胜 发布于:2026年4月12日 📌 核心摘要 本文系统性地解答了“在…...

如何快速提升研究效率:Zotero-GPT完整使用秘籍

如何快速提升研究效率:Zotero-GPT完整使用秘籍 【免费下载链接】zotero-gpt GPT Meet Zotero. 项目地址: https://gitcode.com/gh_mirrors/zo/zotero-gpt Zotero-GPT是一个革命性的AI文献助手,它将GPT智能能力无缝集成到Zotero文献管理软件中&…...

花了两天,让Trae,给我用魔珐星云数字人写了个项目!

文章目录注意代码视频演示项目背景与痛点2.1 行业与社会背景2.2 现有场景的核心痛点分析2.3 项目切入价值总结产品核心功能3.1 语音驱动的自然交互数字人3.2 基于位置感知的智能导航与指路服务(行)3.3 智能科室引导与就医辅助(医)…...

你的浏览器也能轻松聊微信:wechat-need-web插件完全指南

你的浏览器也能轻松聊微信:wechat-need-web插件完全指南 【免费下载链接】wechat-need-web 让微信网页版可用 / Allow the use of WeChat via webpage access 项目地址: https://gitcode.com/gh_mirrors/we/wechat-need-web 还在为无法在浏览器中使用微信网页…...

5分钟解锁微信网页版:wechat-need-web插件完整使用指南

5分钟解锁微信网页版:wechat-need-web插件完整使用指南 【免费下载链接】wechat-need-web 让微信网页版可用 / Allow the use of WeChat via webpage access 项目地址: https://gitcode.com/gh_mirrors/we/wechat-need-web 还在为无法在浏览器中使用微信而烦…...

2026-04-16 全国各地响应最快的 BT Tracker 服务器(移动版)

数据来源:https://bt.me88.top 序号Tracker 服务器地域网络响应(毫秒)1http://60.249.37.20:80/announce广东惠州移动382http://211.75.205.187:6969/announce广东深圳移动403udp://107.189.7.165:6969/announce北京移动1234http://107.189.2.131:1337/announce北京…...

Audio Pixel Studio实操手册:TTS生成语音+UVR5分离伴奏再合成新曲目

Audio Pixel Studio实操手册:TTS生成语音UVR5分离伴奏再合成新曲目 1. 引言:你的极简音频创作工作站 想给视频配上专业旁白,但自己录音效果总是不理想?手头有一段喜欢的音乐,想提取出纯净的伴奏来重新填词演唱&#…...

DeerFlow进阶教程:集成MCP服务,扩展你的AI助理工具箱

DeerFlow进阶教程:集成MCP服务,扩展你的AI助理工具箱 认识一下DeerFlow,你的个人深度研究助理。它已经内置了强大的网络搜索、Python代码执行和报告生成能力。但今天,我们要让它变得更强大——通过集成MCP(Model Cont…...