0%

C++内存对齐

什么是内存对齐

用一个例子带出这个问题,看下面的小程序 。理论上,64位系统下,int占4byte,char占一个byte,那么将它们放到一个结构体中应该占4+1=5byte;但是实际上,通过运行程序得到的结果是8 byte,这就是内存对齐所导致的。

#include
using namespace std;

1
2
3
4
5
6
7
8
9
10
11
12
struct{
int x;
char y;
}Test;

int main()
{
cout<<"sizeof(int) "<<sizeof(int)<<endl; // 4
cout<<"sizeof(char) "<<sizeof(char)<<endl; // 1
cout<<"sizeof(Test) "<<sizeof(Test)<<endl;; // 输出8不是5
return 0;
}

现代计算机中内存空间都是按照 byte 划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐。

为什么要进行内存对齐

性能提升

以64位系统为例,访问内存的IO是以8个字节为单位。现在考虑8字节单位的处理器取long类型变量(64位系统),该处理器只能从地址为8的倍数的内存开始读取数据。

假如没有内存对齐机制,数据可以任意存放,现在一个long变量存放在从地址1开始的连续8个字节地址中,该处理器去取数据时,需要完成多个步骤:
● 要先从0地址开始读取第一个8字节块,剔除不想要的字节(0地址)
● 然后从地址8开始读取下一个8字节块,同样剔除不要的数据(9~15地址)
● 最后留下的两块数据合并放入寄存器

现在有了内存对齐的,long类型数据只能存放在按照对齐规则的内存中,比如说0地址开始的内存。那么现在该处理器在取数据时一次性就能将数据读出来了,而且不需要做额外的操作,提高了效率。

跨平台的支持

有些硬件平台并不能访问任意地址上的任意数据的,只能处理特定类型的数据,否则会导致硬件层级的错误。

有些CPU(如基于 Alpha,IA-64,MIPS,和 SuperH 体系的)拒绝读取未对齐数据。当一个程序要求这些 CPU 读取未对齐数据时,这时 CPU 会进入异常处理状态并且通知程序不能继续执行。

举个例子,在 ARM,MIPS,和 SH 硬件平台上,当操作系统被要求存取一个未对齐数据时会默认给应用程序抛出硬件异常。所以,如果编译器不进行内存对齐,那在很多平台的上的开发将难以进行。

内存对齐规则

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。在64位系统中,gcc中默认#pragma pack(8),可以通过预编译命令#pragma pack(n),n = 1,2,4,8,16来改变这一系数。

有效对齐值:是给定值#pragma pack(n)和结构体中最长数据类型长度中较小的那个。有效对齐值也叫对齐单位。c++11中可以通过alignof(t)来获取。

了解了上面的概念后,我们现在可以来看看内存对齐需要遵循的规则:
(1) 基本类型的对齐值就是其sizeof值;
(2) 结构(struct)(或联合(union))的数据成员,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍;
(3) 结构体的总大小为 有效对齐值 的整数倍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<iostream>
using namespace std;

struct{
char a;
long b;
int c;
short d;
}Test;

int main()
{
cout<<"sizeof(char) "<<sizeof(char)<<endl; // 1
cout<<"sizeof(long) "<<sizeof(long)<<endl; // 8
cout<<"sizeof(int) "<<sizeof(int)<<endl; // 4
cout<<"sizeof(short) "<<sizeof(short)<<endl;// 2
cout<<"alignof(Test) "<<alignof(Test)<<endl;// 8
cout<<"sizeof(Test) "<<sizeof(Test)<<endl;; // 24
return 0;
}

对齐的过程:
● a放在其实地址0;
● b占用8个字节,根据规则2,对齐在min{8, 8}的整数倍,即8; c对齐在min{4, 8}的整数倍,即16;d对齐在min{2, 8}的整数倍,即20;
● 整个数据结构大小为min{8,8}的整数倍,即24。

顺道说一下,
● attribute((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
如,以下Test的占用长度为15。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<iostream>
using namespace std;

struct T{
char a;
long b;
int c;
short d;

}__attribute__((packed));

T Test;

int main()
{
cout<<"sizeof(char) "<<sizeof(char)<<endl; // 1
cout<<"sizeof(long) "<<sizeof(long)<<endl; // 8
cout<<"sizeof(int) "<<sizeof(int)<<endl; // 4
cout<<"sizeof(short) "<<sizeof(short)<<endl;// 2
cout<<"sizeof(Test) "<<sizeof(Test)<<endl;; // 15
return 0;
}

参考链接