字节对齐的总结

作用和原因

字节对齐(Byte Alignment)是计算机系统中数据在内存中存储的一种优化方式,要求数据的起始地址必须是某个特定值(通常是其自身大小或系统字长的整数倍)。对齐的目的是提升内存访问效率,避免因未对齐访问导致的性能损失或硬件异常。

部分的性能紧张的mcu不支持字节不对齐的访问。例如Cotex-m0系列的mcu。

关键字

关于字节对齐,这里有两个可以修改的方式:

1.__packed关键字

__packed通常用于修饰结构体或变量,强制其成员按1字节对齐。

cpp 复制代码
1.__packed关键字

struct __packed ModbusReg {
    short reg1; // 2字节,无填充
    short reg2; // 2字节,无填充
};

2.编译器预处理指令

cpp 复制代码
2.使用 编译器预处理指令(Compiler Pragma Directive)
#pragma pack(2)
struct __packed ModbusReg {
    short reg1; // 2字节,无填充
    short reg2; // 2字节,无填充
};
#pragma pack()

pack()中的数据可以由自己定义,结尾的不能填写表示恢复默认。而期间的代码会按照你定义的对齐字节进行排序。

#pragma pack(2)的作用域是:从指令出现的位置开始,直到文件结束 (或遇到下一个#pragma pack(n)/#pragma pack())。

对齐规则

  • char:1字节对齐,无填充。
  • short:2字节对齐,通常按2的倍数地址分配。
  • int/float:4字节对齐,地址为4的倍数。
  • double/long long:8字节对齐,地址为8的倍数。
  • 指针:32位系统为4字节对齐,64位系统为8字节对齐。
c 复制代码
struct Example {
    char a;      // 1字节-->启始地址0x0000;
    //填充字节       1字节-->启始地址0x0001,因为short与2对齐;
    short e;     // 2字节-->启始地址0x0002;
    char f;      // 1字节-->启始地址0x0004;
    //填充字节       3字节-->启始地址0x0005,因为int与4对齐;
    int b;       // 4字节-->启始地址0x0008;
};               // 总大小:12字节
 

实际用例

在我的实际项目中,定义modbus的寄存器,由于寄存器参数占用的内存太大,需要用__packed来修饰,保证其紧凑性,后续调用指针来也方便,不用去记住哪些地址是因为填充空闲导致调用错误。

例如在上面代码,如果直接使用结构体名作为变量强转后,使用指针指向,可能会指向被填充的无效字节。

但是以指针的方式对结构体的元素进行操作的时候发现,因为采用了packed修饰导致在对指针内的数据进行操作的时候出现了内存对齐的硬件错误。例如:

cpp 复制代码
#pragma pack(1) // 限制最大对齐字节数为1
typedef struct
{
    char x;   
    short z;  
    int e;    
}ModbusRegType_t;

ModbusRegType_t t_ModbusReg
#pragma pack() // 恢复默认

//这里使用 t_ModbusReg强转uint16_t作为pData指针形参传入
void ModbusRegWrite(uint16_t  regAdress,uint16_t *pData,uint16_t value)
{
    pData[regAdress] = value;
}

在代码运行过程中,因为t_ModbusReg使用了pack(1),导致字节不对齐。又因为,函数的形参定义中,我们使用的是结构体类型来修饰pData,导致mcu在执行的时候会使用强转的字节对齐的方式来进行数据的获取。执行函数会因为字节不对齐导致硬件错误。

1.在不支持字节不对齐的mcu中,仍要使用紧凑型的结构体或数据组变量时。采用以下方案:

修改传入的指针数据类型,因为char型的数据对齐规则是1,意味着 不管如何都不会触发字节不对齐的硬件错误规则。

cpp 复制代码
//这里使用 t_ModbusReg作为pData指针形参传入
void ModbusRegWrite(uint16_t  regAdress,uint8_t *pData,uint16_t value)
{
    pData[regAdress] = value&(0xFF00);
    pData[regAdress+1] = value&(0x00FF);
}

2.在支持字节不对齐的读取mcu中,采用以下方案:

cpp 复制代码
//这里使用 t_ModbusReg作为pData指针形参传入
void ModbusRegWrite(uint16_t  regAdress,__packed uint16_t *pData,uint16_t value)
{
    pData[regAdress] = value;
}

因为在解析函数的过程中,mcu对形参以默认的对齐规则进行解析,如果不加上紧凑型的数据关键词修饰,会导致以默认4字节对齐的规则解析不对齐的数据,导致出现问题。

相关推荐
韦禾水4 小时前
记录一次项目部署到tomcat的异常
java·tomcat
曦月合一4 小时前
树莓派安装jdk、tomcat、vnc、谷歌浏览器开机自启等环境配置
java·tomcat·树莓派
harder3214 小时前
RMP模式的创新突破
开发语言·学习·ios·swift·策略模式
jinanwuhuaguo5 小时前
OpenClaw工程解剖——RAG、向量织构与“记忆宫殿”的索引拓扑学(第十三篇)
android·开发语言·人工智能·kotlin·拓扑学·openclaw
Rust研习社5 小时前
使用 Axum 构建高性能异步 Web 服务
开发语言·前端·网络·后端·http·rust
此剑之势丶愈斩愈烈5 小时前
openssl 自建证书
java
面汤放盐5 小时前
何时使用以及何时不应使用微服务:没有银弹
java·运维·云计算
0xDevNull5 小时前
Spring Boot 自动装配:从原理到实践
java·spring boot·后端
qq_589568106 小时前
java学习笔记,包括idea快捷键
java·ide·intellij-idea
淘矿人6 小时前
从0到1:用Claude启动你的第一个项目
开发语言·人工智能·git·python·github·php·pygame