一、前述:数据对齐是可移植性很重要的一部分知识,学习这部分知识会让你知道为什么慎用memcmp来做整个c结构体的内容比较,注意数据对齐是编译器的操作。
二、什么是数据对齐?
对齐是跟数据块在内存中的位置相关的话题,对齐分为自然对齐和强制对齐;
自然对齐是指一个变量的内存地址正好是它长度的整数倍,也就是说,一个大小为2的n次方字节的数据类型,其地址的最低有效位的后n位都应该是0;
强制对齐是强制该变量进行对应字节的对齐。
三、为什么要进行数据对齐?
如果一个数据类型长度较小,用一个长度大的数据类型指针去进行类型转换,会出现对齐问题;
处理器在读取存储的数据时,有时候会二字节或者四字节的读,如果恰好你的数据是整齐的,那么会大大提高性能;举个例子:
struct A{
unsigned short a;
unsigned long b;
};
假设处理器一次性读4字节,处理器第一次读了a和一半的b,第二次读了一半的b,还需要对b进行高低位整合;如果此时进行4字节对齐,你会发现,处理器第一次读了a,第二次读了b,少了整合的过程。
四、结构体数据对齐规则
自然边界是指结构体中最大基本类型长度,默然对齐就是按自然边界进行数据对齐,举个例子:
struct A{
char a;
unsigned long b;
unsigned short c;
char d;
};
sizeof( A ) != 8, sizeof( A ) = 12;
可以看到结构体A中最大基本类型长度为unsigned long 4字节, 按4字节进行对齐, 变量a后需要补3个字节, b不需要补, c + d = 3, 需要补1个字节,所以4 + 4 + 4 = 12;
#pragma pack(n) 用来指定按n字节进行对齐, #pragma pack() 用来取消默认对齐方式,举个例子
#pragma pack( 2 )
struct A{
char a;
unsigned long b;
unsigned short c;
char d;
};
#pragma pack()
sizeof( A ) = 10;
可以看到指定了按2字节进行对齐, 变量a后需要补1个字节,b不需要补, c不需要补, d需要补一个字节, 所以2 + 4 + 2 + 2 = 10;
注意,如果n大于结构体中最长基本类型长度,那么忽略n,仍然按最长基本类型长度进行数据对齐,即在上面例子中,#pragma pack(8)是没有用的;
__attribute__((aligned(m)))也是一种指定对齐的方法,但是它和#pragma有很大差别,首先如果m大于结构体中最长基本类型长度,则按m进行对齐,如果小于结构体中最长基本类型长度,那么就按最长基本类型长度进行数据对齐;
__attribute__((packed))是取消优化数据对齐,即进行单字节数据对齐,请注意__attribute__((aligned(1)))不等同于__attribute__((packed));
__attribute__((aligned(m)))还需要注意结构体中相邻的基本数据类型变量,举个例子:
struct A{
char a;
unsigned long b;
unsigned short c;
char b;
}__attribute__((aligned(8)));
struct B{
unsigned long a;
unsigned long b;
}__attribute__((aligned(8)));
struct C{
unsigned short a;
unsigned short b;
unsigned long c;
}__sttribute__((aligned(8)));
sizeof( A ) = 16; sizeof( B ) = 8; sizeof( C ) = 8;
可以看到,其实我们定义的这三个结构体都是8字节长的变量,而且都是按8字节进行对齐,结果却不同;结论是编译器首先会把相同基本类型的变量遍历,直到不是相同的基本类型变量或者长度大于等于8字节长度,然后进行数据对齐。
有需要的交流的,可查看我的邮箱,谢谢