Featured image of post C语言进阶:内存与union的完美结合

C语言进阶:内存与union的完美结合

一、内存对齐:性能与空间的权衡

什么是内存对齐?

内存对齐是编译器为了优化内存访问速度而采取的策略。CPU访问对齐的内存地址比访问未对齐的地址要快得多。

1struct example {
2    char a;     // 1字节
3    int b;      // 4字节
4    short c;    // 2字节
5};
6
7// 实际内存布局可能:
8// [a][填充][填充][填充][b][b][b][b][c][c][填充][填充]
9// 总大小:12字节而不是7字节

常见对齐规则

  • char: 1字节对齐
  • short: 2字节对齐
  • int: 4字节对齐
  • float: 4字节对齐
  • double: 8字节对齐

手动控制对齐

 1// GCC扩展语法
 2struct __attribute__((packed)) tight_packed {
 3    char a;
 4    int b;
 5    short c;
 6}; // 大小:1+4+2=7字节
 7
 8// 标准C11语法
 9#pragma pack(push, 1)
10struct standard_packed {
11    char a;
12    int b;
13    short c;
14};
15#pragma pack(pop)

二、结构体成员排序的艺术

糟糕的排序浪费内存

1struct bad_order {
2    char a;     // 1字节
3    int b;      // 4字节(需要3字节填充)
4    char c;     // 1字节(需要3字节填充)
5    double d;   // 8字节
6}; // 总大小:1+3+4+1+3+8=20字节

优化后的排序

1struct good_order {
2    double d;   // 8字节
3    int b;      // 4字节
4    char a;     // 1字节
5    char c;     // 1字节
6}; // 总大小:8+4+1+1=14字节(节省6字节!)

排序原则

  1. 从大到小排列成员
  2. 相同类型的成员放在一起
  3. 考虑缓存行大小(通常64字节)

三、union:同一内存的多种视角

union的基本概念

1union data_union {
2    uint32_t raw;           // 4字节
3    uint8_t bytes[4];       // 4字节
4    struct {
5        uint16_t low;       // 2字节
6        uint16_t high;      // 2字节
7    } words;
8}; // 总大小:4字节

嵌入式中的典型应用

1. 寄存器访问

 1typedef union {
 2    uint32_t value;
 3    struct __attribute__((packed)) {
 4        uint32_t enable : 1;
 5        uint32_t mode : 3;
 6        uint32_t frequency : 8;
 7        uint32_t reserved : 20;
 8    } bits;
 9} control_reg_t;
10
11// 使用方式
12control_reg_t reg;
13reg.value = 0x12345678;
14if (reg.bits.enable) {
15    // 硬件使能
16}

2. 协议数据处理

 1struct __attribute__((packed)) sensor_data {
 2    uint8_t sensor_id;
 3    union {
 4        uint8_t raw[8];
 5        struct { float temp; float humidity; } environment;
 6        struct { uint16_t light; uint16_t uv_index; } optical;
 7        struct { int32_t pressure; } atmospheric;
 8    } payload;
 9};
10
11// 统一处理接口
12void process_packet(struct sensor_data *data) {
13    switch(data->sensor_id) {
14        case ENV_SENSOR:
15            printf("Temp: %.1f, Humidity: %.1f\n", 
16                   data->payload.environment.temp,
17                   data->payload.environment.humidity);
18            break;
19        case OPTICAL_SENSOR:
20            // 处理光学数据
21            break;
22    }
23}

3. 数据类型转换

 1union converter {
 2    float f_value;
 3    uint32_t i_value;
 4    uint8_t bytes[4];
 5};
 6
 7float read_temperature() {
 8    union converter conv;
 9    // 从ADC读取原始数据
10    conv.i_value = read_adc();
11    return conv.f_value;
12}

四、三者的完美结合:实战案例

案例:高效的通信协议实现

 1// 优化后的协议头结构
 2struct __attribute__((packed)) protocol_header {
 3    uint8_t destination;    // 目标地址
 4    uint8_t source;         // 源地址
 5    uint16_t packet_id;     // 包ID
 6    uint8_t flags;          // 控制标志
 7    uint8_t payload_type;   // 负载类型
 8    uint16_t payload_len;   // 负载长度
 9}; // 大小:8字节,无填充
10
11// 统一的数据包结构
12struct __attribute__((packed)) data_packet {
13    struct protocol_header header;
14    union {
15        uint8_t raw[56];  // 原始数据
16        struct {          // 类型1:传感器数据
17            int32_t values[8];
18            uint16_t status;
19            uint8_t timestamp[6];
20        } sensor_data;
21        struct {          // 类型2:控制命令
22            uint8_t command;
23            uint8_t parameters[16];
24            uint32_t checksum;
25        } control_cmd;
26        struct {          // 类型3:配置信息
27            uint16_t setting_id;
28            uint8_t value[20];
29            uint8_t reserved[10];
30        } configuration;
31    } payload;
32}; // 总大小:8 + 56 = 64字节(完美对齐缓存行)

内存映射硬件寄存器

 1// GPIO寄存器映射
 2typedef struct __attribute__((packed)) {
 3    union {
 4        uint32_t MODER;    // 模式寄存器
 5        struct {
 6            uint32_t MODER0 : 2;
 7            uint32_t MODER1 : 2;
 8            // ... 其他引脚
 9        } MODER_bits;
10    };
11    union {
12        uint32_t ODR;      // 输出数据寄存器
13        struct {
14            uint32_t ODR0 : 1;
15            uint32_t ODR1 : 1;
16            // ... 其他引脚
17        } ODR_bits;
18    };
19    // 更多寄存器...
20} GPIO_TypeDef;
21
22#define GPIOA ((GPIO_TypeDef *)0x40020000)
23
24// 使用示例
25void led_init() {
26    GPIOA->MODER_bits.MODER5 = 1;  // 设置PA5为输出模式
27    GPIOA->ODR_bits.ODR5 = 1;      // 设置PA5输出高电平
28}

五、最佳实践与注意事项

1. 可移植性考虑

1// 使用标准整数类型
2#include <stdint.h>
3#include <stddef.h>
4
5// 检查结构体大小和偏移
6static_assert(sizeof(struct protocol_header) == 8, 
7              "Header size mismatch");
8static_assert(offsetof(struct protocol_header, payload_len) == 6,
9              "payload_len offset wrong");

2. 字节序处理

 1uint32_t read_big_endian(const uint8_t *data) {
 2    return (data[0] << 24) | (data[1] << 16) | 
 3           (data[2] << 8) | data[3];
 4}
 5
 6void write_big_endian(uint8_t *data, uint32_t value) {
 7    data[0] = (value >> 24) & 0xFF;
 8    data[1] = (value >> 16) & 0xFF;
 9    data[2] = (value >> 8) & 0xFF;
10    data[3] = value & 0xFF;
11}

3. 调试技巧

1// 打印结构体内存布局
2void print_struct_layout(const void *data, size_t size) {
3    const uint8_t *bytes = (const uint8_t *)data;
4    for (size_t i = 0; i < size; i++) {
5        printf("%02X ", bytes[i]);
6        if ((i + 1) % 8 == 0) printf("\n");
7    }
8    printf("\n");
9}

六、性能测试对比

我们通过一个简单的测试来展示优化效果:

 1// 测试未优化结构体
 2struct unoptimized {
 3    char a;
 4    int b;
 5    char c;
 6    double d;
 7};
 8
 9// 测试优化后结构体  
10struct optimized {
11    double d;
12    int b;
13    char a;
14    char c;
15};
16
17void benchmark() {
18    struct unoptimized u[1000];
19    struct optimized o[1000];
20    
21    // 内存占用对比
22    printf("Unoptimized: %zu bytes\n", sizeof(u)); // 约20000字节
23    printf("Optimized: %zu bytes\n", sizeof(o));   // 约14000字节
24    
25    // 访问速度测试
26    // 优化后的结构体通常有更好的缓存命中率
27}