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开始,慢慢走到今天的。你也能行。