嵌入式开发中必须使用指针,因为需直接通过地址访问寄存器、dma缓冲区等硬件资源;volatile修饰确保编译器不优化关键读写;裸机下指针是绕过驱动层操作外设的唯一手段。

嵌入式里为什么非用指针不可
因为寄存器、DMA缓冲区、内存映射外设这些硬件资源,必须靠地址直接访问——编译器不会替你把GPIOA_MODER自动翻译成0x40020000,你得亲手拿指针去点名。
裸机开发中,volatile uint32_t* const reg = (uint32_t*)0x40020000; 这种写法不是炫技,是刚需:没有指针,你就没法绕过驱动层直接读写硬件;没有volatile,编译器可能把两次寄存器写入优化成一次,外设就收不到指令。
- 串口接收中断里,常把DMA目标地址设为指针变量,避免拷贝数据
- RTOS任务栈起始地址由指针传入,栈空间必须按字节对齐,指针算术比数组下标更可控
- Flash模拟EEPROM时,用指针遍历扇区,跳过已擦除区域,比查表快得多
裸指针在资源受限设备上的坑
嵌入式设备没操作系统兜底,new和delete一用就容易碎片化。某款STM32F4项目曾因频繁new小对象,运行72小时后malloc失败,最后改成内存池+指针偏移管理。
常见错误现象:
-
nullptr解引用:未检查指针有效性就*p = 1,MCU直接硬复位 - 悬垂指针:DMA传输完,缓冲区指针没置空,后续误用导致覆盖关键数据
- 类型不匹配:用
uint8_t*读取16位ADC结果,大小端错乱,值全偏移
建议做法:
- 静态分配优先:全局缓冲区用
static uint8_t rx_buf[256]; uint8_t* rx_ptr = rx_buf; - 指针生命周期绑定到外设句柄:比如
struct uart_dev { uint8_t* tx_buf; size_t tx_len; };,释放句柄时自然失效 - 禁止跨作用域返回局部数组地址:
return &local_arr[0]是致命错误
智能指针在嵌入式里能用吗
标准std::shared_ptr和std::unique_ptr基本不用——它们依赖std::mutex和动态内存分配,在无MMU的MCU上既没原子锁也没堆管理器。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
但你可以手写轻量级RAII封装:
- 用
struct scoped_dma_buffer { uint32_t* ptr; scoped_dma_buffer(uint32_t* p) : ptr(p) {} ~scoped_dma_buffer() { if (ptr) dma_free(ptr); } }; - 把
std::unique_ptr删掉异常和分配器依赖,只留移动语义,配合静态内存池使用 - 注意:C++17的
std::optional和std::variant在部分ARM GCC 10+工具链中已支持,但std::shared_ptr仍需慎用
真正实用的是“伪智能指针”:带校验的指针包装类,比如checked_ptr<t></t>在构造时记录内存池范围,operator*前检查地址是否越界。
数组指针和指针数组别再混了
嵌入式代码里写错这两者,轻则逻辑错乱,重则踩到中断向量表——因为它们的sizeof和步进单位差4倍以上。
典型场景:
- 处理图像帧:二维数组
uint16_t frame[480][640],用uint16_t (*p)[640] = frame;,p++跳整行(1280字节) - 中断向量表:声明
void (*isr_table[16])(void),这是指针数组,每个元素是函数指针,isr_table[3]()调用第4个中断服务程序 - 错误写法:
uint16_t* p[640]—— 这是640个指针的数组,不是指向640元素数组的指针
判断技巧:从变量名开始,按运算符优先级往外读。int (*p)[5] → “p 是一个指针,指向长度为5的int数组”;int* p[5] → “p 是一个数组,含5个int指针”。
最易被忽略的是指针类型与总线宽度的隐式耦合:32位MCU上uint32_t*和uint16_t*解引用时,CPU实际发出的读写指令宽度不同,影响时序和外设响应。这点在SPI Flash驱动里踩过坑的人,都记得加__attribute__((packed))或强制类型转换。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










