嵌入式秋招挂了十几场,复盘发现反复考的就这四类题
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 指针作为返回值,为什么会出问题?
- 结构体成员换个顺序,大小变不变?
- 变量分别放在哪个段?上电后谁初始化?
- malloc 出来的内存在任务里没释放,任务删了会怎样?
- DMA 传完数据进中断、中断里置标志位、任务里取数据处理——任务还没处理完,下一次 DMA 又写进来了呢?
- 谁写谁读、临界区该包哪几行、中断为什么不能阻塞、DMA 缓冲什么时候能复用?
- I2C 时钟拉伸是什么场景?
- SPI 四种模式怎么区分?你的设备用哪种?
- UART 接收用中断还是 DMA,为什么?
- 总线上挂多个设备,地址怎么管理?
- 链接脚本改了哪几段?为什么改完程序起不来?
- 向量表在哪?复位后第一条指令是什么?data 段谁搬、bss 谁清零?HardFault 了怎么定位现场?
《参考解析》
这四类题挑人的逻辑是一样的:简历上不单独写,但哪个项目都绕不开。 面试官不是想考你有没有背过,而是想验证项目里的代码是不是你自己写的——所以每一类都要落到「我项目里哪段代码对应这个知识点」。
指针作返回值挂人的地方几乎都是返回了栈上的地址:函数里开一个局部数组返回指针,函数一返回栈帧就作废,调用方拿到的是悬垂指针,读出来的数据取决于后面谁踩了这块栈——现场表现是「偶尔对、偶尔乱码、加个打印就正常了」,比直接崩更难查。返回字符串字面量、静态区变量、或者 malloc 的堆指针没问题,但堆内存的释放责任要写清楚。嵌入式里更隐蔽的一层是 const 全局数组放在 Flash,返回它的指针完全合法,可一旦调用方想写就会进 HardFault。
结构体换成员顺序,大小会变,因为对齐:每个成员的偏移必须是它自身对齐数的整数倍,结构体总大小要补齐到最大对齐数的整数倍。char + int + char 在 4 字节对齐下是 12 字节,把 int 挪到前面就压成 8 字节。协议解析时用 __attribute__((packed)) 省空间,代价是非对齐访问——Cortex-M0/M0+ 不支持非对齐,直接 UsageFault;M3/M4 支持但会多花总线周期,涉及 DMA 时还要注意编译器不保证 packed 成员的原子访问。
变量在哪个段、谁初始化是启动流程的入口问题。.text 是代码,const 全局在 .rodata(烧在 Flash 里,不占 RAM);已初始化的全局和静态变量在 .data,初值存在 Flash,上电后由启动代码搬到 RAM;未初始化的进 .bss,启动代码整段清零;局部变量在栈上,malloc 在堆上。做这些事的是 Reset_Handler,不是 main——它先设栈顶、把 .data 从 Flash 拷到 RAM、把 .bss 清零,再调 SystemInit 和 main。答不出这一串,基本等于没看过启动文件。
任务里 malloc 不释放、任务被删:vTaskDelete 只回收任务自己的栈和 TCB,任务用 malloc 拿的堆块没人管,泄漏到堆耗尽、malloc 返回 NULL;比泄漏更糟的是任务在持锁或进临界区时被删,锁永远不释放,其他任务一起卡死。工程上的做法是能静态分配就静态分配(xTaskCreateStatic + 静态缓冲),必须动态就用内存池按固定块分配,避免碎片;确实要 malloc,释放责任写在任务退出路径上。
DMA 与任务的并发是这套题里最值钱的一问。任务没处理完、DMA 又写进同一块缓冲,表现是数据被覆盖或前后半帧拼接——低频测试永远撞不到,一上量就偶发。解法是双缓冲(ping-pong):DMA 写 A 区时任务读 B 区,传完半满/全满中断里交换并投递信号量,任务处理完才交还缓冲区;或者用环形缓冲加读写指针,靠 DMA 的传输计数算可读长度。这里还有两个常被追问的点:单生产者单消费者才能不加锁,但读写指针要 volatile 加编译屏障;带 D-Cache 的 MCU 要在 DMA 写之前 clean、读之前 invalidate,否则 CPU 读到的是缓存里的旧数据。至于「中断为什么不能阻塞」——中断上下文没有可让出的任务栈,在里面等信号量会把同级和更低优先级的中断全堵住,正确做法是中断只置标志、发队列,重活丢给任务。
I2C 时钟拉伸是从机来不及处理时把 SCL 拉低,主机得检测并等它释放;坑在有些 MCU 的硬件 I2C 不支持从机拉伸,表现为偶发 NAK 或数据错位,只能降速或改软件模拟。SPI 四种模式由 CPOL(空闲电平)和 CPHA(第几个边沿采样)组合,mode0 和 mode3 最常见,主从配错的表现是整体移一位、读出来全是 0xFF 或 0x00。UART 用中断还是 DMA看波特率和帧长:低速率短帧用中断足够,高速率连续数据必须 DMA 加空闲中断(IDLE)收不定长帧,否则每字节一次中断,高波特率下直接丢字节。多设备地址:I2C 靠 7 位从机地址,撞地址就用多路复用器或换器件;SPI 没有地址概念,每个从机一根独立 CS。
链接脚本与启动是「抄来的东西」最容易露馅的地方。改脚本通常动两块:MEMORY 里的 Flash/RAM 起止和长度,SECTIONS 里各段的摆放——向量表必须在 Flash 起始地址,.data 的加载地址在 Flash、运行地址在 RAM,.bss 和栈堆的大小要留够。改完起不来,八成是向量表挪了位置(复位取不到初始栈顶和 Reset_Handler)、RAM 超了导致栈撞堆、或者 .data 的 LMA/VMA 写反初值全错。HardFault 定位的固定套路:先看 LR 的 EXC_RETURN 判断出错时用的是 MSP 还是 PSP,从对应栈里取出压栈的 R0–R3、R12、LR、PC、xPSR,PC 就是出错指令;再看 CFSR/HFSR 和 MMFAR/BFAR 分清是总线错误、用法错误还是非对齐,最后拿 map 文件或 addr2line 落到函数行。