vs15 x86
在开始对alignas和alignof了解之前我们首先考虑一个问题:
struct Test{
char sex;
int length;
char name[10];
};
sizeof(Test)的值是多少呢?
如果你的答案是:15.那么你就需要了解下了.因为正确答案是20.为什么呢也要接着往下看.
1) 概念: 什么是内存对齐?
内存地址对齐是:一种在计算机内存中排列数据(表现为变量的地址)、访问数据(表现为CPU读取数据)的一种方式,包含了基本数据对齐和结构体数据对齐 以及 union和bitfield。
自然对齐: 如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在笔者环境下,假设一个int(sizeof(int)=4)变量的地址为0x00000004,那它就是自然对齐的。
2) 为什么要内存对齐?
内存对齐的作用不仅是便于CPU快速访问,同时合理的利用字节对齐可以有效地节省存储空间。
对于32位机来说,4字节对齐能够使CPU访问速度提高,比如说一个long类型的变量,如果跨越了4字节边界存储,那么CPU要读取两次,这样效率就低了。但是在32位机中使用1字节或者2字节对齐,反而会使变量访问速度降低。所以这要考虑处理器类型,另外还得考虑编译器的类型。在VS15中默认是4字节对齐的,GNU GCC 也是默认4字节对齐。
内存地址对齐是计算机语言自动进行的,也即是编译器所做的工作。但这不意味着我们程序员不需要做任何事情,因为如果我们能够遵循某些规则,可以让编译器做得更好,毕竟编译器不是万能的。
为了更好理解上面的意思,这里给出一个示例。在32位系统中,假如一个int变量在内存中的地址是0x00ff42c3,因为int是占用4个字节,所以它的尾地址应该是0x00ff42c6,这个时候CPU为了读取这个int变量的值,就需要先后读取两个word大小的块,分别是0x00ff42c0~0x00ff42c3和0x00ff42c4~0x00ff42c7,然后通过移位等一系列的操作来得到,在这个计算的过程中还有可能引起一些总线数据错误的。但是如果编译器对变量地址进行了对齐,比如放在0x00ff42c0,CPU就只需要一次就可以读取到,这样的话就加快读取效率。
3)内存对齐的原则
1, 对于标准数据类型只要它的地址(address)是它长度(即它在当前CPU架构下所需占用的字节数)的整数倍就了.
2, 对于非基本数据类型(struct/class, union, bitfield):
a) 如果内含的数据类型的长度均小于我们指定的对齐数, 那么按照这些数据类型中长度最长的进行对齐.
b) 如果内含的数据类型的长度有等于我们指定的对齐数的, 那么按照我们指定的对齐数来进行对齐.
这么来看其实union和class/struct有一些相同我们还是来看一个demo吧了解一下他们的区别:
case 1: #pragma pack(8)
#include <iostream>
#pragma pack(8)
//case 1: struct/class
struct Test {
char sex; // 1 < 8
long long length; //8 = 8
char name[10]; //1 < 8 补足16byte
};
//最终各个成员按8byte对齐
//因此sizeof(Test) = 32
//case 2: union
//由于是个union一次只能存放一个类型的数据
//因此分配的内存至少需要能够容下所需内存最大的那个成员数据.
union Test2 {
char c; //1 < 8
long long long_type; // 8 = 8
char name[10]; // 1 < 8
};
//最终按照8byte对齐
//因此sizeof(Test2) = 16
//case 3: bitfield
struct Test3 {
char c1 : 4; // 1 < 8
char c2 : 2; // 1 < 8
//其中c1 和 c2 加起来另外补足2byte
int number : 3; // 4 < 8
//number只占了3bit补足4byte
};
//最终按照4byte对齐.
//因此sizeof(Test3) = 8
int main()
{
std::cout << sizeof(Test) << std::endl;
std::cout << sizeof(Test2) << std::endl;
std::cout << sizeof(Test3) << std::endl;
std::cout << alignof(Test3) << std::endl;
return 0;
}
case 2: #pragma pack(4)
#include <iostream>
#pragma pack(4)
//case 1: struct/class
struct Test {
char sex; // 1 < 4
long long length; //8 > 4
char name[10]; //1 < 4
};
//最终各个成员按4字节对齐
//因此sizeof(Test) = 24
//case 2: union
//由于是个union一次只能存放一个类型的数据
//因此分配的内存至少需要能够容下所需内存最大的那个成员数据.
union Test2 {
char c; //1 < 4
long long long_type; // 4 < 8
char name[10]; // 1 < 4
};
//最终按照4byte对齐.为了保证为4byte的倍数
//因此sizeof(Test2) = 12
//case 3: bitfield
struct Test3 {
char c1 : 4; // 1 < 4
char c2 : 2; // 1 < 4
//其中c1 和 c2 加起来另外补足4byte
int number : 3; // 4 = 4
//number只占了3bit补足4byte
};
//最终按照4byte对齐.
//因此sizeof(Test3) = 8
int main()
{
std::cout << sizeof(Test) << std::endl;
std::cout << sizeof(Test2) << std::endl;
std::cout << sizeof(Test3) << std::endl;
std::cout << alignof(Test3) << std::endl;
return 0;
}
case 3: #pragma pack(2)
#include <iostream>
#pragma pack(2)
//case 1: struct/class
struct Test {
char sex; // 1 < 2
long long length; //2 < 8
char name[10]; //1 < 2
};
//最终各个成员按2byte对齐
//因此sizeof(Test) = 20
//case 2: union
//由于是个union一次只能存放一个类型的数据
//因此分配的内存至少需要能够容下所需内存最大的那个成员数据.
union Test2 {
char c; //1 < 2
long long long_type; // 2 < 8
char name[10]; // 2 < 4
};
//最终按照2byte对齐
//因此sizeof(Test2) = 10
//case 3: bitfield
struct Test3 {
char c1 : 4; // 1 < 2
char c2 : 2; // 1 < 2
//其中c1 和 c2 加起来另外补足2byte
int number : 3; // 2 < 4
//number只占了3bit补足4byte
};
//最终按照2byte对齐.
//因此sizeof(Test3) = 6
int main()
{
std::cout << sizeof(Test) << std::endl;
std::cout << sizeof(Test2) << std::endl;
std::cout << sizeof(Test3) << std::endl;
std::cout << alignof(Test3) << std::endl;
return 0;
}
4, 查看当前内存对齐策略.
alignof( type-id )
很多时候可能class啊之类的写的很复杂我们不好自己推算因此我们就一个直接使用该操作获取实际的内存对齐数.
5, 强制指定一个object的对齐方式
alignas( expression )
alignas( type-id )
alignas( pack ... )
demo:
// every object of type sse_t will be aligned to 16-byte boundary
struct alignas(16) sse_t
{
float sse_data[4];
};
// the array "cacheline" will be aligned to 128-byte boundary
alignas(128) char cacheline[128];