字节对齐的总结

作用和原因

字节对齐(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字节对齐的规则解析不对齐的数据,导致出现问题。

相关推荐
考虑考虑5 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯6 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
青石路10 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
像我这样帅的人丶你还13 小时前
Java 后端详解(五):Redis 缓存
java·后端·全栈
plainGeekDev15 小时前
GreenDAO → Room
android·java·kotlin
亦暖筑序20 小时前
Java 8老系统AI Workflow实战:把一次性AI对话升级成可恢复工作流
java·后端
敲代码的彭于晏21 小时前
Bean 生命周期完全图解:前端同学也能看懂的 Spring 核心机制
java·前端·后端
plainGeekDev1 天前
ButterKnife → ViewBinding
android·java·kotlin
像我这样帅的人丶你还2 天前
Java 后端详解(四):分页与搜索
java·javascript·后端
她的男孩2 天前
数据权限为什么不能只靠注解?Forge 的 Mapper 层 SQL 改写源码拆解
java·后端·架构