字节对齐的总结

作用和原因

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

相关推荐
Elastic 中国社区官方博客2 小时前
使用 Elastic 进行网络监控:统一网络可观测性
大数据·开发语言·网络·人工智能·elasticsearch·搜索引擎·全文检索
Codefengfeng2 小时前
Python Base环境中加包的方法
开发语言·python
清水白石0082 小时前
《Python 编程全景解析:从核心精要到测试替身(Test Doubles)五大武器的实战淬炼》
开发语言·python
甲枫叶3 小时前
【claude】Claude Code正式引入Git Worktree原生支持:Agent全面实现并行独立工作
java·人工智能·git·python·ai编程
六件套是我3 小时前
无法访问org.springframeword.beans.factory.annotation.Value
java·开发语言·spring boot
LYS_06183 小时前
C++学习(5)(函数 指针 引用)
java·c++·算法
S-码农3 小时前
Linux ——条件变量
linux·开发语言
清水白石0083 小时前
《Python 编程全景解析:从核心精要到 Hypothesis 属性基测试的边界探索》
开发语言·python