背景:在任务中遇到了由于内存对齐引起的一个Double数据读取错误问题,排查很久才发现偏移地址跑了4位。
内存对齐知识整理:
1、一个对齐的例子:
struct Struct1
{
char FileFlag[10]; //
char BuildUnit[100]; //
double BuildTime; // 偏移地址为110 否则按照默认为:112
};
以上结构体的前两个数据单元为char的数组,分别占10个字节和100个字节的内存,但是,由于C++语言默认进行8字节对齐,buildTime这个占8个字节的数据类型就会从8的最小整数倍作为偏移地址:即偏移地址不是110,而是112。整个结构体的数据大小也不是118,而是120字节。
2、内存对齐的好处:
简单来说,是为了提高处理器对于数据的处理效率。
这里涉及一个概念:cpu的内存访问粒度。目前的访问粒度都是4字节,也就是说cpu读取数据的起始偏移地址永远是4的整数倍。而我们常见的int型、double、long等占字节数较多的数据类型都是4的整数倍的字节占用,因此,如果这类数据的起始地址是4的整数倍,cpu就可以以最小的代价获取到数据,不用多进行一次数据访问然后再进行两次查询结果的拼接。
因此,c++语言在执行编译的时候自动对数据结构里的数据执行了对齐操作,加快运行效率。
具体的,
数据类型对齐:(由于cpu访问粒度为4,大于4个字节的数据类型的对齐即为不小于自身大小的4的整数倍)
类型 | 对齐值(字节) |
char | 1 |
short | 2 |
int | 4 |
float | 4 |
double | 8 |
类数据对齐:
使用成员当中最大的对齐字节来对齐。比如在Struct A中,int a的对齐字节为4,比char,short都大,所以A的对齐字节为4
指定对齐字节:
宏 #pragma pack(n)来指定的对齐值(后续有详细使用方法)
有效对齐字节值:
有效对齐字节=MIN(自身对齐字节, 指定对齐字节值)
可以随时使用sizeof运算符来计算此时的结构体大小来确认对齐情况。
3、如何调整对齐规则:
有时候,面对传输过来的数据,我们需要建立结构体来解析数据,如果数据本身没有按照我们这边的对齐方式来仿制数据就会读取错误。
方法为:
#progma pack(n) // n = 1, 2, 4, 8
这个设置是全局的,如果只是修改某个结构的对齐方式,需要加入宏堆栈概念:
#progma pack(push)
#progma pack(1)
...
#progma pack(pop)
举例:
#pragma pack(push)
#pragma pack(1)
struct TFileHeadInfo
{
char FileFlag[10]; //
char BuildUnit[100]; //
double BuildTime; // 偏移地址为110 否则按照默认为:112
};
#pragma pack(pop)
也可以写作:
#pragma pack(push, 1)
...
#pragma pack(pop)
其他:
- 内存对齐对于32位和64位平台有些许不同(32位cpu一次最多能处理32bits的信息,64位最多64bits)
- 使用sizeof运算符可以得到struct和union的确切大小(其中包含了因内存对齐所产生的内存碎片)
- 可以使用offsetof宏来得到一个数据成员在结构体中的位置
- 编译器会对内存的分布进行优化,所以理论计算值和实际输出值有时会有些许不同
- wnidows的默认对齐数为8,Linux的默认对齐数是4(可以理解为pragma宏的默认设置值)
- 结构体中数据成员的顺序可能会影响整个结构体所占内存的大小