(嵌入式面经)第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(资源获取即初始化) 机制,

    用于自动管理动态内存newdelete),避免内存泄漏手动释放内存 的问题。

    在 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_ptrshared_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。

    全部评论

    相关推荐

    华为制造部 精密制造与自动化开发 研发岗 13级 硕士
    点赞 评论 收藏
    分享
    测试糕手手:感觉找工作和找实习都是开头难,有一个后就顺了很多
    点赞 评论 收藏
    分享
    评论
    7
    21
    分享

    创作者周榜

    更多
    牛客网
    牛客企业服务