校招 未知部门 / 硬件工程师 / 一面/二面
[面试日期] 2026-08-08 [面试时长] 未知
[学校背景] 同济大学 | 985 [学历背景] 未知
[发布人] 面试之星官方收录
<h2>面经6. 华为 | 嵌入式软件 | 面经</h2>
<p><em>来源为牛客付费专栏,以下仅收录公开可见内容,付费部分未收录。</em></p>
<p>华为一般为线下面试,在一个酒店当天面完。一面为技术面,二面为主管面,有些看眼缘和聊的程度,以及部门匹配度。</p>
<h2>1. 请简单介绍一下你自己。手撕 hot100: 删除相邻相同的字符</h2>
<h2>2. 在项目中是怎么设计通信数据帧的?如果出现粘包、拆包或者数据丢失怎么处理?</h2>
<h3>回答思路</h3>
<p>从<strong>数据帧格式设计 → 接收解析 → 异常处理</strong>三个方面考虑。</p>
<p>首先是数据帧格式,一般会设计成:</p>
<p><strong>帧头 + 数据长度 + 命令字 + 序号 + 数据区 + CRC校验</strong></p>
<p>例如:</p>
<pre>| 帧头 | 长度 | 命令 | 序号 | Payload | CRC |
</pre>
<ul>
<li><strong>帧头</strong>:用于确定一帧数据的开始;</li>
<li><strong>长度字段</strong>:告诉接收端当前帧有多长;</li>
<li><strong>命令字</strong>:区分不同业务;</li>
<li><strong>序号</strong>:用于检测丢包、重复包和乱序;</li>
<li><strong>Payload</strong>:实际业务数据;</li>
<li><strong>CRC</strong>:检测传输过程中数据是否发生错误。</li>
</ul>
<p>如果出现<strong>拆包</strong>,例如一帧数据分几次才接收完整,我不会立即解析,而是先放到接收缓存中,根据长度字段判断数据是否完整,完整之后再交给协议解析层。</p>
<p>如果出现<strong>粘包</strong>,例如一次接收到两帧甚至多帧数据,就根据帧头和长度字段连续解析,把完整的数据帧逐个取出来。</p>
<p>如果出现<strong>丢包</strong>,可以通过序号发现,例如:</p>
<pre>收到:1001、1002、1004
</pre>
<p>发现1003缺失,就可以根据业务需求进行处理。如果要求可靠传输,可以增加<strong>ACK、超时、重传机制</strong>;如果是实时数据,也可以直接丢弃旧数据,优先保证实时性。</p>
<p>最后通过 <strong>CRC</strong> 判断数据本身有没有被破坏。</p>
<h2>3. 项目中的数据量比较大时,你是怎么考虑缓存、数据处理和传输效率的?</h2>
<h3>回答思路</h3>
<p>一般从<strong>数据产生速度、缓存能力、处理能力、传输带宽</strong>四个方面考虑。</p>
<p>第一是先计算数据量,判断系统是否存在带宽瓶颈。</p>
<p>例如:</p>
<pre>采样率 × 每个采样点字节数 × 通道数
</pre>
<p>如果是16kHz、16bit、单通道音频:</p>
<pre>16000 × 2 = 32KB/s
</pre>
<p>如果通过UART传输,还要考虑UART实际有效吞吐率,不能只看标称波特率。</p>
<p>第二是缓存设计。</p>
<p>如果数据产生速度比较快,可以采用:</p>
<pre>ADC → DMA → 双缓冲/环形缓冲区 → 数据处理 → 发送
</pre>
<p>通过DMA减少CPU参与数据搬运。</p>
<p>第三是处理效率。</p>
<p>尽量采用<strong>批量处理</strong>,避免每收到一个字节就进入一次中断,同时可以把数据采集和数据处理放到不同任务中,通过队列或者环形缓冲区进行解耦。</p>
<p>第四是传输效率。</p>
<p>如果UART带宽不够,可以考虑:</p>
<ul>
<li>提高波特率;</li>
<li>减少无效数据;</li>
<li>降低采样率;</li>
<li>数据压缩;</li>
<li>批量发送;</li>
<li>使用SPI、USB、以太网等更高速的接口。</li>
</ul>
<p>核心是保证:</p>
<pre>数据产生速度 < 系统长期平均处理/传输能力
</pre>
<p>如果生产速度长期大于消费速度,再大的缓存最终也会溢出,所以不能只靠增加Buffer解决。</p>
<h2>4. 多个传感器的数据采样频率不同,你的软件层面怎么设计的?</h2>
<h3>回答思路</h3>
<p>不会让所有传感器都按照同一个周期处理,而是根据<strong>采样频率和实时性要求进行任务或者定时器分层设计</strong>。</p>
<p>例如:</p>
<pre>温度传感器:10Hz
压力传感器:100Hz
IMU:1kHz
</pre>
<p>可以分别使用不同的定时触发机制:</p>
<pre>1kHz → IMU采集
100Hz → 压力采集
10Hz → 温度采集
</pre>
<p>如果使用RTOS,可以设计成不同任务:</p>
<pre>IMU Task 1ms
Pressure Task 10ms
Temperature 100ms
</pre>
<p>采集数据之后统一放到对应的缓存或者消息队列中。</p>
<p>如果不同传感器的数据最终需要进行融合,还要考虑<strong>时间戳</strong>,不能只根据“收到数据的时间”判断数据对应关系。</p>
<p>例如:</p>
<pre>Sensor A:timestamp = 100ms
Sensor B:timestamp = 98ms
Sensor C:timestamp = 101ms
</pre>
<p>后面的数据融合模块根据时间戳进行对齐。</p>
<p>另外还要考虑某个传感器异常,例如I2C设备没有响应,不能让它一直阻塞整个系统。一般会增加<strong>超时、错误状态、重试和设备恢复机制</strong>。</p>
<p>整体架构可以理解为:</p>
<pre>不同采样频率
↓
独立采集
↓
DMA/缓存
↓
时间戳
↓
数据处理
↓
数据融合/业务层
</pre>
<h2>5. 机试的第二道题为什么按这个思路来写?当时怎么考虑的?</h2>
<h2>6. 机试第三题为什么没有全部 AC?现在回过头来看,你认为主要是解题思路的问题、边界条件考虑不充分,还是代码实现能力的问题?如果现在重新做,你会怎么优化?</h2>
<h2>7. const int *p、int *const p 有什么区别?</h2>
<p>答案:<code>const int *p</code>表示指针指向的数据不能通过 p 修改,但 p 本身可以改变;<code>int *const p</code>表示指针本身不能改变,但可以通过 p 修改它指向的数据;<code>const int *const p</code>则表示指针和指向的数据都不能修改。</p>
<h2>8. static 关键字有哪些作用?</h2>
<p>答案:修饰局部变量时,变量生命周期变为整个程序运行期间,但作用域仍然在当前函数内部;修饰全局变量时,限制变量只能在当前 .c 文件中使用;修饰函数时,也限制函数只能在当前 .c 文件中调用。</p>
<h2>9. volatile 关键字有什么作用?</h2>
<p>答案:volatile 告诉编译器,这个变量的值可能随时发生变化,因此每次使用时都需要从内存中重新读取,不能随意进行优化。嵌入式中常用于硬件寄存器、中断服务函数与主程序共享的变量,以及 DMA 等可能异步修改数据的场景。</p>
<h2>10. volatile 能够保证线程安全吗?</h2>
<p>答案:不能。volatile 解决的是编译器优化问题,并不能保证多个线程或任务同时访问变量时的数据一致性。例如:</p>
<pre>count++;
</pre>
<p>实际包含:</p>
<pre>读取
↓
修改
↓
写回
</pre>
<p>即使 count 使用 volatile 修饰,也可能发生竞态条件。需要通过:</p>
<pre>互斥锁
信号量
原子操作
</pre>
<p>解决。</p>
<h2>11. 什么是野指针?常见产生原因有哪些?</h2>
<p>答案:野指针是指向无效或者未知内存地址的指针。常见原因包括:</p>
<pre>指针没有初始化
↓
指向的内存已经被释放
↓
数组越界
↓
指针运算错误
</pre>
<p>通常可以通过:</p>
<pre>初始化为 NULL
↓
释放后置空
↓
检查指针有效性
↓
避免越界访问
</pre>
<p>来减少野指针问题。</p>
<h2>12. 数组和指针有什么区别?</h2>
<p>答案:数组是一块连续的内存空间,数组名代表整个数组;指针是一个存储地址的变量。数组名在表达式中很多情况下会退化为指向首元素的指针,但两者本质上并不是同一种数据类型。例如:</p>
<pre>int arr[10];
int *p = arr;
sizeof(arr); // 40
sizeof(p); // 4或8,取决于平台
</pre>
<p><code>sizeof(arr)</code>得到整个数组的大小,而 <code>sizeof(p)</code>得到指针变量本身的大小。</p>
<h2>13. C 语言程序运行时内存通常分</h2>
AI 推荐面试题
