背了三个月八股还是挂:面试官问的是定义的延长线
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
原帖把面试官的问法总结成「从一个知识点出发顺着往下追,直到你答不上来为止」,下面这几条就是它给出的追问链实例:
volatile 的四层追问
- 你项目里哪个变量加了 volatile,为什么要加?
- 不加会怎么样,具体是什么现象?
- 它和 const 一起用是什么效果?
- 和关中断保护共享变量相比,有什么取舍?什么时候两个都用?
DMA 的四层追问
- DMA 的缓冲区什么时候可以复用?
- 任务还没处理完,DMA 又传来一次数据,怎么办?
- 双缓冲具体怎么设计?
- DMA 传输到一半出错了,怎么恢复?
异常路径类(八股里几乎没有、面试里越来越多)
- 设备升级过程中突然断电,怎么保证还能起来?
- 队列满了,该丢新数据还是丢旧数据?
- 上述这些问题,你项目里对应的代码是哪一段?
《参考解析》
先说结论:八股是散的知识点,面试是连成线的追问。 作者面四场挂四场,回来对答案发现问到的八股基本都会,挂的地方全在「延长线」上——能背出 volatile 的定义(防止编译器优化、每次从内存取)没用,因为面试官一次都没直接问定义。能背出定义和在项目里用过,中间隔着的不是记性,是真的在代码里踩过坑。这个判断在嵌入式、C++、后端所有方向都成立。
volatile:它不是「线程安全」,只是「不许优化掉访问」。 项目里加 volatile 的变量通常有两类:中断里改、主循环里读的标志位(volatile uint8_t flag),以及映射到硬件寄存器的地址(volatile uint32_t *reg)。不加的现象很具体:主循环 while (!flag); 被编译器优化成把 flag 读进寄存器、循环里再也不去内存取,于是中断里置了 1 也永远退不出去——表现为死循环,加个打印就好了(因为打印迫使重新读取),这种「加了打印就好」的 bug 基本可以判定是缺 volatile 或编译器重排。和 const 一起用是 const volatile,语义是「程序不许改、但可能被外部改」,典型就是只读的状态寄存器。和关中断的取舍在于保护粒度:volatile 只保证单次访问不被优化,多字节结构体、读改写序列的原子性它一概不管;关中断(或 taskENTER_CRITICAL)能保证临界区的原子性但有延迟代价。两者都用是常见组合——变量声明成 volatile 保证不被缓存,访问时用临界区保证原子性;涉及外设与内存顺序时还要补 __DMB() 之类的屏障。
DMA 双缓冲:把「传输」和「处理」解耦成两个半区。 单缓冲的问题很直白:DMA 正在往里写的时候 CPU 去读,读到的是半新半旧的数据;CPU 还在处理上一包时 DMA 又来了新包,要么覆盖要么丢。双缓冲(ping-pong)把缓冲区切成 A、B 两块,DMA 写 A 时 CPU 处理 B,写满后交换。三个细节决定它能不能真的工作:一是半满/全满中断要在 DMA 完成中断里只做「切换指针 + 置标志」这种 O(1) 的事,处理放主循环或任务里;二是切换必须原子,通常靠关中断或原子指针交换;三是必须显式处理「CPU 还没处理完就又来一包」——常见策略是计数并丢弃最旧的(实时控制场景)或阻塞写入并反压上游(数据完整优先),这个选择要能说出理由,因为它取决于业务能不能容忍丢帧。DMA 传到一半出错的恢复方式取决于外设:清错误标志、复位 DMA 通道、重新配置传输长度、必要时整包重传;如果有 CRC 就在接收端校验并丢弃半包,绝不把半包当完整数据处理。
升级断电:靠 A/B 双分区 + 完整性校验 + 回滚标记。 核心是解决固件的原子性与可回滚,第一条纪律是绝不在原地覆盖当前正在运行的唯一 APP。典型布局是 Bootloader + APP_A(运行区)+ APP_B(下载区):新固件下载到 B 区,全程不碰 A;下载完做 CRC32 或 SHA-256 校验,配合固件头里的长度、版本、签名;校验通过才把「B 有效」写进独立的元数据区(这一步本身必须原子,常用双 slot 轮流写加序号比较);重启后 Bootloader 读标记决定从哪块启动。下载途中断电则 B 不完整、校验必然失败,自动继续启动 A。再加一层「启动确认」覆盖「固件完整但跑不起来」的情况:B 首次启动后要在限定时间内主动写「确认成功」,否则 Bootloader 计数超限回滚到 A。
队列满了丢新还是丢旧,取决于这条数据晚到还有没有价值。 传感器采样、实时控制这类「旧数据已经过期」的场景丢旧(覆盖最老的一条),保证读到的永远是最新状态;命令、事件、计费这类「每一条都必须处理」的场景丢新并向上游反压,同时在丢弃点打点计数,绝不静默丢弃。第三种选择是分级队列加优先级,把关键消息与非关键消息分开。答题时要先问一句「这个队列承载的是什么语义」,直接给结论会被追问穿。
「项目里哪段代码对应得上」是这套方法的真门槛。 作者的做法是每个知识点拿一张纸写五个问题:这个机制解决什么问题、不用它会怎样、用错了是什么现象、出了 bug 从哪查、我项目里哪段代码对应得上。前四个想清楚、第五个答不出来的,就打开自己的代码去找,找到了对着代码讲一遍;真没有对应场景的,说明项目里没用过,这个知识点直接从「熟悉」降级为「了解」,简历也跟着瘦。这一步做完笔记不是变多而是变少——因为顺着链子一想就发现讲不出项目落点的知识点,本来就不该写在简历上。
异常路径的准备方法可以复用。 把自己项目里每条正常流程旁边的异常情形列出来,一个一个想怎么处理:断电、超时、满队列、部分失败、重入、断线重连。想不出来的地方正是要补的地方,而这类题恰恰是面试官区分「背过」和「想过」的地方——它们没有标准答案,考的是异常路径的处理思路。