为什么需要字节对齐?_zkf11387的博客-CSDN博客_字节对齐的好处
上文中参考全面的解释了字节对齐这一问题,自己简单总结一下:
内存字节对齐目的: 提高CPU访问效率;
64位系统默认进行8字节对齐,32位系统默认进行4自己对齐;
基本数据类型地址是其长度整数倍就可实现内存对齐;
数组中对第一个地址进行对齐后,就可实现内部地址对齐;
联合体对其内部最长元素进行对齐,就可以实现地址对齐;
结构体地址对齐需要对其内部所有元素进行对齐,如下是结构体对齐规则:
1. 数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的偏移量是( #pragma pack 指定的数值和这个数据成员自身长度两者之间的较小值)的整数倍。
2. 结构体的整体对齐规则:在数据成员按照 #1 完成各自对齐之后,结构体本身也要进行对齐。对齐会将结构体的大小增加为( #pragma pack 指定的数值和结构体最大数据成员长度)中较小那个值的整数倍。
例如:
typedef struct test {
char a;//1字节
int b; //4字节
double c; //8字节
char d[11];//11字节
}Test;//数据成员
a的地址为0,则b的地址如果连续分配的话应该为1,但是由于内存对齐的要求,b的地址的偏移量应为4的整数倍,应为4,b占据[4,5,6,7] ,则c默认应从8地址开始,则相对于a的偏移量为8,满足要求,则c占据[8,9,10,11,12,13,14,15]字节位置,d从默认从16字节位置开始,d的长度是1,地址偏移量应为1的整数倍,现在16是1的16倍,所以地址从16开始是合理的,则d占据的空间是[16~27],根据规则2中描述结构体成员大小应扩充为32。
#include <iostream>
typedef struct test1 {
char a;//1字节
int b; //4字节
double c; //8字节
char d[11];//11字节
}Test;//数据成员
int main(int argc, const char* argv[]) {
Test t1;
//默认对齐系数8,即#pragma pack(8)
//char a, 1<8按1对齐,offset=0 [0]
//int b, 4<8按4对齐,char a占到[0],int b应该从地址1开始排, 地址1不是对齐数4的倍数,所以int b的首地址是从4的最小倍数开始,所以offset=4, [4...7];
//double c, 8=8按8对齐,int b已经占到[4...7],double c从地址8开始排,地址8是对齐数8的倍数,所以double c的首地址是从8的最小倍数开始,offse=8 [8...15]
//char d[11], 1<8按1对齐,double c已经占到[8...15], char d[11]从地址16开始,地址16是对齐数1的倍数,所以char d的,offset=16, 存储位置[16...26]
//最后为27位,又因为27不是内部最大成员中double 8位字节的倍数,所以补齐为32)
printf(" %lu\n %p\n %p\n %p\n %p\n %p\n", sizeof(t1), &t1 , &t1.a, &t1.b, &t1.c, &t1.d);
system("pause");
return 0;
}
输出结果为:
对于结构体内部仍有结构体的情况,内存对齐规则按照以下规则进行执行
数据成员为结构体:如果结构体的数据成员还为结构体,则该数据成员的“自身长度”为其内部最大元素的大小。(struct a 里存有 struct b,b 里有char,int,double等元素,那 b “自身长度”为 8)
#include <iostream>
typedef struct test1 {
char a;//1
int b;//4
double c;//8
char d[11];//11
}Test1;
typedef struct test2 {
char a;//1
int b; //4
double c;//8
char d[11];//11
Test1 t1;
}Test2;
int main(int argc, const char* argv[]) {
Test1 t1;
//默认对齐系数8,即#pragma pack(8)
//char a, 1<8按1对齐,offset=0 [0]
//int b, 4<8按4对齐,char a占到[0],int b应该从地址1开始排, 地址1不是对齐数4的倍数,所以int b的首地址是从4的最小倍数开始,所以offset=4, [4...7];
//double c, 8=8按8对齐,int b已经占到[4...7],double c从地址8开始排,地址8是对齐数8的倍数,所以double c的首地址是从8的最小倍数开始,offse=8 [8...15]
//char d[11], 1<8按1对齐,double c已经占到[8...15], char d[11]从地址16开始,地址16是对齐数1的倍数,所以char d的,offset=16, 存储位置[16...26]
//最后为27位,又因为27不是内部最大成员中double 8位字节的倍数,所以补齐为32)
printf(" %lu\n %p\n %p\n %p\n %p\n %p\n", sizeof(t1), &t1 , &t1.a, &t1.b, &t1.c, &t1.d);
Test2 t2;
//默认对齐系数8,即#pragma pack(8)
//char a, 1<8按1对齐,offset=0 [0]
//int b, 4<8按4对齐,char a占到[0],int b应该从地址1开始排, 地址1不是对齐数4的倍数,所以int b的首地址是从4的最小倍数开始,所以offset=4, [4...7];
//double c, 8=8按8对齐,int b已经占到[4...7],double c从地址8开始排,地址8是对齐数8的倍数,所以double c的首地址是从8的最小倍数开始,offse=8 [8...15]
//char d[11], 1<8按1对齐,double c已经占到[8...15], char d[11]从地址16开始,地址16是对齐数1的倍数,所以char d的,offset=16, 存储位置[16...26]
//t1 8=8按8对齐,自身大小为8,则使用32开始地址,ti的占用大小为[32.......64]
//最后为27位,又因为27不是内部最大成员中double 8位字节的倍数,所以补齐为32)
printf(" %lu\n %p\n %p\n %p\n %p\n %p\n %p\n", sizeof(t2), &t2, &t2.a, &t2.b, &t2.c, &t2.d, &t2.t1);
system("pause");
return 0;
}
如何更改内存对齐策略?
#pragma pack(2)
#include <iostream>
typedef struct test1 {
char a;//1
int b;//4
double c;//8
char d[11];//11
}Test1;
int main(int argc, const char* argv[]) {
Test1 t1;
//默认对齐系数2,即#pragma pack(2)
//char a, 1<2按1对齐,offset=0 [0]
//int b, 2<4按2对齐,char a占到[0],int b应该从地址1开始排, 地址1不是对齐数2的倍数,所以int b的首地址是从2的最小倍数开始,所以offset=2, [2...5];
//double c, 2<8按2对齐,int b已经占到[2...5],double c从地址6开始排,地址6是对齐数2的倍数,所以double c的首地址是从2的最小倍数开始,offse=6 [6...13]
//char d[11], 1<2按1对齐,double c已经占到[6...13], char d[11]从地址14开始,地址14是对齐数2的倍数,所以d的,offset=14, 存储位置[14...25]
//共26位,又因为26不是内部最大成员中double 8位字节的倍数,所以补齐为32)
printf(" %lu\n %p\n %p\n %p\n %p\n %p\n", sizeof(t1), &t1 , &t1.a, &t1.b, &t1.c, &t1.d);
system("pause");
return 0;
}
更改为2字节对齐后,输出为:
可见,字节调整为2字节对齐后内存占用减小了,但是会增加CPU的访问时间。
引出问题:结构体相等的比较为什么不能使用memcmp函数?
由于编译器对结构体进行了内存对齐操作,对于结构体中补齐的内存地址,编译器进行了随机值填充,导致这部分内存空间是无法进行比较,而memcmp是对全内存空间进行比较,所以出现问题,两个结构体的比较需要使用符号重载函数。