ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

嵌入式开发实战:7大技巧提升代码质量与可靠性

嵌入式开发实战:7大技巧提升代码质量与可靠性 1. 项目概述为什么嵌入式代码质量是生死线干了十几年嵌入式开发从8位单片机玩到现在的多核Cortex-A系列我最大的感触就是嵌入式软件的代码质量直接决定了产品的生死。这绝不是危言耸听。你写的代码最终是要跑在真实的、物理的硬件上的它控制着电机旋转、传感器采样、通信收发。一个隐藏的数组越界可能导致整个控制系统宕机一个没处理好的中断竞争可能让设备在关键时刻“抽风”。这跟在PC上写个崩了就重启的应用完全是两个概念。损失的不只是用户体验更是真金白银的硬件成本、品牌信誉甚至安全责任。所以当看到“提升嵌入式软件代码质量”这个话题时我觉得太有必要好好聊聊了。这不是什么高深莫测的“玄学”而是一系列具体、可执行、能落地的工程实践。很多团队和开发者尤其是从纯软件转过来的朋友容易把PC端或服务器端的一些“坏习惯”带进来比如过度依赖动态内存、忽视实时性、对硬件抽象不足等。今天我就结合自己踩过的无数个坑分享7个经过实战检验、能切实提升你嵌入式代码质量的技巧。无论你是刚入行的新手还是在寻找团队规范的老手希望这些“土方子”能给你带来启发。2. 核心思路从“能跑”到“跑得稳、改得动”在深入具体技巧之前我们先统一思想。提升嵌入式代码质量目标不仅仅是让程序“能跑起来”而是要达到三个层次可靠性Robustness、可维护性Maintainability和可预测性Predictability。可靠性意味着代码在各种边界条件和异常情况下都能正确运行不死机、不跑飞。可维护性意味着三个月后你自己或者同事还能看懂、能修改这段代码而不是面对一团乱麻。可预测性则特指嵌入式系统的实时性你的函数执行时间、中断响应时间应该是可知、可控的而不是“大概”、“可能”。基于这三大目标我总结的7个技巧可以归为三类设计规范类解决结构和可读性问题、资源管理类解决稳定性和效率问题、验证与防御类解决健壮性和可预测性问题。下面我们就一条条拆开细说。2.1 技巧一拥抱静态分析让机器帮你找“低级错误”这是我认为性价比最高的第一步。很多嵌入式开发者特别是习惯了“烧录-看现象-调试”循环的朋友不太重视编译环节之外的静态检查。但事实上编译器如GCC的-Wall -Wextra和专门的静态分析工具如PC-lint, MISRA C检查器甚至Clang Static Analyzer能在代码运行前就揪出大量潜在缺陷。为什么这对嵌入式至关重要因为嵌入式调试手段往往受限。你可能没有完善的在线调试器或者问题在特定时序下才复现。一个未初始化的局部变量、一个可疑的类型转换、一个可能为空的指针解引用在静态分析阶段被标记出来能节省你大量在逻辑分析仪和串口打印间挣扎的时间。实操要点编译器警告即错误在构建系统如Makefile, CMakeLists.txt中为发布构建Release至少添加-Wall -Wextra -Werror。-Werror会把所有警告当作错误处理强制你解决所有警告。这能消除大量“这个警告应该没事吧”的侥幸心理。选择适合的静态分析工具对于安全要求高的行业汽车、医疗MISRA C/C规则几乎是强制性的可以使用Coverity、QAC等商业工具。对于一般工业或消费电子开源的cppcheck是一个很好的起点。把它集成到你的CI/CD流水线中每次提交都自动运行。理解并配置规则不要盲目启用所有检查规则。有些规则可能过于严格或不适合你的项目比如某些关于浮点数的规则在无FPU的芯片上不适用。花时间阅读工具文档根据你的芯片架构、操作系统或无OS和项目需求启用或禁用特定规则。注意静态分析工具也会有误报False Positive。关键在于你需要定期比如每周查看分析报告不是盲目地“消灭”所有告警而是理解每个告警背后的原因。这个过程本身就是一次深刻的代码审查能极大提升你对语言细节和潜在风险的认识。2.2 技巧二制定并强制执行编码规范编码规范不是官僚主义而是团队高效协作的“宪法”。嵌入式代码经常需要多人维护数年没有统一的规范代码很快就会变成“屎山”。规范的核心不在于争论“括号换行还是不换行”而在于定义那些影响代码安全性和可读性的关键约定。嵌入式领域特别需要关注的规范点命名约定全局变量、静态变量、函数、宏、类型……必须有清晰的前缀或后缀区分。例如我习惯用g_前缀表示全局变量s_表示静态变量t前缀表示类型定义如tMotorStatus宏全部大写并用模块名开头如ADC_MAX_CHANNELS。这让你一眼就能看出标识符的作用域和性质。硬件相关命名寄存器、引脚、外设的命名应与芯片手册或硬件原理图保持一致。如果原理图上LED连接在GPIOA_Pin5那么你的代码中对应的宏或变量最好就叫LED_GPIO_PORT,LED_GPIO_PIN而不是light_pin。头文件守卫与包含每个头文件必须有#ifndef守卫防止重复包含。头文件应自包含即它编译所需的所有其他头文件都已包含在内并且不应包含不必要的头文件以减少编译依赖和时间。禁止使用“魔鬼数字”所有魔法数字如延时值1000、数组大小256必须用有意义的const常量或enum枚举替代。这不仅提高可读性更便于后续修改比如你想把缓冲区从256改到512只需改一个地方。如何落地光有文档不行。使用astyle,clang-format等代码格式化工具将规范固化为配置文件如.clang-format。在代码编辑器VS Code, CLion中配置保存时自动格式化并在Git提交前用预提交钩子pre-commit hook强制检查。让工具来保证一致性解放人的精力去关注逻辑本身。2.3 技巧三模块化与硬件抽象层设计这是区分“嵌入式码农”和“嵌入式工程师”的关键。好的嵌入式代码不是一堆直接操作寄存器的while(1)而是有清晰层次结构的。核心思想分层与解耦。最经典的模型是三层硬件抽象层HAL/板级支持包BSP、驱动层、应用层。HAL/BSP这一层直接与芯片外设寄存器打交道但提供统一的、硬件无关的接口。例如提供一个uart_send_byte(uint8_t data)函数内部实现可能是STM32的USART-DR寄存器操作也可能是ESP32的uart_write_bytes。应用层和驱动层不关心具体芯片。驱动层基于HAL实现特定设备如温湿度传感器SHT30、显示屏ILI9341的驱动逻辑。这一层了解设备的具体通信协议I2C、SPI命令集。应用层实现产品业务逻辑调用驱动层提供的简洁API完全不知道当前用的是STM32还是GD32传感器是I2C还是SPI连接。这样做的好处巨大可移植性更换MCU时理论上只需重写或适配HAL层上层代码几乎不用动。可测试性你可以方便地在PC上模拟HAL层对应用层和驱动层进行单元测试而无需硬件。可维护性硬件相关的“脏活”被隔离在底层上层代码干净、清晰。实操心得设计HAL接口时要面向“行为”而非“寄存器”。比如不是提供set_gpio_high(GPIOA, PIN5)而是提供led_on()。后者的接口语义更清晰底层实现可以灵活变化今天用GPIO控制LED明天可能换成PWM调光。2.4 技巧四谨慎而明确地管理内存嵌入式系统的内存尤其是RAM通常非常有限。动态内存分配malloc/free在嵌入式领域是“危险品”而非“必需品”。为什么慎用动态内存碎片化频繁申请释放不同大小的内存块会导致堆内存碎片化最终可能因为找不到足够大的连续空闲块而导致分配失败即使总空闲内存还很多。非确定性malloc的执行时间是不确定的这对于有实时性要求的任务如中断服务程序是致命的。失败风险在资源受限的系统里分配失败是必须处理的严重错误这增加了代码复杂度。推荐做法静态分配为主池化管理为辅全局或静态数组对于生命周期贯穿整个程序的数据结构如系统状态机、通信缓冲区直接在全局或文件作用域静态分配。这是最安全、最可预测的方式。内存池如果确实需要动态创建/销毁对象如通信协议中的报文请实现或使用一个固定块大小的内存池。启动时一次性分配一个大数组作为池然后从中分配和回收固定大小的内存块。这完全避免了碎片化且分配/释放时间是常数。栈使用分析务必使用工具如GCC的-fstack-usage编译选项或通过map文件分析评估每个任务的栈使用情况并留出足够的余量通常25%-50%。栈溢出是嵌入式系统最难调试的问题之一。踩坑实录我曾在一个项目中为了“灵活”而大量使用malloc。设备在连续运行几天后会概率性死机。最后用自定义的malloc包装函数加入日志才发现是内存碎片化导致一个关键的数据包申请不到内存。后来全部改为内存池问题彻底消失。教训就是在嵌入式里确定性比灵活性更重要。2.5 技巧五防御性编程与断言嵌入式系统运行环境复杂会遭遇电压波动、电磁干扰、传感器失效等意外。你的代码不能假设一切完美必须“防着点”。这就是防御性编程。核心手段参数校验所有对外的API函数尤其是HAL层和驱动层的公共接口必须检查输入参数的有效性。例如一个设置PWM占空比的函数应该检查占空比值是否在0-100范围内。状态检查在执行关键操作前检查硬件或模块的状态。比如在通过SPI发送数据前先检查总线是否繁忙在写入Flash前先检查是否已擦除。使用断言Assert断言是用于在开发阶段捕捉编程错误即不应该发生的“不可能”情况的利器。例如在一个已知只会被中断调用的函数里你可以用断言检查是否真的在中断上下文中。嵌入式断言实现技巧 不要直接使用标准库的assert因为它通常依赖文件IO在嵌入式上不可用或不可靠。实现一个自定义的ASSERT(expr)宏。当表达式为假时它可以打印出错的文件名、行号、表达式如果有串口。将错误信息存入非易失性存储器如Flash的特定区域。触发一个不可屏蔽中断NMI或系统软复位或者进入一个安全的死循环同时点亮错误指示灯。这比让程序在未知状态下继续运行要安全得多。发布版本的处理通常在发布版本中断言检查会被编译掉通过定义NDEBUG宏。但一些最核心的安全性检查如指针非空、数组索引边界应该保留并以更优雅的错误处理机制如返回错误码进入安全模式替代简单的程序中止。2.6 技巧六掌握并善用调试与日志系统printf大法好但不能只有printf。一个分级的、低侵入性的日志系统是嵌入式开发的“眼睛”。一个实用的嵌入式日志系统应具备分级输出如 ERROR, WARN, INFO, DEBUG 等级别。通过宏定义可以在编译时选择日志级别发布版本只保留ERROR甚至关闭所有日志。低开销日志函数本身应尽可能高效避免在日志中调用可能引起阻塞或额外内存分配的函数如sprintf浮点数格式化在某些平台很慢。可以考虑使用编译期字符串连接或直接输出原始数据和格式模板。多种输出后端除了串口还应支持输出到环形缓冲区RAM中供调试器实时查看、Flash用于记录设备死机前的最后状态、甚至通过网络发送。包含丰富上下文自动记录文件名、行号、函数名、时间戳如果系统有RTC或定时器、任务名如果用了RTOS。实现示例简化版// 日志级别定义 typedef enum { LOG_LEVEL_ERROR, LOG_LEVEL_WARN, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG } log_level_t; // 当前编译日志级别 #ifndef CURRENT_LOG_LEVEL #define CURRENT_LOG_LEVEL LOG_LEVEL_INFO #endif // 日志宏 #define LOG(level, fmt, ...) do { \ if ((level) CURRENT_LOG_LEVEL) { \ log_output(level, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__); \ } \ } while(0) #define LOG_ERROR(fmt, ...) LOG(LOG_LEVEL_ERROR, fmt, ##__VA_ARGS__) #define LOG_INFO(fmt, ...) LOG(LOG_LEVEL_INFO, fmt, ##__VA_ARGS__) // ... 其他级别 // 实际输出函数需自己实现 void log_output(log_level_t level, const char* file, int line, const char* func, const char* fmt, ...);在你的log_output函数里你可以将格式化的日志字符串发送到串口或者写入一个全局的环形缓冲区。在调试复杂时序问题时将日志写入RAM缓冲区然后用调试器在死机后导出分析比在线打印干扰系统行为要有效得多。2.7 技巧七为关键代码编写单元测试听到“单元测试”很多嵌入式开发者会摇头硬件千变万化怎么测但这里说的主要是针对驱动层和业务逻辑层的测试特别是那些与硬件无关的算法、状态机、协议解析代码。测试什么算法函数如校验和计算、滤波器、坐标转换等。状态机这是嵌入式系统的核心非常适合单元测试。你可以模拟各种事件输入验证状态转换和输出是否正确。数据协议编解码如自定义的通信报文打包/解包函数。如何测使用测试框架对于C语言Unity、CppUTest是不错的选择。它们轻量适合嵌入式环境。模拟Mock硬件依赖这是关键。将你的驱动层代码依赖的HAL函数如i2c_read_reg抽象成函数指针或接口。在PC上测试时链接一个“模拟”的实现这个模拟实现根据测试用例返回预设的数据或模拟硬件错误。在真实硬件上链接真实的HAL实现。利用CI/CD将单元测试集成到持续集成服务器。每次代码提交自动在PC的模拟环境中运行测试套件快速回归确保修改不会破坏已有功能。一个简单的示例假设你有一个函数process_sensor_data(uint16_t raw_adc)它内部调用了HAL的get_calibration_factor()和一个算法函数convert_to_engineering_unit()。你可以这样组织将get_calibration_factor定义为函数指针默认指向真实HAL函数。在测试文件中创建一个模拟的mock_get_calibration_factor函数返回测试需要的标定值。在测试用例中将函数指针指向模拟函数然后调用process_sensor_data并断言其结果。这听起来有些工作量但一旦建立起基础设施它对代码信心的提升是巨大的。你可以在修改代码后快速运行上百个测试用例来验证而不是每次都烧录到板子上手动测试。3. 技巧融合实战一个传感器驱动模块的改造让我们用一个具体的例子把上述多个技巧串联起来。假设我们要为一个I2C温度传感器比如TMP102编写驱动。初始版本问题重重// tmp102.c float read_temp() { i2c_start(); i2c_write_addr(0x48 1); // 魔鬼数字读写位硬编码 uint8_t msb i2c_read_byte(); uint8_t lsb i2c_read_byte(); i2c_stop(); int16_t val (msb 8) | lsb; val 4; // 假设12位精度硬编码移位 return val * 0.0625; // 魔鬼数字灵敏度 }应用改造技巧后的版本// tmp102.h #ifndef TMP102_H #define TMP102_H #include stdint.h #include stdbool.h // 硬件抽象将I2C操作抽象为接口 typedef struct { bool (*write)(uint8_t dev_addr, uint8_t reg_addr, const uint8_t *data, uint16_t len); bool (*read)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *buffer, uint16_t len); } i2c_interface_t; // 设备句柄包含配置和接口 typedef struct { uint8_t i2c_addr; i2c_interface_t *i2c; float last_temp_c; } tmp102_dev_t; // 初始化设备 bool tmp102_init(tmp102_dev_t *dev, uint8_t addr, i2c_interface_t *i2c_if); // 读取温度摄氏度结果存入dev-last_temp_c并通过参数返回 bool tmp102_read_temperature_c(tmp102_dev_t *dev, float *temp_out); #endif // TMP102_H// tmp102.c #include tmp102.h #include log.h // 引入日志模块 // 常量定义消除魔鬼数字 #define TMP102_TEMP_REG_ADDR (0x00) #define TMP102_CONFIG_REG_ADDR (0x01) #define TMP102_TEMP_LSB_SCALE (0.0625f) // 静态断言确保结构体大小等符合预期C11或编译器扩展 _Static_assert(sizeof(tmp102_dev_t) 64, tmp102_dev_t too large); bool tmp102_init(tmp102_dev_t *dev, uint8_t addr, i2c_interface_t *i2c_if) { // 防御性编程参数检查 if (dev NULL || i2c_if NULL || i2c_if-read NULL || i2c_if-write NULL) { LOG_ERROR(TMP102 init failed: invalid parameters.); return false; } if (addr ! 0x48 addr ! 0x49) { // 假设只支持两个地址 LOG_ERROR(TMP102 init failed: invalid I2C address 0x%02X., addr); return false; } dev-i2c_addr addr; dev-i2c i2c_if; dev-last_temp_c 0.0f; // 可选写入配置寄存器进行初始化 uint8_t config[2] {0x60, 0xA0}; // 示例配置12位精度连续转换 if (!dev-i2c-write(dev-i2c_addr, TMP102_CONFIG_REG_ADDR, config, 2)) { LOG_WARN(TMP102 config write may have failed, proceeding anyway.); // 不一定是致命错误可能设备已配置好 } LOG_INFO(TMP102 sensor at addr 0x%02X initialized., addr); return true; } bool tmp102_read_temperature_c(tmp102_dev_t *dev, float *temp_out) { // 输入校验 if (dev NULL || dev-i2c NULL || temp_out NULL) { LOG_ERROR(TMP102 read temp failed: invalid handle or output pointer.); return false; } uint8_t raw_data[2] {0}; // 读取温度寄存器 if (!dev-i2c-read(dev-i2c_addr, TMP102_TEMP_REG_ADDR, raw_data, 2)) { LOG_ERROR(TMP102 I2C read failed at addr 0x%02X., dev-i2c_addr); return false; } // 数据转换注意字节序和符号位处理TMP102数据格式为12位左对齐 int16_t raw_temp ((int16_t)raw_data[0] 8) | raw_data[1]; raw_temp 4; // 右移4位得到12位有符号整数 // 处理负数二进制补码 if (raw_temp 0x800) { // 检查第11位符号位因为我们已经右移了 raw_temp | 0xF000; // 将16位有符号数的高4位符号扩展 } // 现在 raw_temp 是16位有符号整数代表温度值的整数部分以0.0625度为单位 float temperature (float)raw_temp * TMP102_TEMP_LSB_SCALE; dev-last_temp_c temperature; *temp_out temperature; LOG_DEBUG(TMP102 raw: 0x%04X, temp: %.2f C, (uint16_t)((raw_data[0]8)|raw_data[1]), temperature); return true; }改造点分析模块化与HAL通过i2c_interface_t结构体抽象了I2C操作驱动不依赖具体硬件。这极大提高了可测试性和可移植性。防御性编程所有公共API都检查输入参数和内部状态如指针非空、接口函数有效。消除魔鬼数字寄存器地址、灵敏度系数等都定义为有名字的常量。日志集成每个关键步骤和错误都有相应级别的日志输出便于调试和问题追踪。清晰的错误处理函数返回bool表示成功与否调用者可以据此决定下一步操作。数据结构封装使用tmp102_dev_t句柄来管理设备状态避免了全局变量支持多个传感器实例。这个驱动模块现在结构清晰、安全可靠并且可以轻松地在PC上进行单元测试只需实现一个模拟的i2c_interface_t。4. 常见问题与排查技巧实录即使遵循了所有最佳实践实际开发中还是会遇到各种稀奇古怪的问题。下面分享几个我遇到过的典型问题及其排查思路。4.1 问题一系统运行一段时间后死机看门狗复位这是嵌入式系统最常见也最令人头疼的问题之一。排查思路首先确认是软件看门狗复位还是硬件复位。查看MCU的复位状态寄存器如STM32的RCC_CSR。如果是独立看门狗IWDG复位通常是主循环卡死或任务执行超时如果是窗口看门狗WWDG复位可能是某个中断服务程序ISR执行时间过长。检查栈溢出。这是导致不可预测行为的元凶之一。可以通过以下方法在启动文件中用特定模式如0xDEADBEEF填充栈空间。运行一段时间后用调试器查看栈内存如果模式被破坏说明发生了栈溢出。使用GCC的-fstack-usage编译选项生成栈使用报告并与你分配的任务栈大小对比留出足够余量建议30%-50%。检查中断服务程序ISR。ISR是否执行时间过长中断中应只做最紧急的事如读取数据、清除标志然后将耗时处理交给主循环或任务。用示波器或高精度定时器测量ISR的执行时间。是否发生了中断嵌套或优先级反转配置不当的中断优先级可能导致高优先级中断不断打断低优先级中断使后者一直无法完成。ISR中是否调用了不可重入函数或进行了可能导致阻塞的操作如调用了printf可能使用互斥锁、或等待某个在ISR中无法被设置的事件标志。检查内存泄漏或碎片化。如果你使用了动态内存用自定义的分配/释放函数加入计数和日志监控堆的使用情况。检查并发与资源共享。如果使用了RTOS检查任务间通信队列、信号量、互斥锁的使用是否正确。常见的死锁问题会导致相关任务全部挂起。使用RTOS提供的可视化跟踪工具如FreeRTOS的Tracealyzer是分析这类问题的利器。4.2 问题二通信UART/I2C/SPI间歇性失败通信问题往往和时序、电气特性相关。排查清单电气层面电平与上拉用示波器测量通信线路。I2C的SDA/SCL线是否有足够强的上拉电阻信号上升沿是否太缓UART的波特率是否准确起始位/停止位电平是否正确噪声与干扰通信线是否靠近电源或电机等噪声源是否使用了双绞线或屏蔽线地线是否干净、共地良好软件时序层面时序是否符合从设备要求仔细阅读传感器或芯片的数据手册检查你的主控制器产生的时序如I2C的SCL频率、建立/保持时间是否满足从设备的最差情况要求。很多国产MCU的I2C硬件控制器在高速模式下时序可能不标准。延时是否足够在启动、停止、发送ACK后是否留出了足够的延时特别是对于低速器件软件模拟I2C时SCL低电平时间要足够长。中断干扰高优先级的中断是否可能打断一个正在进行的通信时序对于软件模拟的通信协议在关键时序段可以考虑临时关闭中断。协议与数据层面字节序Endianness这是最常见的坑。从设备发来的16位或32位数据是大端序还是小端序你的处理器如何解释务必在数据手册和代码注释中明确。寄存器地址有些设备的寄存器地址是8位有些是16位。有些需要在地址后跟读写位有些不需要。再次核对数据手册。缓冲区溢出你的接收缓冲区是否足够大是否处理了接收溢出的情况4.3 问题三功耗远高于预期对于电池供电的设备功耗是核心指标。排查与优化步骤测量与定位使用电流计或功耗分析仪测量设备在不同工作模式运行、睡眠、深度睡眠下的电流。确定是哪个阶段功耗偏高。检查外设时钟在进入低功耗模式前是否关闭了所有不必要的外设时钟很多MCU的外设如ADC、定时器、通信接口即使不工作只要时钟开启就会消耗可观的静态电流。仔细检查RCC复位与时钟控制相关寄存器。检查GPIO状态未使用的GPIO应配置为模拟输入无上拉下拉或输出低/高根据外部电路决定避免浮空输入引起漏电流。驱动外部器件如LED、传感器的GPIO在器件不工作时应将其设置为不影响功耗的状态比如通过MOS管控制传感器电源的GPIO应拉低以关闭电源。检查唤醒源是否所有可能的唤醒源外部中断、定时器、通信接口等都已被正确处理或禁用一个未被禁用的、不断产生中断的唤醒源会阻止MCU进入最深的睡眠状态。优化软件架构采用“事件驱动”而非“轮询”模式。主循环大部分时间应处于低功耗等待状态如调用__WFI()指令由中断或事件来触发任务执行。避免使用delay_ms()之类的忙等待函数。4.4 问题四代码在优化等级提高后行为异常为了节省Flash和RAM空间我们通常会用较高的优化等级如-O2,-Os编译发布版本。但有时这会导致程序运行出错。根本原因编译器优化可能会移除它认为“无用”的代码、重新排列指令顺序、将变量存储在寄存器中而非内存。如果你的代码有未定义行为如访问未初始化的变量、数据竞争或过于依赖特定的执行顺序优化就可能暴露或放大这些问题。解决方法使用volatile关键字对于会被硬件如状态寄存器、中断服务程序修改的变量必须用volatile声明告诉编译器不要对它进行优化如缓存到寄存器、移除看似冗余的读取。避免未定义行为确保所有变量在使用前都已初始化。避免有符号整数溢出、除零等操作。注意内存屏障在多核或DMA场景下当CPU需要访问一段由DMA或另一个核心写入的内存时可能需要内存屏障指令如__DSB(),__DMB()来确保数据一致性防止CPU的缓存或预取指令读到旧数据。调试技巧当怀疑是优化导致的问题时可以尝试在可疑的代码段前后添加__asm volatile( ::: memory)内联汇编作为一个编译器屏障阻止优化器跨屏障重排读写顺序。将单个源文件用低优化等级-O0编译看问题是否消失以定位问题文件。仔细阅读编译器的警告信息高优化等级下编译器有时能检测出更多的潜在问题。提升嵌入式代码质量是一个持续的过程没有一劳永逸的银弹。它始于意识成于规范固于工具最终融入你和团队的开发习惯。从今天介绍的这7个技巧中挑选一两个最贴合你当前项目痛点的开始实践比如先强制开启-Werror消除所有警告或者为你的下一个新模块设计清晰的硬件抽象层。一点点改进积累下来你就会发现代码的 Bug 变少了调试效率提高了面对需求变更也更从容了。记住高质量的代码不是写给自己看的是写给六个月后那个可能忘了所有细节但不得不修改它的自己或同事看的。
返回列表