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博客

C/C++内存对齐详解 - 知乎 (zhihu.com)

(60条消息) 【C/C++】内存对齐(超详细,看这一篇就够了)_c++内存对齐_谢老板不用蟹的博客-CSDN博客

posted on 2023-04-17 14:44  小凉拖  阅读(81)  评论(0编辑  收藏  举报