3. C语言基础与数据结构:指针、结构体、链表、位操作
本章覆盖嵌入式开发中最高频的C语言考点。指针和结构体是寄存器映射、内存管理的基石;链表用于动态任务队列;位操作是驱动开发的必备技能。面试中通常要求你手写代码并解释内存布局。下面先给出知识速查表,再用框架图和问答卡片拆解典型考题。
📊 核心知识点速查表
| 知识点 | 核心概念 | 常用操作 | 常见陷阱 |
|---|---|---|---|
| 指针 | 地址 + 类型(决定步长) | 取址 &、解引用 *、指针运算 | 野指针、指针类型不匹配 |
| 结构体 | 自定义复合类型,成员连续存储 | 定义、初始化、成员访问(. / ->) | 内存对齐(padding) |
| 链表 | 动态节点链接,无连续存储 | 插入、删除、遍历、反转 | 断链、内存泄漏 |
| 位操作 | 按位 &、|、^、~、<<、>> | 置位/清零/翻转/提取位 | 优先级错误、有符号数移位 |
❓ 指针与数组有什么区别?
面试官考察点: 是否理解指针是地址变量,数组是连续内存块;sizeof 结果的差异;函数传参时的退化行为。
回答框架: ① 概念对比(存储方式、操作)→ ② sizeof 示例 → ③ 数组名在表达式中退化为首元素指针 → ④ 两者可互换的场景(下标运算实质)→ ⑤ 避坑提醒。
示例回答: 数组是一段连续内存,sizeof 数组得整个大小;指针只占 4/8 字节。函数传数组时退化为指针,sizeof 失效。但 arr[i] 等价于 *(arr+i),故两者可互换使用,但指针可重新赋值,数组名不行。
可能追问: 二维数组传参怎么写?指针数组和数组指针的区别?
避坑提醒: 别说“数组就是常量指针”——数组名不是指针变量,只是能隐式转换;面试中常考 sizeof(指针) 与 sizeof(数组) 的差别。
❓ 结构体内存对齐如何计算?
面试官考察点: 对齐规则(成员对齐、整体对齐)、pragma pack 的作用、对齐对嵌入式存储和访问效率的影响。
回答框架: ① 对齐原因(CPU 访问效率)→ ② 三个规则:成员首地址偏移 = min(自身大小, 默认对齐值) 的整数倍 → ③ 整体大小 = 最大成员对齐值的整数倍 → ④ 举例 int8+int32 浪费 3 字节 → ⑤ 优化:调整成员顺序或使用 __attribute__((packed))。
示例回答: 比如 struct { char a; int b; }; 默认 4 字节对齐,a 占 1,b 需从偏移 4 开始,所以总大小 8。若调换顺序,b 从 0 开始,a 在 4,大小变成 5+3 padding=8,实际未节省,但若用 packed 则 5 字节,但访问变慢。
可能追问: 嵌入式 MCU 中一般用几字节对齐?如何用结构体映射寄存器?
避坑提醒: 不要死记硬背数字,要能推导;注意 32 位 MCU 默认对齐值通常是 4 字节;强调位域的对齐规则更复杂。
❓ 手写一个单向链表插入操作(头插法)
面试官考察点: 指针操作熟练度、内存分配正确性、边界条件(空链表)、是否考虑返回新头指针。
回答框架: ① 定义节点结构体(val + next)→ ② 检查传入的头指针 → ③ 新建节点,分配内存 → ④ 新节点 next 指向原头 → ⑤ 返回新节点作为新头。
示例回答:
struct Node {
int val;
struct Node *next;
};
struct Node* insertHead(struct Node *head, int v) {
struct Node *new = malloc(sizeof(struct Node));
if(!new) return head; // 内存分配失败
new->val = v;
new->next = head;
return new;
}
可能追问: 尾插法怎么写?如何反转链表?如何检测环?
避坑提醒: 务必检查 malloc 返回值;头指针可能为 NULL(空链表),新节点 next = NULL 正确;面试注意返回新头,不修改原头指针(除非用二级指针)。
📁 项目案例:基于 STM32 的智能温控系统
业务背景: 为小型实验室设计一个实时温控器,使用 DS18B20 传感器采集温度,PID 算法控制加热继电器,通过 OLED 显示温度曲线,并支持串口命令设置目标温度。
规模指标: 代码量约 3 千行;使用 3 个定时器、2 个外部中断;温度采样周期 100ms,PID 控制周期 500ms;支持 5 个串口命令。
技术栈: Keil MDK、STM32 HAL 库、C99、内部 Flash 存储参数、FreeRTOS(可选任务管理)。
核心难点: ① 使用结构体封装 DS18B20 操作(初始化、读 ROM、温度转换),方便移植;② 用指针函数回调实现多传感器切换;③ 使用单向链表管理串口命令 ID 和对应的执行函数;④ 位操作解析 DS18B20 的 16 位温度寄存器(符号位 + 精度)。
面试高频追问:
- 结构体内存对齐对这个系统的存储有什么影响?如何确保 regmap 结构体不引入 padding?
- 链表在命令管理中的具体优势?为什么不用数组?
- 你写的温度数据合并代码中,如何进行位的提取和符号扩展?
- 请画一下 DS18B20 读时序的 bit-bang 代码,用到哪些位操作?
可迁移回答话术: “在这个项目中,我大量使用了结构体来抽象外设,例如将 DS18B20 的配置参数、状态、回调函数打包成一个结构体实例,这样更换传感器时只需替换初始化函数指针。对于串口命令,我设计了一个命令节点链表,插入新命令只需分配节点并挂在尾部,比固定数组更灵活且节省内存。位操作主要用于解析温度数据的高低位和 CRC 校验,这部分我专门封装了一个位操作工具模块,支持置位、位域提取,提高了代码可读性和复用性。”