C 语言嵌入式进阶篇二:高级指针与链表实战,搞定单片机缓存、队列与数据管理
一、前言 & 本期目标
上一篇我们学习了嵌入式必备的有 / 无符号类型、位运算、宏、const/volatile、函数指针、标准 GPIO 框架,完成了从入门 C 到嵌入式工程 C 的思维转换。
在实际单片机项目中:串口收发、CAN 总线、传感器数据采集、协议解析、任务状态管理,都会遇到批量临时数据存储、动态数据管理场景。普通固定数组长度死板、空间浪费,这时就必须用到:高级指针 + 链表。
本期继续深耕嵌入式专用 C 语法,全程不做 PC 娱乐案例,所有代码、用法、场景均对标 51/STM32、FreeRTOS、通信缓冲区、设备链表。
本期学习目标
- 吃透结构体指针(嵌入式使用频率最高的指针);
- 分清指针数组、数组指针,解决硬件参数表、ID 列表开发问题;
- 入门多级指针,看懂复杂工程传参;
- 掌握单向链表基础:节点定义、增删查改,用于设备管理;
- 实战 环形缓冲区(FIFO 队列)—— 单片机串口 / 通信标配;
- 全部案例落地硬件场景,养成嵌入式标准编码习惯。
前置基础:结构体、基础指针、函数、位运算、宏定义
第一部分:嵌入式高频 —— 结构体指针
结构体是外设、参数、设备的载体,结构体指针是工程传参、访问成员的主流写法。
1. 基础回顾与指针访问成员
普通结构体用 . 访问成员;结构体指针必须用 -> 箭头访问成员(嵌入式代码随处可见)。
// 统一嵌入式标准类型
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
// 模拟一个外设结构体:简易传感器
typedef struct
{
u16 addr; // 设备地址
u16 data; // 采集数据
u8 status; // 设备状态
}Sensor_Def;
int main(void)
{
Sensor_Def sen1;
Sensor_Def *p_sen = &sen1; // 结构体指针指向实体
// 指针访问成员:必须使用 ->
p_sen->addr = 0x01;
p_sen->data = 1024;
p_sen->status = 1;
return 0;
}
2. 结构体指针做函数参数(工程标准写法)
嵌入式函数传递结构体,一律传指针,不传整个结构体。原因:
- 结构体数据量大,传值会拷贝整份数据,浪费栈空间、拖慢速度;
- 传指针直接操作原数据,效率极高,是单片机硬性规范。
#include <stdio.h>
typedef unsigned char u8;
typedef unsigned short u16;
typedef struct
{
u16 addr;
u16 data;
u8 status;
}Sensor_Def;
// 函数接收结构体指针
void Sensor_SetData(Sensor_Def *p_sen, u16 val)
{
if(p_sen == NULL) // 空指针判断,嵌入式必写防护
{
return;
}
p_sen->data = val;
}
int main(void)
{
Sensor_Def sen;
Sensor_SetData(&sen, 2048);
return 0;
}
嵌入式铁律:大型结构体、外设参数、设备信息,函数传参只传指针,并增加空指针判断防崩溃。
第二部分:指针数组 vs 数组指针(硬件查表必备)
这一组概念极易混淆,在硬件 ID 表、命令表、状态表、协议列表中大量使用。
1. 指针数组:存放多个指针(硬件地址 / ID 表常用)
定义:类型 *数组名[]本质:一个数组,里面每一个元素都是指针。
嵌入式场景:多设备地址表、多字符串提示、多命令 ID。
#include <stdio.h>
// 指针数组:存放多个字符串(提示信息表)
const char *MsgTable[] =
{
"Device Ready",
"Data Receive",
"Error Occur",
"System Reset"
};
int main(void)
{
// 遍历查表
printf("%s\n", MsgTable[0]);
printf("%s\n", MsgTable[1]);
return 0;
}
搭配 const 修饰,表格存入 Flash,不占用 RAM,单片机最优写法。
2. 数组指针:指向整个数组的指针(底层硬件偏移、批量寄存器)
定义:类型 (*指针名)[长度]嵌入式场景:寄存器块、连续内存区域、批量数据块操作。
typedef unsigned char u8;
u8 buf[8] = {1,2,3,4,5,6,7,8};
u8 (*p_buf)[8] = &buf; // 数组指针,指向一整个数组
快速区分口诀(记牢不踩坑)
char *arr[]→ 指针数组:数组里装指针(查表、列表用)char (*arr)[]→ 数组指针:指针指向数组(整块内存操作)
第三部分:多级指针入门(看懂复杂工程传参)
多级指针 = 指针的指针,嵌入式工程、二级函数、动态传参偶尔会遇到,不用深挖,看懂即可。
基础示例
typedef unsigned char u8;
void Test(u8 **p)
{
**p = 0x55;
}
int main(void)
{
u8 val = 0;
u8 *p = &val;
Test(&p); // 传递指针地址
return 0;
}
应用场景:函数内部修改外部指针本身的指向、动态缓存分配、二级回调,STM32 标准库、第三方协议库中经常出现。
第四部分:单向链表(设备 / 节点管理)
数组长度固定,链表可以动态增减节点,适合:多设备挂载、动态传感器、多路外设管理、离线设备列表。
1. 链表节点定义(嵌入式标准节点)
每个节点 = 设备数据 + 下一个节点指针
typedef unsigned char u8;
typedef unsigned short u16;
// 链表节点:模拟一个从设备
typedef struct Node
{
u8 dev_id; // 设备ID
u16 dev_data; // 设备数据
struct Node *next; // 指向下一个节点
}Node_TypeDef;
2. 基础操作:创建节点、尾插、遍历
完整极简嵌入式链表 Demo:
#include <stdio.h>
#include <stdlib.h> // malloc / free
typedef unsigned char u8;
typedef unsigned short u16;
typedef struct Node
{
u8 dev_id;
u16 dev_data;
struct Node *next;
}Node_TypeDef;
// 链表头节点
Node_TypeDef *head = NULL;
// 尾插法:新增设备节点
void Node_Add(u8 id, u16 data)
{
Node_TypeDef *new_node = (Node_TypeDef *)malloc(sizeof(Node_TypeDef));
if(new_node == NULL)
{
return;
}
new_node->dev_id = id;
new_node->dev_data = data;
new_node->next = NULL;
if(head == NULL)
{
head = new_node;
}
else
{
Node_TypeDef *p = head;
while(p->next != NULL)
{
p = p->next;
}
p->next = new_node;
}
}
// 遍历所有设备
void Node_Scan(void)
{
Node_TypeDef *p = head;
while(p != NULL)
{
printf("ID:%d Data:%d\n", p->dev_id, p->dev_data);
p = p->next;
}
}
int main(void)
{
Node_Add(0x01, 100);
Node_Add(0x02, 200);
Node_Add(0x03, 300);
Node_Scan();
return 0;
}
嵌入式使用说明
malloc动态内存:部分带操作系统 (FreeRTOS/RT-Thread) 单片机常用;- 若无操作系统、裸机单片机,一般改用静态链表(数组模拟链表),避免动态内存碎片;
- 用途:多路传感器、多个从机、多路 IO 设备统一管理。
第五部分:本期终极实战 —— 环形缓冲区 FIFO(单片机通信核心)
环形缓冲区(循环队列)是嵌入式开发必掌握项目:串口收发、CAN、网口、传感器数据流、UART 缓存全部基于它实现。
特点:
- 先进先出 FIFO
- 循环复用内存,不移动数据,效率极高
- 固定数组实现,裸机 / RTOS 通用
完整可运行代码(标准环形 FIFO)
#include <stdio.h>
#include <string.h>
// 嵌入式标准类型
typedef unsigned char u8;
typedef unsigned int u32;
// FIFO 缓存大小
#define FIFO_BUF_LEN 64
// 环形缓冲区结构体
typedef struct
{
u8 buf[FIFO_BUF_LEN]; // 缓存数组
u32 wr_ptr; // 写指针
u32 rd_ptr; // 读指针
}FIFO_TypeDef;
// 缓冲区初始化
void FIFO_Init(FIFO_TypeDef *fifo)
{
if(fifo == NULL) return;
memset(fifo->buf, 0, FIFO_BUF_LEN);
fifo->wr_ptr = 0;
fifo->rd_ptr = 0;
}
// 判断缓冲区是否满
u8 FIFO_IsFull(FIFO_TypeDef *fifo)
{
return ((fifo->wr_ptr + 1) % FIFO_BUF_LEN) == fifo->rd_ptr;
}
// 判断缓冲区是否空
u8 FIFO_IsEmpty(FIFO_TypeDef *fifo)
{
return fifo->wr_ptr == fifo->rd_ptr;
}
// 写入1字节数据
u8 FIFO_Write(FIFO_TypeDef *fifo, u8 dat)
{
if(FIFO_IsFull(fifo))
{
return 0; // 已满,写入失败
}
fifo->buf[fifo->wr_ptr] = dat;
fifo->wr_ptr = (fifo->wr_ptr + 1) % FIFO_BUF_LEN;
return 1;
}
// 读取1字节数据
u8 FIFO_Read(FIFO_TypeDef *fifo, u8 *p_dat)
{
if(FIFO_IsEmpty(fifo) || p_dat == NULL)
{
return 0; // 为空,读取失败
}
*p_dat = fifo->buf[fifo->rd_ptr];
fifo->rd_ptr = (fifo->rd_ptr + 1) % FIFO_BUF_LEN;
return 1;
}
// 测试主函数
int main(void)
{
FIFO_TypeDef fifo;
u8 i, tmp;
FIFO_Init(&fifo);
// 连续写入数据
for(i = 0; i < 10; i++)
{
FIFO_Write(&fifo, i);
}
// 循环读出数据
while(FIFO_Read(&fifo, &tmp))
{
printf("%d ", tmp);
}
return 0;
}
工程应用场景
- 串口接收缓存:中断里写 FIFO,主循环读 FIFO,中断极简、系统稳定;
- CAN 总线数据缓存;
- 传感器连续采样数据流;
- FreeRTOS 消息队列底层原型。
第六部分:嵌入式 C 指针 & 链表 高频坑点总结
- 结构体指针必须用
->,普通结构体用.,不要混用; - 函数接收指针务必做空指针判断,单片机空指针直接死机;
- 传结构体优先传指针,禁止整结构体传值,节省栈空间;
- 指针数组 / 数组指针 书写格式不要写错,查表、寄存器操作极易出错;
- 裸机单片机慎用
malloc/free,容易产生内存碎片,优先静态数组 / 静态链表; - 环形缓冲区读写指针判断逻辑不要写反,避免数据丢失、重复读取。
第七部分:本篇总结
- 掌握结构体指针,看懂并编写嵌入式标准外设函数;
- 分清指针数组、数组指针,搞定硬件查表、ID 列表、字符串列表;
- 理解多级指针,能阅读复杂第三方库、标准库代码;
- 学会单向链表,实现多设备、动态节点管理;
- 吃透 环形 FIFO 缓冲区,掌握串口 / CAN 通信底层核心;
至此,嵌入式 C 语言指针体系全部打通。从基础指针 → 函数指针 → 结构体指针 → 链表 → 队列,覆盖 90% 单片机 C 指针用法。
下期预告(继续嵌入式路线)
下一期主题:
嵌入式 C 高级专题:多文件编程 + 工程模块化 + 条件编译 + 头文件规范内容:
- 头文件
.h/ 源文件.c分工规范 - 防止头文件重复包含
#ifndef / #define / #endif - 条件编译
#ifdef(版本裁剪、硬件适配) - 多文件工程拆分(GPIO、UART、KEY 分文件管理)
- 完整单片机分层工程模板(驱动层、应用层分离)
学完即可搭建标准单片机工程架构,正式进入项目开发阶段。
更多推荐


所有评论(0)