1. 函数调用栈
main函数中调用foo, foo中调用bar.
遵循后进先出,栈向下生长,RBP指向当前栈帧的底部(高地址),RBP会随着函数栈的切换变化
2. 野指针/悬垂的成因与预防
空指针:指针值明确等于NULL;
野指针:指针存随机垃圾地址;
悬垂指针:指针存曾经有效、但对象已经销毁的旧地址。
2.1 野指针
成因:未初始化
预防:指针定义时立刻初始化,不知道指向哪里就赋值NULL
2.2 悬垂指针
成因:指针本身保存曾经合法的地址,但是这个地址对应的内存对象已经被销毁(生命周期结束)
例如:
场景 1:返回局部栈变量的地址
场景 2:free()堆内存之后,指针没有置 NULL
场景 3:结构体 / 数组对象销毁,外部还保存它的成员地址
预防:
- 禁止返回局部栈变量的地址。需要返回,用 malloc 堆或者 static 变量。
free(p)之后立刻手动赋值p = NULL;,把悬垂转为安全空指针
3. 串口丢失数据情况
问题本质:生产速度 > 消费速度,数据溢出丢失(数据过载)
- 生产者:ISR 中断,硬件源源不断产生数据(串口收到字节、DMA 收到数据),产生数据很快。
- 消费者:main 主循环,做协议解析、运算、打印,处理慢。
如果没有 RingBuffer:ISR 里面直接做解析处理。一旦处理慢,新的数据又来了,就会覆盖寄存器,丢失数据,还会拉长 ISR 执行时间,破坏系统实时性。
原因:中断来不及处理 → 中断丢失 / 覆盖(硬件层面)
UART 收到一个字节,硬件把字节放到 DR 寄存器,同时触发 RXNE 中断。 如果你 ISR 执行太慢,还没读完 DR,下一个字节又来了,新字节直接覆盖 DR 寄存器旧字节 →硬件丢字节。
解决思路:ISR 只干一件事:把字节搬走,存到内存缓冲区,立刻退出 ISR。硬件寄存器尽快腾空,不让新来的数据把它覆盖。这就是 RingBuffer 干的活。
解决方案:
DMA 硬件缓冲区 + 软件 RingBuffer(工程最常用)
- DMA 把数据填到硬件接收 buffer;半满 / 满触发一次中断。
- ISR 中把 DMA 这一批字节批量拷贝进软件 RingBuffer,立刻退出 ISR。
- main 循环从 RingBuffer 取出字节做协议解析业务。
- DMA:减少中断次数,免除 CPU 单字节搬运。
- RingBuffer:做流量削峰,抵抗瞬时突发数据。
就算 DMA,依然需要软件环形缓冲区做二级缓冲。DMA 的硬件缓冲区一般不大,不能当长期缓存。
4. 链表相关理解
每个节点是使用malloc分配的内存并获得其指针,通常是使用这个指针来进行操作。
链表头指针通常会遇到临界条件,需要单独考虑。
节点的构成:vlaue + 指向下块节点的指针。