ILP32 指 int,long、point 是 32 位。
LP64 指 long、point 是 64 位。
LLP64 指 long long、point 是 64 位。
偏移量的概念:
偏移量就是程序的逻辑地址与段首的差值。
1、什么是内存对齐
还是用一个例子带出这个问题,看下面的小程序,理论上,32位系统下,int占4byte,char占一个byte,那么将它们放到一个结构体中应该占4+1=5byte;但是实际上,通过运行程序得到的结果是8 byte,这就是内存对齐所导致的。
//32位系统
#include<stdio.h>
struct{
int x;
char y;
}s;
int main()
{
printf("%d\n",sizeof(s); // 输出8
return 0;
}
现代计算机中内存空间都是按照 byte 划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐。
2、为什么要进行内存对齐
1.尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的.它一般会以双字节,四字节,8字节,16字节甚至32字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度。(平台原因)
2.现在考虑4字节存取粒度的处理器取int类型变量(32位系统),该处理器只能从地址为4的倍数的内存开始读取数据。
3.假如没有内存对齐机制,数据可以任意存放,假设现在从地址1将一个int变量存放在连续四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址,也就是它占了地址1~3和地址4,但是0~3,4~7都是它的)(注意存取粒度存取,比如存取粒度是4,则会4个字节4个字节的存取),最后留下的两块数据合并放入寄存器,这需要做很多工作。
现在有了内存对齐的,int类型数据只能存放在按照对齐规则的内存中,比如说0地址开始的内存。那么现在该处理器在取数据时一次性就能将数据读出来了,而且不需要做额外的操作,提高了效率。
3、内存对齐规则
1.每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。gcc,vs环境下默认对齐数是4,可以通过预编译命令#pragma pack(n),n = 1,2,4,8,16来改变这一系数。
2.了解了上面的概念后,我们现在可以来看看内存对齐步骤如下:
(1)首先第一个成员的首地址就是结构体的首地址
(2)从第二个成员开始,都要对齐到对齐数的整数倍地址处。(对齐数:成员自身大小和默认对齐数#pragma pack(n)两者的较小值)(该成员的首地址就是对齐数的整数倍,注意不能占用别人的地址,也不能占用别人的填充地址。)
(3)结构体总大小,必须是最大对齐数的整数倍(最大对齐数:就是所有成员中的对齐数中最大的数。比如说结构体A的最大对齐数是4)
下面给出几个例子以便于理解:
1 //32位系统 2 #include<stdio.h> 3 struct 4 { 5 int i; 6 char c1; 7 char c2; 8 }x1; 9 10 struct{ 11 char c1; 12 int i; 13 char c2; 14 }x2; 15 16 struct{ 17 char c1; 18 char c2; 19 int i; 20 }x3; 21 22 int main() 23 { 24 printf("%d\n",sizeof(x1)); // 输出8 25 printf("%d\n",sizeof(x2)); // 输出12 26 printf("%d\n",sizeof(x3)); // 输出8 27 return 0; 28 }
eg1.#pragma pack(4)
(1)首先使用步骤(1)~(2),对成员变量进行对齐:
sizeof(c1) = 1 <= 4(#pragma pack(4)),按照1字节对齐,占用第0单元。
sizeof(i) = 4 <= 4(#pragma pack(4)),首地址应是4的倍数,这里单元4即不是别人的地址,也不是别人的填充地址,则首地址是单元4,占用单元4,5,6,7。
sizeof(c2) = 1 <= 4(#pragma pack(4)),首地址为1的倍数,0~7是别人的地址和别人的填充地址,因此首地址是单元8,占用单元8。
(2)然后使用步骤(3),单元0~8是9个字节,9不是4的倍数,9往后12是4的倍数,因此结构体占12个字节,这样就对结构体整体进行了对齐。
根据上面的分析,不难得出上面例子三个结构体的内存布局如下:
eg2.#pragma pack(1)
不难看出成员是连续存放的,三个结构体的大小都是6字节。
eg3.#pragma pack(2)
三个结构体的大小应为6,8,6。内存分布图如下:
3.含有数组时,成员的长度就是:数组中各元素数据类型的长度*元素的个数
eg.(#pragma pack(4))
1 struct stu1 { 2 char a[18];//sizeof(a)=18>(#pragma pack(4)),对齐数为4 3 int b[3]; 4 short c; 5 char d; 6 int e; 7 short f; 8 };
4.结构体嵌套:如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
#pragma pack(4)
1 struct stu2 { 2 char x; 3 int y; 4 double z; 5 char v[6]; 6 }; 7 struct stu1 { 8 char a; 9 struct stu2 b; 10 int c; 11 };
结果为32
5.__attribute__((packed))
取消变量对齐,按照实际占用字节数对齐(就是让变量之间排列紧密,不留缝隙)。(gcc才支持)
VS使用#pragma pack(push,1)
1 struct __attribute__((packed)) stu1 { // 取消内存对齐 2 char a; 3 long b; 4 short c; 5 float d; 6 int e; 7 };
alignas与 alignof
c++11以后引⼊两个关键字 alignas与 alignof。其中 alignof可以计算出类型的对齐方式,alignas可以指定结构体的对⻬⽅式。
alignas和#pragma pack的区别
- alignas是对某一个结构体设置对齐方式,#pragma pack是对所有结构体设置对齐方式
- alignas只能设置比默认对齐(vs默认是4)大的值,#pragma pack可以设置更小的对齐
offsetof
计算元素的偏移量
1 struct MyStruct 2 { 3 short a; 4 int b; 5 }; 6 int main() { 7 cout << offsetof(MyStruct, a) << "----->" << offsetof(MyStruct, b) << endl; 8 return 0; 9 }
参考文章:
(60条消息) C语言深度理解结构体(内存对齐、位段、偏移量、柔性数组)_字节偏移和位偏移_SPMAX的博客-CSDN博客