嵌入式软件笔试高频考点解析:C语言、操作系统与Linux全覆盖
2026/8/31 3:52:51 网站建设 项目流程

每年秋招季,总有一批准备投嵌入式软件岗的同学到处翻往年的笔试真题。顺丰科技2019秋招这套嵌入式软件开发工程师客观题,虽然年份久了一点,但嵌入式岗位考察的底层知识相当稳定,C语言、操作系统、数据结构、计算机组成原理、网络这几座大山基本没变过。我拿到这份题集重新梳理了一遍,发现里面的考点放到现在依然有很强的指向性,很适合作为一轮系统复习的摸底清单。这篇文章就来逐块拆解这套题的考察逻辑,结合我自己的复习和面试经验,讲清楚每类题背后的知识点、容易踩的坑,以及怎么把题目延伸到面试环节去准备。

1. 整体设计与考察范围拆解

客观题作为一个笔试环节,设计的核心目标是用有限的时间筛出基础扎实、思维严谨的人。所谓客观题,典型形态就是单选、多选、判断和少量填空题。嵌入式软件开发岗位面对的工作内容是驱动开发、系统移植、应用层逻辑实现、性能优化,因此笔试的覆盖面必然横跨整个CS核心知识体系。

从这套2019年的题集来看,考察范围可以大致归成六类:

  • C语言与内存管理:指针、数组、结构体对齐、关键字语义、内存布局。
  • 数据结构与算法基础:链表、栈、队列、树、复杂度分析。
  • 操作系统原理:进程线程、调度、同步互斥、死锁、内存管理。
  • 计算机组成原理:大小端、位域、中断、寄存器、Cache。
  • 网络基础:TCP/IP分层、三次握手、IP地址基础。
  • Linux与嵌入式基础:常用命令、交叉编译、驱动模型初步、文件系统。

这个分布不是顺丰独有的,而是嵌入式软件岗位笔试的通用模板。原因很直接:嵌入式开发本质上是在资源受限的硬件环境里写软件,任何一个底层知识点没吃透,都可能在生产环境酿成线上问题。比如一个结构体对齐的误判会导致通信协议解析错位,一个对volatile的忽视会让中断共享变量在多线程中出现野值。笔试看起来在考概念,实际上是在筛掉那些只背API、不懂原理的人。

我对这份题集的整体评价是:难度中等偏上,但胜在范围广、细节深。没有特别偏门的硬件电路题,也没有特别艰深的算法题,重点放在"基础是否扎实"和"有没有踩过坑"这两个维度。这两点正是嵌入式软件工程师日常工作最需要的素质。

2. 核心知识点拆解:C语言与内存管理

2.1 指针与数组的关系

这套题里,指针和数组几乎必考,常见出法有这几种:

  • sizeof一个数组名和sizeof一个指针变量的区别。
  • 数组名作为函数参数传递后退化为指针。
  • 二维数组的指针偏移计算。
  • char *pchar p[]的区别。

我当年复习时有个体会,很多同学做错不是不知道定义,而是没有在头脑中建立"内存图景"。比如下面这个典型例子:

char str[] = "hello"; char *p = "hello"; printf("%d %d\n", sizeof(str), sizeof(p));

在32位系统上,str是数组,sizeof(str)是6(5个字符加一个结尾符),p是指针,sizeof(p)是4。这个题几乎每年笔试都会出现,但错误率一直很高。原因就是很多人把数组和指针混为一谈。数组名在大多数表达式中会退化为指向首元素的指针,但在sizeof&操作符下不退化。这一点必须记牢,因为嵌入式面试官极爱追问。

实际工程里,指针数组和数组指针也容易搞混。int *a[10]是"指针的数组",int (*a)[10]是"数组的指针"。在解析通信协议帧、处理二维矩阵时,这两种类型用错直接编译报错或者内存越界。

2.2 结构体字节对齐与内存布局

结构体对齐是嵌入式笔试的保留节目,因为它在实际项目中的影响太明显了。通信协议、硬件寄存器映射、文件头结构,全都依赖结构体的内存布局。

题集里这类题一般会给一个结构体然后问sizeof

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

在32位系统、默认4字节对齐下,a占1字节,填充3字节,b占4字节,c占1字节,再填充3字节,总大小12字节。如果我把成员顺序换一下:

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

同样三个成员,大小却是8字节。这里面的核心规则就一句话:每个成员的偏移量必须是其自身对齐数的整数倍,结构体总大小必须是最大对齐数的整数倍。嵌入式工程师经常手动调整成员顺序来压缩结构体体积,这在资源紧张的MCU上非常实用。

实际嵌入式场景里,还有更进阶的玩法:用#pragma pack(1)__attribute__((packed))来取消对齐,让结构体紧凑排列,通常用于网络协议或者存储格式。但这会带来一个问题:非对齐访问在某些架构上会触发硬件异常,或者降低访问效率。笔试可能不会考到这么深,但面试时聊到这里,能把这个代价说出来,会很加分。

2.3 大小端模式

考察大小端的题目在这套题里也有出现。大端模式是高位字节存低地址,小端模式是低位字节存低地址。x86 是小端,ARM 默认也是小端(可以切换),而网络字节序统一是大端。

常见的代码题是这样:

unsigned int x = 0x12345678; unsigned char *p = (unsigned char *)&x; printf("%x %x %x %x\n", p[0], p[1], p[2], p[3]);

在小端机器上输出是78 56 34 12。很多新手第一次在这里翻车,是因为直觉上认为内存里的顺序应该和书写顺序一致。在实际开发中,MCU 和传感器之间通过 SPI/I2C 通信,协议里明确规定字节序,如果主控和从机字节序不一致,读出来的数据就会面目全非。

有一个判断大小端的经典程序也需要掌握:

int is_little_endian(void) { int x = 1; return *(char *)&x; }

返回1是小端,返回0是大端。这个函数看起来简单,但里面包含了指针强转、内存视图切换两个核心概念,面试官喜欢让候选人现场写。

2.4 位操作与位域

嵌入式开发离不了位操作,笔试常考的是用位运算实现特定功能:

  • 将某位置1:x |= (1 << n)
  • 将某位清0:x &= ~(1 << n)
  • 翻转某位:x ^= (1 << n)
  • 取某位的值:(x >> n) & 1

这些操作在寄存器配置里最常用。例如操作 GPIO 的输出寄存器,要单独把第5引脚拉高而不影响其他引脚,就可以这样做。

还有一类题目考位域:

struct status { unsigned int a : 1; unsigned int b : 3; unsigned int c : 4; };

位域在嵌入式里常用于硬件寄存器定义,比如把一个32位寄存器按功能拆成多个字段。但面试官心里很清楚,位域是跨编译器行为最容易不一致的特性之一,位域的内存分配顺序、对齐方式在不同编译器上可能不同,因此在可移植性要求高的代码里,很多团队宁可自己写宏定义加位运算,也不用位域。答题时能主动提到这一层,会让面试官觉得你不仅有知识,还有工程判断力。

2.5 关键字:const、static、volatile

这组关键字是嵌入式面试出现频率最高的内容,尤其是在选择题里用来考概念辨析。

const修饰变量的含义是该变量在作用域内不可被修改。但要注意,const修饰的是指针本身还是指针指向的内容。我在复习时总结了一个口诀:"const*左边修饰指向的内容,在*右边修饰指针本身"。

static有两个核心作用:修饰局部变量时,变量生命周期延长到程序结束,且只初始化一次;修饰函数和全局变量时,限制其作用域在当前文件。在嵌入式项目中,static局部变量常用于函数内需要保持状态值的场合,比如按键扫描的去抖计数器。

volatile是嵌入式里最重要的关键字之一,笔试里考的是:告诉编译器该变量可能被外部意外修改,禁止将其优化到寄存器中,每次都必须从内存地址读取。它主要用来修饰三类东西:硬件寄存器、中断服务程序中修改的全局变量、多线程间共享的变量。

这里有一个经典陷阱题:

int flag = 0; void isr(void) { flag = 1; } while (flag == 0) { // 等待中断 }

如果flag没有加volatile,编译器在优化时可能将flag的读取操作提到循环外,导致循环永远出不来。这就是笔试中经常出现的考察点。在软件上,很多线上问题最终定位到missing volatile,都是血泪教训。

3. 操作系统、数据结构与计组考点精讲

3.1 进程与线程:概念辨析

这套题里操作系统部分占了不小比例,进程和线程的区别就是必考内容。典型问题包括:进程是资源分配的基本单位,线程是CPU调度的基本单位。同一个进程下的线程共享地址空间、文件描述符,但各有独立的栈和寄存器上下文。进程之间互相隔离,通信需要借助IPC。

嵌入式里还有一个容易被轻视的概念叫"并发与并行的区别":并发是多个任务在宏观上同时推进,但微观上可能交替执行;并行是多个核心在同一个时刻真正同时执行多个任务。笔试会考概念,面试时则可能让你结合一个实际的双核MCU项目谈如何分配任务。

线程间同步也是重点。互斥锁、信号量、条件变量、自旋锁的使用场景,几乎是嵌入式软件笔试的固定题组。

这里我给出一个速查表,方便复习时对照:

同步机制适用场景注意事项
互斥锁保护临界区,防止多个线程同时写注意死锁,避免嵌套加锁
信号量资源计数、任务同步二值信号量和互斥锁不完全等价
条件变量等待某个条件满足再继续必须配合互斥锁使用
自旋锁临界区极短且不会睡眠会忙等,长临界区不适合

3.2 调度算法与死锁

操作系统调度算法的选择题,考察的核心是理解各算法的出发点和代价。先来先服务看重公平性但短任务可能等很久;短作业优先能降低平均等待时间,但长任务可能饿死;时间片轮转保证响应性但切换开销大;优先级调度实时性好,但需要处理优先级反转。

嵌入式实时操作系统里最常见的是优先级抢占式调度,FreeRTOS、RT-Thread、uC/OS 都采用这种策略。题目如果给你三个任务的优先级和CPU时间,让你计算某个任务的完成时间,你需要先画出调度时序图,再逐步计算。这种题没有技巧,多练几遍就熟了。

死锁的四个必要条件——互斥、持有并等待、不可剥夺、循环等待——也是必背内容。实际面试时更喜欢让你讲讲实际项目里怎么避免死锁。我自己的经验是:能不用锁就别用锁,能用原子操作解决的就用原子操作,必须用锁时保持一致的加锁顺序,并配合超时机制。

3.3 链表、栈、队列与二叉树

数据结构在嵌入式笔试中考察范围不大,但都是最常用的那几样。链表题几乎必考,尤其是单链表反转、判断链表是否有环、合并两个有序链表。这些题目可以直接在LeetCode上刷,但嵌入式岗位一般不会考太偏的题,把基础题刷熟就够。

还有一个高频考点是栈和队列的互相实现。两个栈模拟一个队列,或者两个队列模拟一个栈,这种题考察的是对数据结构的本质理解。另外,表达式中缀转后缀也是经典题,有些编译器或计算器模块里会用到。

二叉树部分主要考察遍历方式,前序、中序、后序、层序,以及根据两种遍历序列推导树结构。嵌入式开发里二叉树用得不算多,但红黑树是内核和很多RTOS定时器的底层实现,所以面试官有时候会问树的基本概念。

时间复杂度分析题也经常出现。比如链表在头部插入是O(1),在尾部插入要遍历到末尾是O(n);有序数组二分查找是O(logn),但如果插入数据是O(n)。不要死记结论,要会从实现角度推导。

3.4 中断、寄存器与Cache

计算机组成原理在嵌入式笔试里主要考察中断系统和存储层次。

中断相关的关键概念:中断向量表、中断优先级、中断服务函数不能执行耗时操作、需要处理临界区保护。有一类选择会给一个场景让你判断哪些操作不能在ISR里做,答案通常是打印、动态内存分配、长时间的延时。

中断嵌套与抢占也是常考点。在Cortex-M系列处理器里,NVIC支持中断嵌套,高优先级中断可以打断低优先级中断。题目如果问你关中断的粒度问题,核心考量是:关中断时间太长会导致实时性下降,太短又保护不了临界区。

Cache的考察点集中在局部性原理、Cache命中率、写回与写直达策略。嵌入式工程师在做性能优化时,经常需要考虑数据和指令在Cache中的布局。

另外,volatile与硬件寄存器也在这里再次交汇。映射到地址空间的寄存器必须用volatile声明,防止编译器优化。笔试里的形式通常是给一段内存映射寄存器操作代码,让你判断哪里有问题。

4. 网络、Linux与嵌入式实战方向

4.1 TCP/IP 基础与三次握手

TCP/IP协议栈在嵌入式网络开发中是绕不开的。选择题经常出现:TCP三次握手的状态转换、第四次挥手为什么要TIME_WAIT。

三次握手的细节要能默写:客户端发送SYN,服务端回复SYN+ACK,客户端再回复ACK。还有一个常见变体:为什么需要三次而不是两次?核心原因是防止失效的连接请求突然到达服务端,导致资源浪费。

四次挥手里面有一个高频追问:为什么客户端最后要等2MSL?原因有两点:一是保证最后一个ACK能到服务端,万一丢了可以重传;二是让旧连接的所有报文在网络中消失,避免干扰新连接。这个点如果面试时能答出两层含义,就很完整了。

IP地址相关的计算题,比如给定IP和子网掩码求网络号、广播地址,这类题偶尔出现。计算方法很简单:IP和掩码按位与得到网络号,主机位全置1得到广播地址。

4.2 Linux 常用命令与文件系统

嵌入式开发和Linux密不可分,笔试会考常用命令的用法。比如:

  • ls列出文件,cd切换目录,pwd显示当前路径。
  • cp复制,mv移动或重命名,rm删除。
  • cat查看文件内容,head/tail查看文件头部/尾部,grep搜索文本。
  • find查找文件,chmod修改权限,ps查看进程。
  • top查看系统资源,df/du查看磁盘使用情况。
  • ifconfig/ip addr查看网络接口,ping测试连通性。
  • tar打包解包,make编译工程。

有一类题目会问grepfind的区别:grep在文件内容中搜索文本,find按文件名或属性查找文件。这个区分在实际使用中很常见,但在笔试中被单独拿出来点名时,还是有一批人会混淆。

文件系统部分常考的是嵌入式Linux里常用的文件系统类型:jffs2yaffs2ubifs是Flash上常用的日志型文件系统,ext4是PC上标准的磁盘文件系统。题集的考察思路通常是让你根据"读写均衡、掉电安全、NAND Flash"这些关键词选合适的文件系统。

4.3 交叉编译与驱动基础

嵌入式岗位的笔试有一个区别于纯后端开发的特色:交叉编译和驱动模型。

交叉编译考的是概念,比如:arm-linux-gnueabihf-gcc在x86主机上编译出ARM架构的程序,这个编译过程叫交叉编译。题目会问为什么不能直接在ARM板子上编译——因为板子性能弱、工具链齐全度差、存储空间有限。

驱动部分的题目主要看有没有基础概念。字符设备、块设备、网络设备的区别,platform总线的作用,设备树文件的作用。这些题目如果没做过Linux驱动开发,可能只能靠背概念。但从我的经验看,笔试考得不会太深,能把openreadwriteioctl这些系统调用到驱动函数的流转路径讲清楚,就已经能胜过绝大多数候选人了。

4.4 常见笔试代码题:解决一个实际场景

有时候客观题会结合代码片段来考。比如下面这种:

int func(void) { static int count = 0; count++; return count; }

连续调用三次,返回值分别是1、2、3。因为static局部变量只初始化一次,存储于静态存储区。

还有一类考察位操作和宏定义的组合:

#define SET_BIT(x, n) ((x) |= (1 << (n))) #define CLEAR_BIT(x, n) ((x) &= ~(1 << (n)))

题目往往以选择题形式出现,要求判断宏在参数为表达式时是否有副作用。比如SET_BIT(a, i++)i++会被展开两次,产生未定义行为。这是宏定义里最经典的坑,考察的是对预处理机制的理解深度。

5. 常见问题与备考避坑技巧

5.1 高频易错点复盘

结合这套题集和这些年秋招观察到的普遍问题,我把高频易错点整理成了下面的表格,复习时可以直接对照检查:

易错点正确的理解常见错误
sizeof数组名返回整个数组占用的字节数误认为是指针大小
结构体对齐总大小要满足最大对齐数的整数倍漏算尾部填充字节
volatile禁止编译器优化到寄存器认为只是普通修饰符
数组名作参退化为指向首元素的指针以为仍保留数组长度信息
大小端小端低位字节在低地址受到书写顺序干扰
TCP挥手TIME_WAIT确保最后一个ACK可靠忽略2MSL的语义
static局部变量生命周期为程序运行期误以为作用域也全局化

5.2 多选题:选错一个就全错

顺丰这套题里有多选题,多选题的残酷之处在于选错一个就全错,所以策略与单选题不一样。我的建议是:遇到不确定的选项,宁可少选,不要冒险多选。如果是拿不准的选项,根据概念包含关系去判断。比如"下列关于栈的描述正确的是",只要有一个选项描述的是队列特性,它肯定错。

多选题里最阴险的考法是把两个概念放在一起混淆。比如一个选项说"数组可以在O(1)时间内删除中间元素",这个选项明显是链表特性的错位。做这类题的关键是对每个选项单独判断,而不是靠排除法找正确答案。

5.3 刷题之外:把知识点串成体系

我不建议纯粹靠刷题来通过嵌入式笔试,因为客观题考察的知识点虽然固定,但每年的问法和组合方式都在变。真正稳妥的复习方式是把知识点连成网,比如C语言的内存管理可以和操作系统的内存管理联系起来复习,结构体对齐可以延伸到大端小端和通信协议解析,volatile可以串联到中断服务和多线程同步。

我复习时习惯画一张思维导图,从"内存"出发,延伸到栈、堆、静态存储区、寄存器映射,再延伸到指针、数组、结构体、位域、volatile、const。从"执行"出发,延伸到进程线程、任务调度、中断、系统调用、驱动模型。这样笔试考任何一个点,我都能快速定位到它所属的知识群。

5.4 嵌入式学习路线的建议

结合这套题集的覆盖面,给正在准备秋招的同学一个学习路线参考。如果是从零基础开始,顺序可以这样安排:

  1. 先打C语言基础,做到能用指针操作内存,理解数组和结构体的内存布局。
  2. 学数据结构,重点线性表和二叉树,能手写链表反转和树的遍历。
  3. 学操作系统原理,理解进程线程、调度、同步互斥、死锁。
  4. 学计算机组成原理,重点中断、存储层次、汇编基础。
  5. 学Linux基础,掌握常用命令和文件系统概念。
  6. 选一块开发板做实战,推荐STM32或全志/瑞芯微平台的Linux开发板,跑通一个带中断、定时器、通信接口的综合项目。
  7. 有余力再深入Linux驱动,理解字符设备框架和设备树。

这套路线走下来,刷上一两套真题集,笔试通过率会显著提升。很多同学只刷题不补体系,遇到原题能答对,换一个问法就懵,这就是知识不成网导致的。

6. 从笔试到面试:如何把客观题转化为加分项

笔试结束往往只是第一关,紧接着的面试才是真正区分高下的地方。我的体会是,客观题里出现的知识点,面试官极有可能顺着追问。比如笔试里考了结构体对齐,面试官可能让你现场设计一个通信协议帧结构,问你怎么安排成员顺序、要不要加CRC、怎么处理字节序。

我在这里分享一个万能思路:把笔试知识点翻译成项目经历。简历上写"熟悉Linux驱动开发"的人很多,但能讲清楚"设备树里如何描述一个SPI设备"的人就少很多。面试官想听到的不是名词,而是你对取舍和原理的理解。

举例来说,如果你在笔试里遇到了大小端的题,那么面试时你可以主动说:"我在项目里用I2C读取传感器数据时,发现传感器的输出是大端格式,而MCU是小端,所以我在驱动里做了一次字节交换处理,并且封装成了统一的接口。" 这样一段话,就把大小端这个知识点从"会做题"提升到了"有实战经验"。

再举一个例子,笔试题里常考volatile,面试时你可以结合一个实际的BUG来谈:某个变量在中断里修改,主循环里没有加volatile导致优化后读取不到新值,排查了一整个下午才发现是编译器优化的问题。这种真实经历远比背书上的定义更能打动面试官。

还有一个很多同学会忽略的点:客观题里的数据结构题,面试时可能会要求现场手写代码。链表反转、判断环入口、两个栈实现队列,这些题务必做到能脱稿手写且保证语法正确。我的习惯是面试前在草稿纸上反复默写这些经典代码,写到条件反射的程度。

最后提醒一句,笔试中做错的原题也要复盘。我当时把做错的每道题都整理成了一个错题本,在旁标注错误原因和正确的推导过程。秋招后期我会翻看这些记录,很多易错点会在不同公司的题目里反复出现,比如结构体对齐、进程和线程的区别、TCP的TIME_WAIT,几乎每家公司都考。

这套2019年的题集虽然年份有些久,但嵌入式岗位的考察内核却没怎么变。把基础打扎实、把错题复盘到位、把知识点串成网络,秋招笔试这一关并没有想象中那么难。希望这篇文章能帮你理清复习思路,少走一些弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询