(嵌入式面经)第11章 20+公司面经杂谈(一):华为、博世、理想、韶音
本篇涉及的所有问题概要:大家可以试试在看参考答案前,提前尝试解答,以便明确自身知识点的不足部分!
1. TCP & UDP了解吗,简单说一下,有什么区别?
2. 好的,那你了解粘包跟拆包吗,这两个协议会出现这个情况吗,为什么呢?
3. 了解编译器优化吗?
4. 刚才你有说到内联函数,你来讲讲它的作用,以及与普通函数的区别,有不建议使用的时候吗?
5. 那么有没有碰到过禁止编译器优化的场景,能举例吗?
6. 看门狗了解吗,说一说独立看门狗与窗口看门狗?
7. 说一说堆(Heap)和栈(Stack)的区别,以及是否自动分配?
8. 重入函数、函数重写、函数重载你了解吗,说一说?
9. 嵌入式开发中,你一般debug会看什么信息?
10. 专栏订阅奖励(支持模仿)——个人创新点问答:我看你在自己设计的FreeRTOS PLUS(我自己取的RTOS名字)内存管理中采用了内存推迟合并策略,这是什么?如何实现的?
---------------------------------------------------------------------------------------------------
1. TCP & UDP了解吗,简单说一下,有什么区别?
TCP(传输控制协议):
- 面向连接:通信前需要建立连接(三次握手),通信结束时关闭连接(四次挥手)。
- 可靠传输:数据传输有确认机制、超时重传机制,确保数据可靠性。
- 有序传输:数据包按发送顺序到达,不乱序。
- 流量控制与拥塞控制:能动态调整发送速率,避免网络拥堵。
- 适用场景:网页浏览(HTTP/HTTPS)、文件传输(FTP)、邮件传输(SMTP)等。
UDP(用户数据报协议):
- 无连接:不需要建立连接,可以直接发送数据包。
- 不可靠传输:不保证数据包一定送达,不提供确认机制,不进行重传。
- 无序传输:数据包可能乱序到达。
- 轻量高效:协议简单,效率高,延迟低。
- 适用场景:实时通信(视频会议、直播)、网络游戏、DNS查询等对速度敏感但能容忍部分数据丢失的场景。
简单来说:
- TCP强调可靠性,UDP强调速度和效率。
2. 好的,那你了解粘包跟拆包吗,这两个协议会出现这个情况吗,为什么呢?
粘包与拆包的定义:
粘包:
定义:
- 多个数据包被合并成一个包,接收方无法分辨消息边界。
可能的接收情况:
发生原因:
- TCP 发送缓冲区优化:为了减少小数据包的开销,Nagle 算法 可能会将小包合并。
- TCP 以流方式传输,没有消息边界。
- 发送方发送的数据 < 接收方 TCP 缓冲区,系统等待数据填满缓冲区后一次性发送。
拆包:
定义:
- 一个数据包被拆成多个小包 进行传输,接收方一次
recv()
可能收到不完整的数据。
可能的接收情况:
发生原因:
- 发送方发送的数据 > TCP 缓冲区大小,需要 分片发送。
- 网络传输中的 MTU(最大传输单元)限制,超过 MTU 需要拆分。
TCP与UDP出现的情况:
如何解决TCP粘包、拆包可能带来的问题:
(1)固定长度协议
- 方法:规定 每个数据包固定大小,接收方按固定字节读取。
- 适用场景:定长结构数据(如传感器数据)。
优点:
缺点:
1024
字节,仍然要填充。(2)分隔符协议
\n
, \0
, |
),接收方按分隔符拆分数据。- 接收方
优点:
缺点:
- 数据中包含分隔符时可能误判,需要 转义或编码。
(3)消息头协议
- 接收方
优点:
缺点:
- 需要额外存储消息头,协议较复杂。
3. 了解编译器优化吗?
1. 编译器优化的目的
编译器优化的主要目标是 提高程序执行效率、减少代码大小,并降低运行时资源消耗,同时 保证代码逻辑正确性。
优化可分为:
- 代码级优化:调整代码结构,提高执行速度。
- 指令级优化:调整汇编代码顺序,提高 CPU 指令流水线利用率。
- 存储优化:减少内存访问,提高缓存命中率。
2. 编译器优化的主要策略
编译器在 编译时(compile-time) 和 运行时(runtime) 进行优化,主要包括:
(1)代码优化
✅ 常量传播(Constant Propagation)
- 如果变量的值是编译期已知的常量,则用该常量替换变量,减少计算次数。
优化前
优化后
✅ 常量折叠(Constant Folding)
- 编译器提前计算常量表达式,减少运行时计算。
优化前
优化后
✅ 死代码消除(Dead Code Elimination)
- 删除不会被执行的代码,减少程序大小。
✅ 内联展开(Function Inlining)
- 小函数可以直接展开,避免函数调用的开销。
优化前
优化后
(2)指令优化
✅ 指令重排序(Instruction Reordering)
- 调整指令执行顺序,提高 CPU 指令流水线利用率。
优化前
优化后(可能)
✅ 寄存器分配(Register Allocation)
- 尽量使用寄存器存储变量,减少内存访问,提高速度。
优化前
优化后
✅ 循环展开(Loop Unrolling)
- 减少循环控制开销,提高 CPU 指令执行效率。
优化前
优化后
- 这样可以减少
for
循环的控制开销,提高速度。
(3)存储优化
✅ 内存对齐(Memory Alignment)
- 优化数据结构布局,减少 CPU 访问内存的时间。
优化前
优化后
这样能 提高 CPU 读取效率,减少缓存行冲突(Cache Miss)。
✅ 缓存优化(Cache Optimization)
- 提高数据局部性,减少 CPU 访问内存的延迟。
优化前
优化后(列访问)
- 减少缓存不命中(Cache Miss),提高 CPU 读取速度。
3. 现代编译器优化工具
(1)编译器优化级别
(2)GCC/Clang 编译器优化选项
-O2
:启用优化-march=native
:针对本地 CPU 优化-flto
:启用 链接时优化(Link-Time Optimization)4. 优化方法总结
结论
✅ 编译器优化能显著提高程序性能,但可能会引入 并发问题。
✅ 指令重排序、寄存器缓存、循环优化 是编译器优化的核心策略。
✅ 使用 -O2
、-O3
可以让 GCC 进行更高级别优化,提高执行效率。
4. 刚才你有说到内联函数,你来讲讲它的作用,以及与普通函数的区别,有不建议使用的时候吗?
1. 什么是内联函数?
内联函数(inline function
)是 在编译时展开 而 不执行常规的函数调用 的函数。
编译器会将函数体直接替换到调用点,避免函数调用的开销。
示例
编译器优化后
关键点:
- 没有函数调用的开销(如
push/pop
、返回地址)。 - 适用于小函数,提高程序执行效率。
2. 为什么使用内联函数?
✅ 减少函数调用开销
- 普通函数调用涉及 栈帧创建、参数传递、返回地址保存,而 内联函数避免了这些额外开销。
✅ 提高程序运行效率
- 短小的函数,如 数学运算、逻辑判断,可以 直接在调用点展开,减少 CPU 指令数。
✅ 优化代码分支预测
- 内联展开可减少跳转,提高 CPU 指令流水线 的执行效率。
3. 内联函数 vs 普通函数
4. 什么时候不适合使用内联函数?
❌ (1)递归函数
问题:
- 递归调用无法展开,编译器 不会内联。
- 递归会创建新的栈帧,不能优化。
❌ (2)函数体很大
问题:
- 大函数展开会导致代码膨胀(Code Bloat),增加可执行文件大小。
- 会影响 CPU 指令缓存(Cache Miss)。
❌ (3)虚函数
问题:
- 虚函数是通过 vtable(虚函数表)调用的,无法内联。
- 因为实际调用的函数在运行时决定,所以编译器无法展开。
5. 现代 C++ 中 inline 的改进
(1)C++17 inline
变量
- C++17 允许在类外定义
inline
变量,保证 唯一性。
(2)constexpr
结合 inline
- C++11 及以上可使用
constexpr
进行编译时计算,替代inline
constexpr
适用于编译期常量计算,性能更优。
结论
✅ 内联函数减少函数调用开销,提高性能,适用于 短小、高频调用的函数。
✅ 避免在 递归、虚函数、大型函数 中使用 inline
,防止代码膨胀。
✅ 现代 C++ 推荐 constexpr
作为 inline
的替代方案。
5. 那么有没有碰到过禁止编译器优化的场景,能举例吗?
在某些特殊情况下,我们需要 禁止编译器优化,以确保 正确性、可调试性 或 特定硬件行为。
(关键是答出volatile
关键字的作用及应用场景!)
1. 多线程共享变量
场景:
- 在 多线程编程 中,编译器可能会优化掉某些变量的读取和存储,导致 线程无法正确感知变量的变化。
示例(使用 volatile
防止优化)
✅ volatile
保证 stop_flag
每次都从内存读取,而不会被编译器优化。
2. 防止指令重排序
场景:
- 在 高性能并发代码 中,编译器可能调整指令顺序,导致 CPU 乱序执行,影响多线程同步的正确性。
示例(使用 memory barrier
防止优化)
✅ std::memory_order_acquire
和 std::memory_order_release
确保 ready
的写入先于读取,防止 CPU 和编译器重排序。
3. 避免优化延迟计算
场景:
- 在某些 时间敏感的应用(如定时器、硬件访问),编译器可能优化掉 看似无用的循环,导致代码行为异常。
示例(使用 volatile
防止空循环优化)
✅ volatile
确保循环不会被优化掉。
4. 访问硬件寄存器
场景:
- 在 嵌入式系统、驱动程序开发 中,访问 I/O 端口、寄存器 需要 禁止编译器优化,否则可能导致硬件操作失败。
示例(访问硬件寄存器)
✅ volatile
让 GPIO_PORT
每次都直接访问内存,而不会使用寄存器缓存。
5. 避免调试优化
场景:
- 在调试模式下,编译器可能会优化掉某些变量,使得 GDB、LLDB 等调试工具无法准确读取变量值。
示例(使用 volatile
变量防止调试优化)
✅ volatile
让 x
始终存储在内存中,而不会被优化掉,确保 调试时可以正确读取 x
。
6. 读取随机数、时间等外部状态
场景:
- 编译器可能优化掉外部状态读取(如
time()
、rand()
),导致错误的结果。
示例(确保每次 rand()
都会被调用)
✅ volatile
确保 rand()
每次都执行,而不会被优化掉。
7. 防止优化特定代码块
场景:
- 某些情况下,我们需要防止编译器优化整个代码块,例如测试 CPU 性能、精确计时。
示例(使用 asm volatile
禁止优化)
✅ asm volatile("" ::: "memory")
作为 memory barrier
,确保代码不会被优化掉。
结论
✅ 在多线程、硬件编程、调试、性能测试等场景下,可能需要禁止编译器优化。
✅ 使用 volatile
、std::atomic
、memory barrier
可以防止编译器进行特定优化。
✅ 合理使用 volatile
,避免编译器优化带来的错误,提高代码的正确性和可维护性! 🚀
6. 看门狗了解吗,说一说独立看门狗与窗口
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
作者简介:仅用几个月时间0基础天坑急转嵌入式开发,逆袭成功拿下华为、vivo、小米等15个offer,面试经验100+,收藏20+面经,分享求职历程与学习心得。 专栏内容:这是一份覆盖嵌入式求职过程中99%问题指南,详细讲解了嵌入式开发的学习路径、项目经验分享、简历优化技巧、面试心得及实习经验,从技术面,HR面,AI面,主管面,谈薪一站式服务,助你突破技术瓶颈、打破信息差,争取更多大厂offer。