1. 结构体:自定义数据类型的艺术
在嵌入式开发中,结构体是最基础也最重要的复合数据类型之一。它允许我们将不同类型的数据组合成一个有机整体,这在硬件寄存器映射、协议封装等场景中尤为常见。
1.1 结构体定义的三重奏
结构体定义有三种经典形式,各有适用场景:
c复制// 形式1:先定义类型再声明变量(最规范)
struct student {
char name[20];
int age;
float score;
};
struct student stu1;
// 形式2:定义同时声明变量(适合局部使用)
struct teacher {
char name[20];
int age;
} tea1, tea2;
// 形式3:匿名结构体(一次性使用)
struct {
int x;
int y;
} point;
实际开发建议:在头文件中采用第一种形式定义全局可用的结构体,局部临时结构体可考虑第三种形式。
1.2 内存对齐:性能与空间的博弈
结构体大小计算是嵌入式笔试的必考题。理解对齐规则对内存敏感型应用至关重要:
c复制struct example1 {
int a; // 4字节 @0-3
char b; // 1字节 @4
short c; // 2字节 @6-7(5被填充)
}; // 总大小8字节(4+1+1填充+2)
struct example2 {
char a; // 1字节 @0
int b; // 4字节 @4-7(1-3填充)
short c; // 2字节 @8-9
}; // 总大小12字节(1+3填充+4+2+2填充)
对齐规则详解:
- 成员对齐:首地址需是其类型大小的整数倍
- 结构体对齐:总大小需是最大成员大小的整数倍
- 可通过
#pragma pack(n)修改对齐系数(嵌入式常用pack(1))
1.3 结构体操作实战技巧
初始化与赋值:
c复制// 完全初始化
struct student stu = {"Tom", 18, 90.5};
// 部分初始化(C99特性)
struct student stu2 = {.name="Jerry", .age=19};
// 结构体拷贝
struct student stu3 = stu; // 合法操作
访问方式对比:
c复制// 直接访问
printf("Name: %s\n", stu.name);
// 指针访问
struct student *p = &stu;
printf("Age: %d\n", p->age);
// 数组访问
struct student class[30];
class[0].age = 20;
常见陷阱:结构体包含指针成员时,直接赋值会导致浅拷贝问题。此时需要手动实现深拷贝。
2. 共用体:内存共享的魔法
2.1 共用体的本质特性
共用体(union)允许不同数据类型共享同一块内存空间,这在协议解析、类型转换等场景非常有用:
c复制union data {
int i;
float f;
char str[4];
} data;
关键特性:
- 所有成员共享同一内存地址
- 空间大小由最大成员决定
- 同一时刻只能有效存储一个成员的值
2.2 大小端检测实战
共用体最经典的应用就是检测处理器的大小端模式:
c复制union endian_test {
int value;
char bytes[4];
} test;
test.value = 0x12345678;
if (test.bytes[0] == 0x78) {
printf("Little endian\n");
} else {
printf("Big endian\n");
}
原理分析:
- 小端模式:低位字节存储在低地址(x86架构)
- 大端模式:高位字节存储在低地址(网络字节序)
2.3 共用体高级应用
协议解析示例:
c复制union protocol {
struct {
uint8_t type;
uint8_t cmd;
uint16_t length;
} header;
uint8_t raw[4];
} packet;
// 接收网络数据
recv(socket, packet.raw, 4, 0);
// 直接访问解析后的字段
printf("Packet type: %d\n", packet.header.type);
类型转换技巧:
c复制union converter {
float f;
uint32_t u;
} conv;
conv.f = 3.14f;
printf("Float bits: 0x%08X\n", conv.u);
注意事项:共用体成员访问需要严格同步,不当使用会导致数据解释错误。
3. 枚举:优雅的状态管理
3.1 枚举基础与语法糖
枚举为离散值提供了语义化的命名方式:
c复制enum week { SUN, MON, TUE, WED, THU, FRI, SAT };
// 指定起始值
enum color {
RED = 1,
GREEN, // 自动递增为2
BLUE = 5
};
枚举的本质是整型常量,但提供了类型检查:
c复制enum week day = MON; // 正确
day = 100; // 合法但不符合设计初衷
3.2 枚举与宏定义的世纪之争
| 特性 | 枚举 | 宏定义 |
|---|---|---|
| 处理阶段 | 编译期 | 预处理期 |
| 类型检查 | 有 | 无 |
| 调试支持 | 可显示符号名 | 只显示替换后的值 |
| 作用域 | 遵循C作用域规则 | 全局除非#undef |
| 关联性 | 值自动关联 | 需要手动维护 |
3.3 枚举工程实践
状态机实现示例:
c复制enum state { IDLE, RUNNING, ERROR };
void handle_state(enum state s) {
switch(s) {
case IDLE:
// 空闲状态处理
break;
case RUNNING:
// 运行状态处理
break;
case ERROR:
// 错误处理
break;
}
}
标志位组合技巧:
c复制enum flags {
FLAG_A = 1 << 0, // 0001
FLAG_B = 1 << 1, // 0010
FLAG_C = 1 << 2 // 0100
};
int status = FLAG_A | FLAG_C; // 组合标志
if (status & FLAG_A) {
// 检查A标志
}
4. 位操作:硬件的语言
4.1 位运算基础大全
嵌入式开发必须掌握的6种位操作:
-
按位与(&):掩码操作、清零特定位
c复制val &= ~(1 << n); // 第n位清零 -
按位或(|):置位操作
c复制val |= (1 << n); // 第n位置1 -
按位异或(^):位翻转、交换变量
c复制a ^= b; b ^= a; a ^= b; // 交换两数 -
按位取反(~):反转所有位
c复制mask = ~(0xFF << 8); // 生成掩码 -
左移(<<):乘以2^n
c复制x = y << 3; // y*8 -
右移(>>):除以2^n
c复制x = y >> 2; // y/4
4.2 位域:精打细算的内存
位域允许更精细地控制结构体成员的位数:
c复制struct {
unsigned int enable : 1;
unsigned int mode : 3;
unsigned int reserved : 4;
} control;
特性说明:
:n指定成员占用的位数- 不可取地址(&操作非法)
- 实际存储单元是int或unsigned int
4.3 寄存器操作实战
GPIO配置示例:
c复制#define GPIOA_CRL (*(volatile uint32_t*)0x40010800)
// 设置PA5为推挽输出(50MHz)
GPIOA_CRL &= ~(0xF << 5*4); // 清除原有配置
GPIOA_CRL |= (0x3 << 5*4); // 写入新配置
// 翻转PA5输出
GPIOA_ODR ^= (1 << 5);
位操作宏定义集锦:
c复制#define BIT(n) (1 << (n))
#define SET_BIT(var, n) ((var) |= BIT(n))
#define CLR_BIT(var, n) ((var) &= ~BIT(n))
#define TGL_BIT(var, n) ((var) ^= BIT(n))
#define CHK_BIT(var, n) ((var) & BIT(n))
5. 综合应用与性能优化
5.1 复合数据类型设计
通信协议示例:
c复制typedef struct {
union {
struct {
uint8_t dest;
uint8_t src;
uint16_t seq;
} header;
uint8_t raw[4];
} meta;
enum {
CMD_READ = 0x01,
CMD_WRITE = 0x02
} command;
struct {
uint32_t data1 : 16;
uint32_t data2 : 16;
} payload;
} packet_t;
5.2 内存优化技巧
-
成员排序优化:按大小降序排列减少填充
c复制// 优化前:12字节 struct bad { char a; int b; char c; }; // 优化后:8字节 struct good { int b; char a; char c; }; -
共用体节省空间:
c复制union sensor_data { int raw; struct { unsigned int value : 12; unsigned int status : 4; } parsed; };
5.3 位操作算法集锦
计算汉明重量(1的个数):
c复制int hamming_weight(uint32_t n) {
int count = 0;
while (n) {
n &= (n - 1); // 清除最低位的1
count++;
}
return count;
}
位反转算法:
c复制uint32_t reverse_bits(uint32_t x) {
x = ((x >> 1) & 0x55555555) | ((x & 0x55555555) << 1);
x = ((x >> 2) & 0x33333333) | ((x & 0x33333333) << 2);
x = ((x >> 4) & 0x0F0F0F0F) | ((x & 0x0F0F0F0F) << 4);
x = ((x >> 8) & 0x00FF00FF) | ((x & 0x00FF00FF) << 8);
x = (x >> 16) | (x << 16);
return x;
}
在嵌入式开发中,结构体、共用体、枚举和位操作就像瑞士军刀的不同工具,各有其适用的场景。掌握它们的本质特性并灵活运用,可以写出既高效又易于维护的嵌入式代码。