ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

嵌入式软件笔试攻略:C语言基础、显示控制与答题策略全解析

嵌入式软件笔试攻略:C语言基础、显示控制与答题策略全解析

说实话,收到某显示技术公司的笔试邀请时,我第一反应不是兴奋,而是有点紧张。在LED显示控制、视频处理这一行,这家公司的笔试出了名的扎实,也出了名的“不按套路出牌”。题面看起来都是C语言基础、数据结构、操作系统这些老生常谈,可真动笔的时候,每一个考点都像在提醒你:写代码不是背答案,而是能不能在嵌入式环境下写出稳定、可读、不越界的东西。

这篇文章把我准备和参加这家公司笔试的完整经历整理了出来。我会把试卷结构、核心考点、手写代码的高频题、显示控制方向特有的图像与视频基础题,以及我踩过的坑和总结的答题顺序,全部摊开讲清楚。无论你是应届生准备第一份工作,还是想转行进入显示控制行业的嵌入式开发者,这套思路都能直接拿来用。

1. 笔试考什么:先把试卷结构摸清楚

1.1 岗位方向决定试卷差异

我刚开始准备的时候犯过一个错误:觉得“笔试就是笔试”,把所有岗位的题目一锅端地复习。后来问了一圈才明白,这类公司笔试是分方向的,你投的是嵌入式软件、FPGA逻辑、上位机应用还是图像算法,试卷的侧重点完全不一样。

拿我接触到的常见划分来说:

岗位方向重点考察内容占比高的题型
嵌入式软件C语言、内存管理、中断、操作系统选择题、手写函数、简答题
FPGA逻辑Verilog、时序分析、跨时钟域、状态机填空、波形分析、代码改错
上位机/应用C++、界面开发、多线程、TCP/UDP编程题、设计题
图像算法图像处理基础、色彩空间、算法复杂度计算题、算法设计题

选对复习方向,效率完全不一样。我当时投的是嵌入式软件方向,就重点攻C语言和数据结构,图像题只补了显示控制行业最常用的那部分,结果证明这个策略是对的。你要是本来就熟悉某个方向,别贪多,先把本方向的确定性拿分点稳住。

1.2 题型分布与时间分配

结合我参加的那场笔试和身边同行反馈,这类企业的笔试通常包含四类题型:单选题或填空题、简答题、手写编程题,以及一道综合设计题。整体时间在90到120分钟左右,题量不小,尤其是手写代码部分,占分比重相当高。

我根据自己的经验做了个通用时间分配参考:

  1. 选择和填空:不超过25%的时间,会就选,不会先标记,别纠结。
  2. 简答题:控制在20%的时间,逻辑清楚、要点齐全即可。
  3. 手写编程题:留足45%以上的时间,这是拉分的关键区。
  4. 综合设计题:最后10%时间搭框架、写思路,不追求完美。

这套分配的核心逻辑很简单:选择和填空的单题分值低,纠结一道容易丢大分;编程题一道往往十几分甚至二十分,写出一版能跑通的主逻辑,比空着强太多。

2. 嵌入式C语言:绕不开的根基

2.1 指针、内存与经典错误

不管哪个方向,C语言几乎是必考的,嵌入式方向更是重灾区。笔试里通常不会让你背概念,而是给你一段代码让你找错、改错,或者让你手写一个函数。这类题目背后的核心考点就三个:指针理解、内存生命周期、边界条件。

先看一个我印象很深的经典改错题:

int* get_value(void) { int a = 100; return &a; }

这个函数看起来没什么问题,但返回的是局部变量a的地址。a在函数返回后就已经销毁了,调用方拿到的是一个悬垂指针,一旦访问,结果是未定义的。这正是面试官想看到你注意的点。

正确做法通常有三种:把a改成静态变量、用malloc在堆上分配并由调用方释放,或者通过出参返回:

int get_value(int *out) { if (out == NULL) return -1; *out = 100; return 0; }

笔试里类似的陷阱还有:返回局部数组名、memcpy长度不对导致越界、malloc之后没有判空就使用、释放后继续访问。我的检查习惯是写完函数先问自己三个问题:这个指针指向的内存是谁分配的?生命周期到什么时候结束?谁来负责释放?三个问题都能回答清楚,指针题基本稳了。

2.2 结构体对齐与大小端判断

结构体对齐是选择题和填空题的常客,很多人在这里丢分,因为这不是“背下来就行”的知识,而是要理解对齐规则。默认情况下,结构体成员的偏移量必须是其自身对齐系数的整数倍,整个结构体的大小也必须是最大成员对齐系数的整数倍。

举个例子:

struct Test { char a; // 1字节 int b; // 4字节 char c; // 1字节 };

按默认4字节对齐来算,sizeof(struct Test)不是6,而是12。char a占偏移0,int b要从偏移4开始占4字节,char c占偏移8,最后整个结构体大小要补齐成4的倍数,所以是12。笔试里经常接着追问:如果调整成员顺序,把b放前面:

struct Test2 { int b; char a; char c; };

大小就变成8了。这个例子说明一个实用技巧:把大类型成员往前放,可以显著节省内存。在嵌入式环境里,结构体数组中成千上万个元素,节省下来的空间是实打实的。

大小端也经常和结构体、协议解析绑在一起考。判断本机大小端的经典手法是用联合体:

union { unsigned short val; unsigned char bytes[2]; } u; u.val = 0x0102; if (u.bytes[0] == 0x01) { // 大端模式 } else { // 小端模式 }

为什么显示控制行业特别在意大小端?因为视频数据要通过网络或自定义协议传输,收发双方如果不统一字节序,RGB数据转出来就是花屏。笔试里如果给你一段网络字节序转换的代码,你要能看出htonl、ntohs这类函数的作用,并解释为什么需要它们。

2.3 高频手写编程题

手写代码是笔试的大头,题型其实比较集中,翻来覆去就是链表、二叉树、排序、字符串、队列这几类。我最推荐提前手写一遍以下题目:

  • 单链表反转
  • 有序数组二分查找
  • 手写strcpy并考虑内存重叠
  • 用数组实现环形队列
  • 二叉树层序遍历

以单链表反转为例,我建议至少会两种写法:迭代和递归。迭代版是基础,必须写得快且没有边界错误:

struct Node { int val; struct Node *next; }; struct Node* reverse_list(struct Node *head) { struct Node *prev = NULL; struct Node *curr = head; while (curr != NULL) { struct Node *next = curr->next; curr->next = prev; prev = curr; curr = next; } return prev; }

这里最容易翻车的点有两个:一是在循环体内先把next保存好,否则修改curr->next之后找不到原链表;二是循环结束后返回的是prev而不是curr,因为此时curr已经变成NULL。我笔试时还会在函数开头加上对head == NULL的判断,这种边界处理虽然不扣分,但能给阅卷人留下好印象。

还有一个高频题是手写字符串拷贝,但它和普通strcpy不一样的地方在于内存重叠。标准写法要注意从后往前拷贝,否则源区域和目的区域重叠时可能覆盖数据。笔试时我建议写一个不依赖库函数的版本,并且把“为什么这样写”在注释里点明,这比单纯写对代码更拿分。

3. 操作系统与协议:嵌入式避不开的第二座山

3.1 任务调度、锁与死锁

嵌入式软件方向的笔试,操作系统题目几乎必考。选择题喜欢考进程和线程的区别、上下文切换、中断和异常的区别;简答题喜欢让你分析一段多线程代码,指出竞态条件和解决办法;编程题偶尔出现生产者和消费者,或者两个线程交替打印数字。

我记得有一道典型的题目:两个线程,一个打印1、3、5,另一个打印2、4、6,要求最终输出1到6。这个题看起来简单,实际上考察你对互斥锁和条件变量的理解。用信号量或互斥锁都能做,但如果你只会背“加锁解锁”而不理解阻塞和唤醒,边角情况会漏处理。

死锁的几个必要条件也需要能脱口而出:互斥、持有并等待、不可剥夺、循环等待。笔试里还可能给你一段加锁顺序不一致的代码,让你判断是否可能死锁。我的判断思路是:把每个线程请求锁的顺序画出来,如果存在环路,就存在死锁风险。这个“画环路”的习惯在复习阶段帮我省了很多时间。

3.2 网络与字节序

虽然嵌入式岗位不要求你精通全栈网络,但基本功要有。TCP三次握手为什么是三次而不是两次、UDP和TCP怎么选、粘包怎么处理,这些高频题建议提前组织好语言,因为简答题考察的就是你“能不能用简洁准确的话讲清楚”。

我在备考时特意用抓包工具看了一次本地回环的数据包,对照三次握手的序列号变化看了一遍。纸上得来终觉浅,这句话在网络协议上特别适用。笔试答题时我写的是:第一次握手建立连接意向,第二次确认并回传自己的序号,第三次客户端确认同时双方都知道“自己发出去的包对方能收到”。不要背过程性描述,要写出“为什么需要第三次”的本质。

字节序和粘包是显示控制场景里的实际问题。视频流数据量大,分包传输会产生粘包,接收端必须根据包头长度字段拆包。笔试如果让你设计一个简单的拆包函数,记住“循环+状态机”的思路:先收够固定长度的包头,再根据包头里的长度字段接收完整数据体,最后处理可能残留的半包。

4. 显示控制方向的专业题:图像与视频基础

4.1 为什么这类笔试要考图像处理

看标题你可能觉得奇怪:一个做LED显示控制系统的公司,笔试为什么考图像?答案是,这类公司的核心产品就是把视频信号接收、处理、转换后驱动到LED屏上,从各种视频接口进来的信号,要经过裁剪、缩放、色彩转换、灰度调制,最后在屏体上正确显示。不了解这些基础,写出来的代码可能逻辑正确,但显示效果一塌糊涂。

所以笔试里经常出现:RGB和YUV色彩空间的区别、Gamma校正的作用、灰度等级和位深的关系、分辨率和刷新率的带宽计算。这些题目不难,但如果你没接触过显示行业,很容易无从下手。

4.2 带宽计算题:现场推一遍就记住

我笔试那天遇到一道计算题:一块3840乘2160分辨率的显示屏,刷新率60Hz,每个像素用24位RGB表示,问未经压缩的视频数据带宽是多少(忽略消隐时间)。

拆解计算:

  1. 每帧像素数:3840 × 2160 = 8,294,400
  2. 每帧数据量:8,294,400 × 3字节 = 24,883,200字节
  3. 每秒数据量:24,883,200 × 60 = 1,492,992,000字节/秒
  4. 换算成Gbps:1,492,992,000 × 8 ≈ 11.94 Gbps

也就是说,单路4K60的RGB888无压缩视频,光数据就需要大约12Gbps的带宽。如果考虑实际视频接口的行消隐和场消隐,高带宽接口也就是勉强够用。这个计算结果给很多人一个直观感受:为什么显示控制设备里要用YUV420、用压缩,或者用多通道并行传输。答这道题的关键不只是算出数字,而是后面跟上“所以实际系统会采用……来降低带宽压力”这句分析,这能体现你对行业的理解。

4.3 接口、扫描方式与常见协议

显示控制方向笔试还会零星问到传输接口和LED屏扫描方式。常见的视频接口有HDMI、DP、DVI、SDI,还有嵌入式设备里常用的LVDS、eDP、MIPI;LED屏的驱动则涉及行扫描、灰度控制。选择题可能会问:LED显示器的灰度用多少位表示、刷新率和灰度冲突时优先保证哪个、为什么室内屏和室外屏的亮度要求不同。

这类题不需要你背协议细节,理解几个核心概念就够:PWM调灰度、扫描行数影响刷新率、亮度与环境光的平衡。备考时我建议搜几篇LED显示屏驱动原理的文章,把“灰度”“刷新率”“扫描”这三个词之间的关系捋清楚,应付笔试绰绰有余。

5. 笔试实战:答题顺序与避坑清单

5.1 我的答题顺序和理由

考场上最忌讳的是“从第一题按顺序硬做到底”。我个人的顺序是:先把所有编程题通读一遍,选出最熟悉的一题先写;然后回到选择和填空快速过;中间穿插简答题;最后留出时间回头补没做的编程题,并检查之前代码的边界条件。

理由很简单:编程题分值高、思维量也大,放在头脑最清醒的时候做,正确率和速度都有保证。如果先做选择题消磨了精力,到了编程题反而容易卡壳,那种挫败感会影响整场状态。我上次笔试就是先写链表反转题,十分钟搞定,后面整个人的心态都稳了。

5.2 写代码时的检查清单

笔试手写代码,阅卷人看的不只是对错,还有习惯。我给自己定了一个检查清单,每次写完代码都过一遍:

  1. 变量有没有初始化,尤其是累加器和指针。
  2. 循环边界是< n还是<= n,是不是从0开始。
  3. 数组下标会不会越界。
  4. 内存:分配了有没有释放,释放后有没有置空。
  5. 空指针、空串、空链表等边界输入有没有处理。
  6. 函数返回值能不能反馈错误原因。
  7. 是否有未使用的变量或冗余逻辑。

这个小清单看起来琐碎,但它帮我抓住了好几次低级错误。比如有一次我写环形队列的入队函数,忘记处理队列判空的特殊情况,逻辑上就会出现“队空时头尾指针相同,入队后判断失效”的问题。考试时把这些情况想清楚,比你多背十道题都管用。

5.3 不会做的题,至少拿到思路分

说实话,没有人能保证每道题都会。笔试遇到完全没思路的编程题,我的策略是:绝不空白。把题目拆解成几个步骤,用伪代码写下你理解的流程,再标明关键数据结构和大致的复杂度。阅卷人通常会给思路分,哪怕代码不能跑通,至少证明你有分析能力。

比如遇到“实现一个简易图像缩放”这类题,即使你不熟悉插值算法,也可以写下“遍历目标像素坐标,按比例映射回源图,用最近邻或双线性方式取像素值”的思路。这就是一份合格的答题框架。比空白答题拿到的分数多得多。我见过不少同行说,笔试里“写了框架”和“完全没写”,结果天差地别。

6. 高频问题与经验速查

6.1 笔试中的典型翻车点

我把身边人笔试失败反馈最多的点整理成了一张速查表,考前看一遍非常有用:

翻车点典型表现正确思路
结构体对齐算错sizeof结果和实际偏差按成员对齐系数和最终补齐规则重新算
内存泄漏每循环一次malloc一次确认释放位置,最好用分配/释放对称原则
字符串处理越界strcpy目标缓冲区过小用带长度限制的函数或先计算长度
整数溢出乘积结果变成负数计算前提升类型或先拆分成多步
忽略输入格式编程题没处理换行和空格明确输入边界,必要时用fgets替换scanf
位运算优先级混淆表达式结果不符合预期加括号,不依赖默认优先级
死锁分析漏环只分析了一个线程画出所有锁请求关系再判断

6.2 考前的最后一晚和当天的心态

笔试前一晚我通常不再刷新题,而是把之前写过的代码重新默写一遍,重点是链表反转、二分查找、字符串拷贝这几个高频函数。这样做有两个好处:一是形成肌肉记忆,考场上不用现场推;二是建立信心,心里有底了,睡眠质量也会好。

考试当天我一般提前二十分钟到电脑前,先把环境试一下。如果是线上笔试,摄像头、输入法、代码编辑器都要提前确认;如果是线下笔试,看清题目是在纸上写还是机器上提交,纸笔题的字迹和排版要工整,机器提交则要注意代码缩进和注释。细节决定成败,这句话在笔试里真不是空话。

6.3 笔试之后:复盘比刷题更重要

笔试结束不是终点,尤其是投这类显示控制公司,笔试往往只是第一关,后面还有面试。我每次考完都会趁着记忆新鲜,把不会的题和判断模糊的知识点记下来,当晚就查资料弄明白。这个复盘习惯帮我积累了整整一本笔记,后续面试时很多问题都从这里面出。

我个人还有个习惯:把笔试中出现的计算题,比如带宽计算、时序计算,整理成模板公式,标注每一步的物理含义。因为面试官很喜欢在面谈环节追问:“你笔试里这道题为什么是这个答案?”如果你能现场推导一遍,基本上这道题就成了一块加分牌。

最后再分享一个小经验:备考这类笔试,别迷信题海战术,吃透C语言基础和显示控制行业的基本常识,比刷一百道偏题怪题有效。你真正要在考场上展示的,是“遇到问题能拆解、写代码能想边界”的习惯,这个能力准备好了,笔试怎么变都不怕。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进