3、C语言基础回顾:指针、结构体、位运算、内存管理——嵌入式开发中的高频用法
好,咱们直接进入正题。很多初学者觉得C语言就是大学里考个试就完事了,但到了嵌入式开发里,C语言才是真正跟你朝夕相处的伙伴。指针、结构体、位运算、内存管理——这四个东西,说白了就是嵌入式开发的“四根柱子”。今天我就带你把这四根柱子重新打磨一遍。
3.1 指针:别怕,它就是地址
指针这东西,我见过太多人一上来就晕。其实你想想看,指针就是个存放地址的变量。就像你家门牌号,你不需要把整个房子搬来搬去,只需要知道门牌号就能找到它。
核心理解:指针就是地址,地址就是编号。在嵌入式里,我们经常直接操作寄存器地址,这时候指针就是你的“万能遥控器”。
// 直接操作寄存器地址——嵌入式里太常见了
#define GPIO_BASE 0x40021000
volatile uint32_t *gpio_odr = (volatile uint32_t *)GPIO_BASE;
// 点亮LED
*gpio_odr |= (1 << 5); // 把第5位置1
我个人习惯在定义指针时,一定先初始化成NULL。为什么?因为野指针太可怕了。我在项目中遇到过,一个同事没初始化指针就直接用,结果程序跑飞了,查了两天才找到原因。
避坑指南:我曾经因为指针类型不匹配,把一个uint8_t*强转成uint32_t*来读数据,结果字节序全乱了。记住:指针的类型决定了它怎么解释内存里的数据。
3.2 结构体:把数据打包,清爽又高效
结构体在嵌入式里,说白了就是“数据打包器”。你想想看,一个传感器可能有温度、湿度、气压三个数据,你难道要定义三个单独的变量?那多乱啊。
// 传感器数据结构体
typedef struct {
float temperature; // 温度
float humidity; // 湿度
float pressure; // 气压
uint8_t status; // 状态标志
} SensorData_t;
SensorData_t sensor;
sensor.temperature = 25.6;
sensor.humidity = 60.2;
嗯,这里要注意:结构体在内存里是有对齐的。比如你定义了一个char和一个int,它可能不是紧挨着放的,中间会填充字节。为什么?因为CPU读数据时,对齐的地址效率更高。
| 成员类型 | 大小(字节) | 对齐要求 |
|---|---|---|
| char | 1 | 1字节对齐 |
| short | 2 | 2字节对齐 |
| int | 4 | 4字节对齐 |
| 指针 | 4(32位) | 4字节对齐 |
小技巧:如果你想把结构体打包得紧凑一点,可以用 __attribute__((packed))。但代价是访问速度会变慢。我在做低功耗设备时经常用,省一个字节是一个字节。
3.3 位运算:嵌入式里的“瑞士军刀”
位运算这东西,在嵌入式里简直无处不在。为什么?因为寄存器里每个位都有特定含义,你要操作某个位,就得用位运算。
// 常见的位操作宏
#define SET_BIT(reg, bit) ((reg) |= (1 << (bit)))
#define CLR_BIT(reg, bit) ((reg) &= ~(1 << (bit)))
#define GET_BIT(reg, bit) (((reg) >> (bit)) & 0x01)
// 使用示例
uint8_t status_reg = 0x00;
SET_BIT(status_reg, 3); // 第3位置1
CLR_BIT(status_reg, 1); // 第1位清0
你想想看,一个8位的寄存器,可以同时控制8个LED或者8个开关。如果用变量来存,你得定义8个变量,多浪费。位运算就是让你用最小的代价做最多的事。
高频用法:在I2C、SPI等通信协议里,经常需要把多个位拼成一个字节。比如一个传感器的数据是12位的,你得用位运算把它拆成两个字节发送。我刚开始做时,经常搞错移位方向,后来养成了先画图再写代码的习惯。
3.4 内存管理:别让内存“泄漏”了
嵌入式系统的内存是有限的,可能就几KB或几十KB。你想想看,如果代码里有个内存泄漏,跑几天后系统就崩了,那多可怕。
我个人习惯是:能用静态分配就不用动态分配。为什么?因为静态分配在编译时就确定了,不会出现运行时分配失败的情况。
// 静态分配——安全可靠
#define BUFFER_SIZE 256
static uint8_t rx_buffer[BUFFER_SIZE];
// 动态分配——谨慎使用
uint8_t *temp_buffer = (uint8_t *)malloc(128);
if (temp_buffer != NULL) {
// 使用缓冲区
free(temp_buffer); // 一定要释放!
temp_buffer = NULL; // 避免野指针
}
避坑指南:我曾经在一个项目中用了malloc,但忘记free了。结果系统跑了72小时后,内存耗尽,直接死机。从那以后,我定了个规矩:谁malloc谁free,而且必须在同一个函数里完成。
还有一个常见问题:栈溢出。嵌入式里的栈空间通常很小,可能就1KB。如果你在函数里定义了一个大数组,比如 uint8_t big_array[1024],那栈可能直接就爆了。
建议:大数组用静态分配或者全局分配。递归调用也要小心,每递归一次就消耗一层栈空间。我一般递归深度不超过3层,超过就用循环代替。
3.5 总结一下
指针、结构体、位运算、内存管理——这四个东西,你掌握了,嵌入式C语言就算入门了。说白了,它们就是你和硬件之间的“翻译官”。指针让你直接操作地址,结构体让你把数据组织得井井有条,位运算让你精打细算每个比特,内存管理让你在有限的资源里游刃有余。
嗯,最后说一句:多写、多练、多踩坑。我当年也是从点亮一个LED开始,慢慢走到今天的。你也能行。