字节对齐的总结

作用和原因

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

相关推荐
不会代码的小猴8 小时前
21. 泛型编程上
开发语言·c++·笔记·算法
IKUN家族8 小时前
Spring IoC&DI
java·spring·rpc
一只旭宝8 小时前
细讲C加加【9】C++ std::function与std::bind详解|仿函数、绑定器、类成员绑定、占位符、成员偏移指针
开发语言·c++·算法
山荷枝10 小时前
03-框架--Spring
java·后端·spring
ChaHae-In10 小时前
SpringBoot统一功能处理
java·spring boot·后端
coder!mq11 小时前
说几个常见的语法糖?
java·开发语言·算法
gugucoding11 小时前
38. 【Java】Stream API(下):高级操作与性能
java·开发语言
Lhan.zzZ11 小时前
在 Visual Studio 2022 中打造可扩展的动态链接库模块:从零搭建到原理解析
开发语言·c++·visual studio
心平气和量大福大11 小时前
android-实例-蒲公英-更新与安装-5-签名与生成APK
android·java·开发语言
码哥DFS11 小时前
构造函数、实例对象、对象原型 ----三者关系
开发语言·javascript·原型模式