蔚来 嵌入式 一面 面经
1. 说一下 volatile 的作用,它能保证线程安全吗?
答:
volatile 的核心作用是保证变量的可见性,并且在一定程度上禁止指令重排,但它不能保证复合操作的原子性,所以一般不能单独用来保证线程安全。
- 可见性
- 一个线程修改了
volatile变量后,其他线程能够立刻看到最新值。 - 它适合做状态标志位,比如“设备初始化完成”“接收到数据”等。
- 禁止指令重排
- 编译器和 CPU 在优化时可能会调整指令顺序。
volatile会在读写时加入内存屏障,避免关键步骤被乱序执行。
- 不能保证原子性
- 像
count++这种操作,本质上是“读-改-写”三步,不是原子操作。 - 即使
count是volatile,多个线程同时执行时仍然可能出错。
- 使用场景
- 中断与主循环共享的标志位。
- 多线程中的状态通知。
- 配置参数刷新。
- 不适合的场景
- 计数器累加。
- 多线程共同修改复杂结构体。
- 需要加锁保护的临界区。
一句话总结:volatile 更适合“通知”,不适合“竞争修改”。
2. 详细说一下函数调用时,参数和局部变量一般放在哪里?
答:
这个问题本质上是在考察程序内存布局和函数调用过程。
- 参数和局部变量通常放在栈上
- 函数被调用时,会创建一个栈帧。
- 形参、返回地址、部分寄存器现场、局部变量,通常都在这个栈帧中保存。
- 栈的特点
- 由编译器自动分配和释放。
- 访问速度快。
- 空间相对较小。
- 生命周期跟随函数调用过程。
- 特殊情况
static局部变量不在栈上,而是在全局/静态区。- 如果局部变量特别大,可能导致栈溢出。
- 某些优化情况下,局部变量可能直接放在寄存器里,不一定真的落到栈中。
- 为什么不能返回局部变量地址
- 局部变量属于当前函数栈帧。
- 函数返回后,栈帧被销毁,地址虽然还在,但内容已经不可靠。
- 嵌入式里要注意什么
- 任务栈一般比较小,不能在函数中定义过大的局部数组。
- 中断函数也要尽量减少栈占用。
一句话总结:普通局部变量大多在栈上,static 局部变量不在栈上。
3. malloc 申请的内存和数组定义出来的内存,有什么区别?
答:
这道题主要考察堆、栈、生命周期、管理方式。
- 分配位置不同
malloc申请的内存通常在堆上。- 普通局部数组通常在栈上。
- 全局数组或
static数组在静态存储区。
- 生命周期不同
malloc申请后,必须手动free。- 局部数组在函数结束后自动销毁。
- 全局数组在程序整个运行期间都存在。
- 大小灵活性不同
malloc可以运行时动态决定大小。- 普通数组大小一般在编译时确定。
- 使用风险不同
malloc可能申请失败。- 可能发生内存泄漏、野指针、重复释放、碎片化。
- 栈数组虽然简单,但空间太大可能栈溢出。
- 嵌入式开发中的考虑
- 小型 MCU 中通常慎用动态内存分配。
- 因为堆空间有限,且长期运行后容易产生碎片。
- 很多项目更倾向于静态分配或内存池。
一句话总结:malloc 更灵活,但管理更复杂;数组更简单,但大小和生命周期受限制。
4. 为什么很多驱动开发都强调“寄存器操作要谨慎”?
答:
因为寄存器直接对应硬件行为,写错一位,可能导致整个外设异常。
- 寄存器和普通变量不同
- 普通变量只是软件数据。
- 寄存器往往直接控制时钟、IO、电平、DMA、串口发送等硬件动作。
- 读改写风险
- 某些寄存器中的位是状态位、清零位、自清位。
- 如果直接“先读后改再写”,可能误清除状态或覆盖其他位。
- 位操作要准确
- 常用掩码方式设置和清除某些位:
reg |= maskreg &= ~mask- 避免影响无关位。
- 要注意时序
- 某些寄存器必须按固定顺序配置。
- 比如先开时钟,再配模式,再使能模块。
- 顺序错了,外设可能不工作。
- 要注意
volatile
- 寄存器地址通常会定义为
volatile。 - 防止编译器优化掉读写操作。
- 中断和并发问题
- 主程序和中断都访问同一个寄存器时,要考虑同步问题。
- 某些操作需要临界区保护。
一句话总结:寄存器操作不是简单赋值,而是在直接控制硬件,所以必须精确、谨慎、按时序来。
5. 说一下 Bootloader 和应用程序的区别?
答:
这道题在车载和嵌入式面试里很常见。
- Bootloader 是什么
- 上电后最先运行的一段程序。
- 负责基础初始化、程序校验、升级、跳转到应用程序。
- Application 是什么
- 就是业务功能程序。
- 例如电机控制、通信协议、传感器采集、人机交互等。
- 二者分工不同
- Bootloader 更关注启动和升级。
- Application 更关注具体业务逻辑。
- Bootloader 常见功能
- 初始化最基本的硬件。
- 检查应用程序是否有效。
- 接收升级包,比如 CAN、串口、以太网升级。
- 擦写 Flash。
- 完成程序跳转。
- 为什么要分开
- 防止应用程序损坏后设备彻底无法启动。
- 方便在线升级和故障恢复。
- 提高系统可维护性。
- 跳转时要注意
- 关闭中断。
- 设置 MSP。
- 设置向量表偏移。
- 跳到应用程序复位入口。
一句话总结:Bootloader 负责“启动和升级”,应用程序负责“具体功能”。
6
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
嵌入式面试八股文全集 文章被收录于专栏
这是一个全面的嵌入式面试专栏。主要内容将包括:操作系统(进程管理、内存管理、文件系统等)、嵌入式系统(启动流程、驱动开发、中断管理等)、网络通信(TCP/IP协议栈、Socket编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。

查看5道真题和解析