(嵌入式面经)第11章 20+公司面经杂谈(四):兆易创新、联影医疗、诺瓦星云、经纬恒润
本篇涉及的所有问题概要:大家可以试试在看参考答案前,提前尝试解答,以便明确自身知识点的不足部分!
1.说一下局部变量与全局变量的区别。在MCU上空间上有什么不同吗?
2.内联函数和宏函数的区别,你知道吗?
3.进程与线程:区别与应用?
4.数组和链表有什么区别?
5.智能指针是什么,智能指针有哪些?
6.GD32/STM32的启动文件 (startup_stm32xxxx.s)了解吗 ?
7.中断与异常的区别?
8.你在FreeRTOS中如何给任务合理分配栈?
9.简单讲一下整个字符驱动怎么实现的?过程中实现了什么驱动功能?
10.专栏订阅奖励(支持模仿)——个人创新点问答:你机器人小车竞赛中的动态PD差速控制算法是什么,介绍一下设计思想?
---------------------------------------------------------------------------------------------------
1.说一下局部变量与全局变量的区别。在MCU上空间上有什么不同吗?
局部变量(Local Variable)和全局变量(Global Variable)是 两种不同作用域的变量,
它们在存储方式、生命周期、访问权限等方面存在显著区别,
尤其在 MCU(微控制器) 上,由于资源有限,二者的 空间占用 也存在明显不同。
1. 局部变量(Local Variable)
📌 特点
📌 示例
📌MCU 上的存储
- 局部变量默认分配在栈(Stack),函数调用时自动分配空间,函数返回后自动释放。
- 栈空间有限(通常只有几 KB),如果使用过多局部变量,可能导致 栈溢出(Stack Overflow)。
- 局部变量在多次调用时不会保留上次的值(除非使用
static
关键字)。
2. 全局变量(Global Variable)
📌 特点
📌 示例
📌 MCU 上的存储
- 全局变量存放在 RAM 的数据区(BSS 或 DATA 段)。
- BSS 段:未初始化的全局变量(默认初始化为 0)。
- DATA 段:已初始化的全局变量。
- 占用 RAM 的空间固定,如果 RAM 资源紧张,会影响系统性能。
3. 局部变量 vs. 全局变量的空间分布(MCU)
在 MCU(如 STM32)中,RAM 的存储结构一般如下:
📌 空间上的不同点:
- 局部变量存储在栈中,每次函数调用时动态分配,函数返回后释放。
- 全局变量存储在 BSS 或 DATA 段中,始终占据 RAM 空间,不会自动释放。
4. static 变量
static
变量在局部变量和全局变量之间提供了折中方案:
- 局部
static
变量:作用域仍然是局部,但生命周期变成 全局。 - 全局
static
变量:作用域限制在当前文件,不能被其他文件访问(避免变量冲突)。
📌 示例
📌 局部 static
变量存储在 DATA 段,不在栈中,所以不会随着函数调用和返回而释放。
5. 何时使用局部变量 vs. 全局变量
static
修饰局部变量,如果需要长期存储但不想影响全局空间。结论
📌 在 MCU 上,由于 RAM 资源有限,应避免全局变量过多,以免影响系统性能!
🚀 总结:
- 局部变量更节省空间(栈自动管理)。
- 全局变量更占用 RAM(始终驻留)。
- 合理使用
static
,避免全局变量污染。 - MCU 资源有限,应优先使用局部变量,避免栈溢出或 RAM 过度占用!
2.内联函数和宏函数的区别,你知道吗?
在 C 语言和 C++ 中,内联函数(inline) 和 宏函数(#define 宏) 都用于减少函数调用开销,
但它们在实现方式、编译过程、安全性等方面有显著区别。
1. 内联函数(Inline Function)
📌 定义
- 通过
inline
关键字定义的函数,编译时会直接展开到调用处,避免普通函数的调用开销。 - 适用于 短小、频繁调用 的函数,提高执行效率。
📌 示例
📌 运行机制
- 编译时 将
square(5)
替换为5 * 5
,避免了普通函数的 入栈/出栈 开销。 inline
仅是建议,编译器可能忽略,特别是当函数过于复杂时。
📌 优势
✅ 避免普通函数调用开销(如入栈、出栈、参数传递)。
✅ 有类型安全检查(确保参数和返回值匹配)。
✅ 支持调试(能够查看展开后的代码,支持 gdb
调试)。
✅ 适用于 C 和 C++,特别是 C++ 中的类内联函数。
📌 限制
❌ 无法递归调用(编译器不会无限展开)。
❌ 仅适用于短小函数,否则代码膨胀(Code Bloat)。
❌ 编译器可能忽略 inline
关键字,最终仍然按普通函数处理。
2. 宏函数(Macro Function)
📌 定义
- 使用
#define
预处理指令定义的函数,在预处理阶段直接替换。 - 无类型检查,仅仅是文本替换。
📌 示例
⚠️ 如果调用 SQUARE(5+1)
,会被展开为 (5+1 * 5+1)
,结果错误。
📌 运行机制
- 预处理阶段,编译器直接将
SQUARE(5)
替换为(5 * 5)
。 - 不会进行类型检查,也不会在符号表中创建函数。
📌 优势
✅ 无函数调用开销(因为是简单的文本替换)。
✅ 比内联函数更早处理(预处理阶段)。
✅ 适用于简单的数学运算和编译开关(如 #ifdef
)。
📌 限制
❌ 无类型检查,可能导致错误,例如 SQUARE(5+1)
。
❌ 无法调试(无法在 gdb
里设置断点)。
3. 内联函数 vs. 宏函数
4. 适用场景
✅ 适合使用 inline
内联函数
- 短小、逻辑简单的函数,如:
- C++ 类成员函数:
- 需要调试、类型安全 的函数。
❌ 适合使用 #define
宏
- 编译开关
- 简单的数学计算(但要小心副作用)
结论
✅ 推荐使用 inline
代替 #define
宏,因为:
- 更安全(有类型检查)
- 可调试
- 避免
#define
的副作用
❌ 仅在编译开关等场景下使用 #define
宏。
🚀 最佳实践:
- 短小计算函数:用
inline
。 - 编译选项:用
#define
。 - 避免宏副作用,如
#define SQUARE(x) (x*x)
。
🎯 总结:inline
是 #define
宏的安全替代方案,优先使用 inline
,避免 #define
带来的问题!
3.进程与线程:区别与应用?
在操作系统中,进程(Process) 和 线程(Thread) 是并发执行的基本单位,
但它们在资源管理、执行方式、通信方式等方面有显著区别。
1. 进程(Process)
📌 定义
- 进程是程序执行的独立单位,它拥有自己的地址空间,是操作系统进行资源分配和调度的基本单位。
- 每个进程至少包含一个线程(主线程),但也可以包含多个线程(多线程进程)。
📌 特点
📌 示例
🚀 执行 fork()
后,进程会被复制,父进程和子进程拥有独立的地址空间。
2. 线程(Thread)
📌 定义
- 线程是进程的最小执行单元,一个进程可以包含多个线程,这些线程共享相同的地址空间。
- 线程是更轻量级的并发执行单元,能比进程更快地创建和切换。
📌 特点
📌 示例
🚀 pthread_create()
创建的线程与主线程共享数据,可快速切换。
3. 进程 vs 线程:详细对比
4. 进程 vs 线程的应用场景
✅ 使用多进程(Process)
- 独立任务(数据库服务器、浏览器进程)
- 需要高稳定性(避免一个进程崩溃影响其他进程)
- 分布式计算(不同进程运行在不同服务器上)
✅ 使用多线程(Thread)
- 高效并行计算(Web 服务器、多线程下载)
- 共享数据(游戏引擎、图像处理)
- 降低开销(线程切换比进程快)
结论
🚀 进程 提供独立的执行环境,适用于需要隔离的任务。
🚀 线程共享地址空间,适用于需要快速共享数据的任务。
4.数组和链表有什么区别?
数组(Array)和链表(Linked List)是两种常见的数据结构,
它们在内存分配、访问速度、插入删除操作等方面存在显著区别。
1. 数组(Array)
📌 定义
- 数组是一块连续的内存空间,每个元素占用相同的存储空间,并且通过**索引(index)**进行访问。
📌 特点
📌 示例
2. 链表(Linked List)
📌 定义
- 链表由多个节点(Node)组成,每个节点存储一个数据,并且通过**指针(Pointer)**指向下一个节点。
📌 特点
📌 示例
3. 数组 vs. 链表:详细对比
4. 数组 vs. 链表的应用场景
✅ 适合用数组的情况
- 数据大小已知,访问频繁(如查表、缓存)
- 数组大小固定,元素不变(如数学矩阵、静态数据)
- 需要快速访问某个元素(如CPU 指令流水优化)
✅ 适合用链表的情况
- 数据大小不固定(如动态数据存储、任务队列)
- 插入/删除操作频繁(如队列、堆栈)
- 需要节省数组扩展的开销(如C 语言实现的动态集合)
结论
🚀 数组适合快速访问,链表适合频繁插入/删除。
🚀 如果数据量大,访问频繁,用数组;如果数据动态变化快,用链表。
5.智能指针是什么,智能指针有哪些?
智能指针 是 C++ 提供的一种 RAII(资源获取即初始化) 机制,
用于自动管理动态内存(new
和 delete
),避免内存泄漏 和 手动释放内存 的问题。
在 C++11 及以上版本,标准库 <memory>
提供了智能指针,用于替代 裸指针(raw pointer),提高代码的安全性和可读性。
1. 智能指针的主要类型
2. std::unique_ptr(独占所有权)
📌 特点
- 只能有一个
unique_ptr
指向同一个对象,不能进行拷贝(copy
)。 - 对象生命周期由
unique_ptr
管理,离开作用域时自动释放内存。 - 适用于独占资源的场景,如文件、网络连接、硬件资源等。
✅ 使用示例
📌 作用域结束时 unique_ptr
自动释放内存,无 delete
操作。
3. std::shared_ptr(共享所有权)
📌 特点
- 多个
shared_ptr
可以共享同一块动态内存。 - 内部使用 引用计数(Reference Count) 机制,当最后一个
shared_ptr
离开作用域时,释放内存。 - 适用于多个对象需要共享同一资源的情况,如对象池、缓存管理。
✅ 使用示例
✅ shared_ptr
自动管理对象,当引用计数降为 0 时,自动释放。
4. std::weak_ptr(弱引用,不影响对象生命周期)
📌 特点
weak_ptr
是shared_ptr
的弱引用,不增加引用计数。- 防止循环引用(shared_ptr 互相引用,导致内存泄漏)。
- 不能直接使用,需要转换为
shared_ptr
才能访问对象。
✅ 使用示例
✅ weak_ptr
仅用于检测对象是否仍然存活,不影响对象生命周期。
5. unique_ptr vs shared_ptr vs weak_ptr
6. 智能指针 vs. 普
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
作者简介:仅用几个月时间0基础天坑急转嵌入式开发,逆袭成功拿下华为、vivo、小米等15个offer,面试经验100+,收藏20+面经,分享求职历程与学习心得。 专栏内容:这是一份覆盖嵌入式求职过程中99%问题指南,详细讲解了嵌入式开发的学习路径、项目经验分享、简历优化技巧、面试心得及实习经验,从技术面,HR面,AI面,主管面,谈薪一站式服务,助你突破技术瓶颈、打破信息差,争取更多大厂offer。