字节对齐的总结

作用和原因

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

相关推荐
李宥小哥3 分钟前
SQLite04-表数据管理
java·jvm·数据库
李宥小哥3 分钟前
SQLite05-常用函数
java·开发语言·jvm
huohuopro3 分钟前
idea配置servlet项目
java·servlet·intellij-idea
皮卡狮4 分钟前
C++面向对象编程的三大核心特性之一:多态
开发语言·c++
zzb15806 分钟前
Agent学习-ReAct框架
java·人工智能·python·机器学习·ai
zhangx1234_7 分钟前
java list介绍
java·开发语言·list
Java面试题总结7 分钟前
Go运行时系统解析: runtime包深度指南
开发语言·后端·golang
识君啊8 分钟前
拆分与合并的艺术·分治思想:Java归并排序深度解析
java·数据结构·算法·排序算法·归并排序·分治
左左右右左右摇晃10 分钟前
Java Object 类笔记
java·笔记
lly20240610 分钟前
jEasyUI 树形菜单加载父/子节点详解
开发语言